select的限制以及poll的使用

select的限制以及poll的使用

1.先说select在多路IO中的限制:1)linux中每个程序能够打开的最多文件描述符是有限制的。默认是1024.可以通过ulimit -n进行查看和修改:

xcy@xcy-virtual-machine:~/test/sock10_poll$ ulimit -n1024xcy@xcy-virtual-machine:~/test/sock10_poll$ ulimit -n 2048 // n 这里进行修改xcy@xcy-virtual-machine:~/test/sock10_poll$ ulimit -n2048xcy@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 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 : 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

#include

#include

#include

#include

#include

#include

#include

#include

#include

//#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

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

{

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

#include

#include

#include

#include

#include

#include

#include

/*

这里是暴力测试最多能连接几个。由于进程能打开的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:2037client connect, conn:2041,ip:127.0.0.1, port:38222, count:2038client connect, conn:2042,ip:127.0.0.1, port:38224, count:2039client connect, conn:2043,ip:127.0.0.1, port:38226, count:2040client connect, conn:2044,ip:127.0.0.1, port:38228, count:2041client connect, conn:2045,ip:127.0.0.1, port:38230, count:2042client connect, conn:2046,ip:127.0.0.1, port:38232, count:2043client connect, conn:2047,ip:127.0.0.1, port:38234, count:2044accept: Too many open filesxcy@xcy-virtual-machine:~/test/sock10_poll$

client的(截取):

......Server: port:8080, ip:127.0.0.1, count:2036Server: port:8080, ip:127.0.0.1, count:2037Server: port:8080, ip:127.0.0.1, count:2038Server: port:8080, ip:127.0.0.1, count:2039Server: port:8080, ip:127.0.0.1, count:2040Server: port:8080, ip:127.0.0.1, count:2041Server: port:8080, ip:127.0.0.1, count:2042Server: port:8080, ip:127.0.0.1, count:2043Server: port:8080, ip:127.0.0.1, count:2044Server: port:8080, ip:127.0.0.1, count:2045socket: Too many open filesxcy@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被关闭。

相关推荐

香江忆旧录||华洋杂处之下那些混血儿的命运……
饥荒新家眼球草怎么打 眼球草如何消灭
一步步教你写一份优秀的软件测试简历