C++ Select()是一種系統調用函數,能在一組文件描述符中強制時間限制內等待一個或多個條件成立,可用於I/O多路復用。下面從不同方面對C++ Select()做詳細的闡述。
一、C Select函數
C Select函數的原型為:int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout),其中:
• nfds為文件描述符集合中數值最大的描述符加1
• readfds,writefds,exceptfds為文件描述符集合
• timeout表示阻塞時間
C Select函數用於檢查文件描述符集合中是否有可讀、可寫、異常等事件,可用於單進程內多個文件描述符的I/O事件監聽。
以下為C++ Select函數使用示例:
#include<stdio.h>
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
int main() {
fd_set rfds;
struct timeval tv;
int retval;
char buf[256];
/* 等待stdin(標準輸入)上的輸入 */
FD_ZERO(&rfds);
FD_SET(STDIN_FILENO, &rfds);
/* 等待5秒鐘 */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* 由於我們只等待stdin上的輸入,所以不需要傳遞參數除STDIN_FILENO外 */
if (retval == -1)
perror("select()");
else if (retval) {
fgets(buf, sizeof buf, stdin);
printf("輸入: %s\n", buf);
}
else
printf("沒有輸入數據在五秒內.\n");
return 0;
}
二、C Selection和S Selection
C Selection和S Selection都是Select函數的改進版,其擴展了參數類型,可以通過hash表實現高效的查找,提升了系統利用率。其中,C Selection適用於用戶空間,S Selection適用於內核空間,可以用於高速網路和操作系統的性能優化。
C++ Select函數的相關參數和C Selection、S Selection相比,基本一致。在使用S Selection時,需要在建立socket時進行一個參數的設置,同時需要用到poll函數。
下面是借用《UNIX環境高級編程》一書中關於S Selection的代碼示例:
#include<sys/time.h>
#include<sys/types.h>
/* Selserv.cpp */
int
main(int argc, char *argv[])
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
if (bind(listenfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
err_sys("bind error");
if (listen(listenfd, LISTENQ) < 0)
err_sys("listen error");
else
printf("<時間%ld> 監聽埠 %d on %s\n",
time(NULL), SERV_PORT, inet_ntop(AF_INET, &servaddr.sin_addr, str, sizeof(str)));
maxfd = listenfd; /* 定義初始最大描述符值 */
maxi = -1; /* 有效客戶連接下標 */
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* 初始化 */
FD_ZERO(&allset);
FD_SET(listenfd, &allset); /* 添加監聽描述符 */
for ( ; ; ) {
rset = allset; /* 每次循環時都重新設置select的可讀文件描述符集 */
if ( (nready = select(maxfd+1, &rset, NULL, NULL, NULL)) < 0)
err_sys("select error");
if (FD_ISSET(listenfd, &rset)) { /* 新的客戶連接請求 */
clilen = sizeof(cliaddr);
if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0)
err_sys("accept error");
printf("<時間%ld>:新的客戶連接來自%s: %d\n",
time(NULL), inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) {
client[i] = connfd; /* 保存新的描述符 */
break;
}
if (i == FD_SETSIZE)
err_quit("too many clients");
FD_SET(connfd, &allset); /* 添加新的文件描述符到讀集合中 */
if (connfd > maxfd)
maxfd = connfd; /* for select */
if (i > maxi)
maxi = i; /* 數組client中最大元素的下標 */
if (--nready <= 0)
continue; /* 沒有更多就緒事件時,繼續回到select()調用*/
}
for (i = 0; i <= maxi; i++) { /* 檢測客戶端socket */
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
/* 當客戶關閉連接時,伺服器也關閉對應的連接,並將相應的client數組元素設置為-1,表示空閑 */
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
} else {
Writen(sockfd, buf, n);
}
if (--nready <= 0)
break; /* 沒有更多就緒事件時,繼續回到select()調用*/
}
}
}
}
三、C Select語句
C Select語句是select函數的一種可移植、跨平台的I/O多路復用機制,可以在一組文件描述符上等待多個I/O事件,多線程同時處理。C Select語句適用於網路編程中,方便實現高並發的伺服器。
以下是一個使用C Select語句進行I/O復用的示例:
/* 實現一個TCP伺服器,監聽10086埠,並在有客戶端連接時返回當前系統時間 */
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<time.h>
const int MAXLINE = 1024, LISTENQ = 5, SERV_PORT = 10086;
int main() {
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
ssize_t n;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
fd_set rset, allset;
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("error: socket");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
perror("error: bind");
return -1;
}
if (listen(listenfd, LISTENQ) == -1) {
perror("error: listen");
return -1;
}
maxfd = listenfd;
maxi = -1;
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1;
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
for (;;) {
rset = allset;
if ((nready = select(maxfd + 1, &rset, NULL, NULL, NULL)) == -1) {
perror("error: select");
return -1;
}
if (FD_ISSET(listenfd, &rset)) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
printf("accept client ip=%s port=%d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) {
client[i] = connfd;
break;
}
if(i == FD_SETSIZE) {
perror("too many clients");
return -1;
}
FD_SET(connfd, &allset);
if (connfd > maxfd)
maxfd = connfd;
if (i > maxi)
maxi = i;
if (--nready == 0)
continue;
}
for (i = 0; i <= maxi; i++) {
if ((sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ((n = read(sockfd, buf, MAXLINE)) == 0) {
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
}
else {
time_t t = time(NULL);
char buffer[1024] = {0};
strftime(buffer, 1024, "%Y-%m-%d %H:%M:%S\n", localtime(&t));
write(sockfd, buffer, strlen(buffer));
}
if (--nready == 0)
break;
}
}
}
return 0;
}
四、C Select詳解與模型
C Select是一種基於事件驅動的編程範式,其主要用於網路編程中的I/O多路復用與高並發伺服器。其基本工作原理為:首先需要定義一組文件描述符,通過select函數等待其事件到達,當其中任意一個文件描述符事件發生變化時,select函數返回;在返回後,通過遍歷文件描述符集合獲取到其中發生變化的文件描述符並進行對應處理。
C Select模型與並發伺服器中的運作模型是有關聯的:在並發伺服器中,通過創建多線程來支持多並發客戶端連接,當每個線程需要處理多個不同的客戶端請求時,其I/O事件規律拓撲結構應採用Multiplexer或Reactor這兩種I/O通信模型,其中,Multiplexer模型使用了Select程序,而Reactor模型使用的是相對更為高級的Epoll機制。
以下為一個簡單的使用C Select函數的示例:
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/time.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>void server(int port) {
int fd = socket(AF_INET, SOCK_STREAM, 0); // 創建套接字
if (fd == -1) {
perror("socket");
return;
}
int optval = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval); // 配置選項
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
if (bind(fd, (struct sockaddr*)&addr, sizeof addr) == -1) { // 綁定埠
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/278898.html