1.先說select在多路IO中的限制:
1)linux中每個程序能夠打開的最多文件描述符是有限制的。默認是1024.
可以通過ulimit -n進行查看和修改:
xcy@xcy-virtual-machine:~/test/sock10_poll$ ulimit -n
1024
xcy@xcy-virtual-machine:~/test/sock10_poll$ ulimit -n 2048 // n 這里進行修改
xcy@xcy-virtual-machine:~/test/sock10_poll$ ulimit -n
2048
xcy@xcy-virtual-machine:~/test/sock10_poll$
這就意味着我們的服務器進程最多能打開1024個文件描述符。(而且0 1 2 還已經被占用了)。
而且一般服務器還有一個監聽套接字,所以當第1021個連接發起時就會失敗(假定前面沒有關閉)。
2)我們知道select的第2-4個參數是這個類型的fd_set。這里東西可以把它看成是數組。這個數組也是有邊界的。
邊界就是 FD_SETSIZE。
man select的部分截取:
NOTES
An fd_set is a fixed size buffer. Executing FD_CLR() or FD_SET() with
a value of fd that is negative or is equal to or larger than FD_SETSIZE
will result in undefined behavior. Moreover, POSIX requires fd to be a
valid file descriptor.
這個數組最大就是FD_SETSIZE。超過這個數以后就會越界。
FD_SETSIZE定義在系統的頭文件中(具體哪個文件我沒找到),可以修改那個頭文件,再重新編譯內核。這樣比較麻煩。
想要突破這個限制,就需要poll函數了。
2.poll函數
先看man手冊(截取部分):
SYNOPSIS
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
DESCRIPTION
poll() performs a similar task to select(2): it waits for one of a set
of file descriptors to become ready to perform I/O.
也可以用來監測多個IO。但是不會被FD_SETSIZE限制。
參數:
fds:一般是一個struct pollfd類型的數組,
nfds:要監視的描述符的數目。
timeout:超時時間,-1表示不會超時。0表示立即返回,不阻塞進程。 >0表示等待數目的毫秒數。
返回值:
-1:出錯了,錯誤代碼在errno中
0:設置了超時時間,這里表示超時了
>0:數組中fds准備好讀、寫、或異常的那些描述符的總數量
下面來看看struct pollfd這個結構體:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events 請求的事件,具體哪些值見下面 */
short revents; /* returned events 返回的事件,有點像傳出參數。哪個事件發生了就存儲在這里*/
};
// events和revents的值可以是下面:
The bits that may be set/returned in events and revents are defined in
<poll.h>:
POLLIN There is data to read. //可讀
POLLPRI
There is urgent data to read (e.g., out-of-band data on
TCP socket; pseudoterminal master in packet mode has seen
state change in slave).
POLLOUT // 可寫
Writing now will not block.
POLLRDHUP (since Linux 2.6.17)
Stream socket peer closed connection, or shut down writ‐
ing half of connection. The _GNU_SOURCE feature test
macro must be defined (before including any header files)
in order to obtain this definition.
POLLERR // 出錯
Error condition (output only).
POLLHUP
Hang up (output only).
POLLNVAL
Invalid request: fd not open (output only).
3.實例:
先看server端:
#include<sys/types.h> #include<sys/socket.h> #include<sys/select.h> #include<netinet/in.h> #include<arpa/inet.h> #include<poll.h> #include<stdlib.h> #include<stdio.h> #include<string.h> #include<errno.h> //#define CLIENTCOUNT FD_SETSIZE #define CLIENTCOUNT 2048 int main(int argc, char **argv) { int listenfd = socket(AF_INET, SOCK_STREAM, 0); if(listenfd < 0) { perror("socket"); return -1; } unsigned short sport = 8080; if(argc == 2) { sport = atoi(argv[1]); } struct sockaddr_in addr; addr.sin_family = AF_INET; printf("port = %d\n", sport); addr.sin_port = htons(sport); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if(bind(listenfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return -2; } if(listen(listenfd, 20) < 0) { perror("listen"); return -3; } struct sockaddr_in connaddr; int len = sizeof(connaddr); int i = 0, ret = 0; struct pollfd client[CLIENTCOUNT]; for(i = 0; i<CLIENTCOUNT; i++) client[i].fd = -1; int maxi = 0; client[0].fd = listenfd; client[0].events = POLLIN; int count = 0; int nready = 0; char buf[1024] = {0}; while(1) { nready = poll(client, maxi+1, -1); if(nready == -1) { perror("select"); return -3; } if(nready == 0) { continue; } if(client[0].revents & POLLIN) { int conn = accept(listenfd, (struct sockaddr*)&connaddr, &len); if(conn < 0) { perror("accept"); return -4; } char strip[64] = {0}; char *ip = inet_ntoa(connaddr.sin_addr); strcpy(strip, ip); printf("client connect, conn:%d,ip:%s, port:%d, count:%d\n", conn, strip,ntohs(connaddr.sin_port), ++count); int i = 0; for(i = 0; i<CLIENTCOUNT; i++) { if(client[i].fd == -1) { client[i].fd = conn; client[i].events = POLLIN; if(i > maxi) maxi = i; break; } } if(i == CLIENTCOUNT) { printf("to many client connect\n"); exit(0); } if(--nready <= 0) continue; } for(i = 0; i < CLIENTCOUNT; i++) { if(client[i].fd == -1) continue; if(client[i].revents & POLLIN) { ret = read(client[i].fd, buf, sizeof(buf)); if(ret == -1) { perror("read"); return -4; } else if(ret == 0) { printf("client close remove:%d, count:%d\n", client[i], --count); close(client[i].fd); client[i].fd = -1; // 要在這里移除 } //printf("client%d:%s\n", client[i], buf); write(client[i], buf, sizeof(buf)); memset(buf, 0, sizeof(buf)); if(--nready <= 0) continue; } } } close(listenfd); return 0; }
所有的client都存放在數組struct pollfd client[CLIENTCOUNT]中。每連接一個就加入到數組中。
關於這個server 的理解,可以參考這個的例子(這兩個例子其實很像):http://www.cnblogs.com/xcywt/p/8087677.html
下面是client端:
#include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/select.h> #include<stdlib.h> #include<stdio.h> #include<string.h> /* 這里是暴力測試最多能連接幾個。由於進程能打開的fd的限制最多的1024. 所以這里最多是1024 - 3. 也就是連接1022個的時候就出錯了 (0 1 2 已經被占用了) 設置成2048就是另外一個結果了 */ int main(int argc, char **argv) { unsigned short sport = 8080; if(argc == 2) { sport = atoi(argv[1]); } struct sockaddr_in addr; addr.sin_family = AF_INET; printf("port = %d\n", sport); addr.sin_port = htons(sport); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); int count = 0; while(1) { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0) { perror("socket"); sleep(5); // 這個是為了保證連接完成 return -1; } if(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("connect"); return -2; } struct sockaddr_in addr2; socklen_t len = sizeof(addr2); if(getpeername(sockfd, (struct sockaddr*)&addr2, &len) < 0) { perror("getsockname"); return -3; } printf("Server: port:%d, ip:%s, count:%d\n", ntohs(addr2.sin_port), inet_ntoa(addr2.sin_addr), ++count); } return 0; }
client就是暴力連接,測試能連接的最大的數目:運行:
注意運行的終端需要將能打開的最大描述符設成2048,如果不改的話看不出效果。
結果(截取部分):
server:(最多只能有2048 - 4個能連接上來,0 1 2 已經被占用,還有一個監聽套接字)
......
client connect, conn:2040,ip:127.0.0.1, port:38220, count:2037
client connect, conn:2041,ip:127.0.0.1, port:38222, count:2038
client connect, conn:2042,ip:127.0.0.1, port:38224, count:2039
client connect, conn:2043,ip:127.0.0.1, port:38226, count:2040
client connect, conn:2044,ip:127.0.0.1, port:38228, count:2041
client connect, conn:2045,ip:127.0.0.1, port:38230, count:2042
client connect, conn:2046,ip:127.0.0.1, port:38232, count:2043
client connect, conn:2047,ip:127.0.0.1, port:38234, count:2044
accept: Too many open files
xcy@xcy-virtual-machine:~/test/sock10_poll$
client的(截取):
......
Server: port:8080, ip:127.0.0.1, count:2036
Server: port:8080, ip:127.0.0.1, count:2037
Server: port:8080, ip:127.0.0.1, count:2038
Server: port:8080, ip:127.0.0.1, count:2039
Server: port:8080, ip:127.0.0.1, count:2040
Server: port:8080, ip:127.0.0.1, count:2041
Server: port:8080, ip:127.0.0.1, count:2042
Server: port:8080, ip:127.0.0.1, count:2043
Server: port:8080, ip:127.0.0.1, count:2044
Server: port:8080, ip:127.0.0.1, count:2045
socket: Too many open files
xcy@xcy-virtual-machine:~/test/sock10_poll$
可以看到已經超過了1024個了。
poll可以突破FD_SETSIZE的限制,但是還是無法突破進程能打開最大文件描述符的限制。
下面命令可以查看進程能打開的最大文件描述符限制(ulimit不能設置無限大),和計算機的內存有關:
cat /proc/sys/fs/file-max
5.關於上面client的sleep(5)的作用:
如果沒有sleep(5):那么client這邊連接第2045的時候,進程會立即退出。就會關閉進程打開的套接字。TCP協議就會給server發送FIN段。從而server這邊就會檢測到有的client已經關閉了。所以server這邊的count就可能會不准確了。因為有的已經關閉了,就可以再次打開。
如果加上sleep(5):就可以保證前面2044個連接都發送過去了,只是第2045個連接會失敗。但是server也只能接收2044個連接。保證在關閉之前沒有client的fd被關閉。