http://blog.csdn.net/chenxun_2010/article/details/50489577

一、用select实现的并发服务器,能达到的并发数,受两方面限制

1、一个进程能打开的最大文件描述符限制。这可以通过调整内核参数。可以通过ulimit -n来调整或者使用setrlimit函数设置, 但一个系统所能打开的最大数也是有限的,跟内存大小有关,可以通过cat /proc/sys/fs/file-max 查看

2、select中的fd_set集合容量的限制(FD_SETSIZE,一般为1024) ,这需要重新编译内核。

可以写个测试程序,只建立连接,看看最多能够建立多少个连接,客户端程序如下:

[cpp] view plaincopy
  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <netinet/in.h>
  4. #include <arpa/inet.h>
  5. #include <signal.h>
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8. #include <errno.h>
  9. #include <string.h>
  10. #define ERR_EXIT(m) \
  11. do \
  12. { \
  13. perror(m); \
  14. exit(EXIT_FAILURE); \
  15. } while(0)
  16. int main(void)
  17. {
  18. int count = 0;
  19. while(1)
  20. {
  21. int sock;
  22. if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
  23. {
  24. sleep(4);
  25. ERR_EXIT("socket");
  26. }
  27. struct sockaddr_in servaddr;
  28. memset(&servaddr, 0, sizeof(servaddr));
  29. servaddr.sin_family = AF_INET;
  30. servaddr.sin_port = htons(5188);
  31. servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  32. if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
  33. ERR_EXIT("connect");
  34. struct sockaddr_in localaddr;
  35. socklen_t addrlen = sizeof(localaddr);
  36. if (getsockname(sock, (struct sockaddr *)&localaddr, &addrlen) < 0)
  37. ERR_EXIT("getsockname");
  38. printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));
  39. printf("count = %d\n", ++count);
  40. }
  41. return 0;
  42. }

服务器的代码serv.c

[cpp] view plaincopy
  1. #include<stdio.h>
  2. #include<sys/types.h>
  3. #include<sys/socket.h>
  4. #include<unistd.h>
  5. #include<stdlib.h>
  6. #include<errno.h>
  7. #include<arpa/inet.h>
  8. #include<netinet/in.h>
  9. #include<string.h>
  10. #include<signal.h>
  11. #include<sys/wait.h>
  12. #define ERR_EXIT(m) \
  13. do { \
  14. perror(m); \
  15. exit(EXIT_FAILURE); \
  16. } while (0)
  17. int main(void)
  18. {
  19. signal(SIGPIPE, SIG_IGN);
  20. int listenfd; //被动套接字(文件描述符),即只可以accept, 监听套接字
  21. if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
  22. //  listenfd = socket(AF_INET, SOCK_STREAM, 0)
  23. ERR_EXIT("socket error");
  24. struct sockaddr_in servaddr;
  25. memset(&servaddr, 0, sizeof(servaddr));
  26. servaddr.sin_family = AF_INET;
  27. servaddr.sin_port = htons(5188);
  28. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  29. /* servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
  30. /* inet_aton("127.0.0.1", &servaddr.sin_addr); */
  31. int on = 1;
  32. if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
  33. ERR_EXIT("setsockopt error");
  34. if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
  35. ERR_EXIT("bind error");
  36. if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前
  37. ERR_EXIT("listen error");
  38. struct sockaddr_in peeraddr; //传出参数
  39. socklen_t peerlen = sizeof(peeraddr); //传入传出参数,必须有初始值
  40. int conn; // 已连接套接字(变为主动套接字,即可以主动connect)
  41. int i;
  42. int client[FD_SETSIZE];
  43. int maxi = 0; // client数组中最大不空闲位置的下标
  44. for (i = 0; i < FD_SETSIZE; i++)
  45. client[i] = -1;
  46. int nready;
  47. int maxfd = listenfd;
  48. fd_set rset;
  49. fd_set allset;
  50. FD_ZERO(&rset);
  51. FD_ZERO(&allset);
  52. FD_SET(listenfd, &allset);
  53. int count = 0;
  54. while (1) {
  55. rset = allset;
  56. nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
  57. if (nready == -1) {
  58. if (errno == EINTR)
  59. continue;
  60. ERR_EXIT("select error");
  61. }
  62. if (nready == 0)
  63. continue;
  64. if (FD_ISSET(listenfd, &rset)) {
  65. conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);  //accept不再阻塞
  66. if (conn == -1)
  67. ERR_EXIT("accept error");
  68. printf("count = %d\n", ++count);
  69. for (i = 0; i < FD_SETSIZE; i++) {
  70. if (client[i] < 0) {
  71. client[i] = conn;
  72. if (i > maxi)
  73. maxi = i;
  74. break;
  75. }
  76. }
  77. if (i == FD_SETSIZE) {
  78. fprintf(stderr, "too many clients\n");
  79. exit(EXIT_FAILURE);
  80. }
  81. printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),
  82. ntohs(peeraddr.sin_port));
  83. FD_SET(conn, &allset);
  84. if (conn > maxfd)
  85. maxfd = conn;
  86. if (--nready <= 0)
  87. continue;
  88. }
  89. for (i = 0; i <= maxi; i++) {
  90. conn = client[i];
  91. if (conn == -1)
  92. continue;
  93. if (FD_ISSET(conn, &rset)) {
  94. char recvbuf[1024] = {0};
  95. int ret = read(conn, recvbuf, 1024);
  96. if (ret == -1)
  97. ERR_EXIT("read error");
  98. else if (ret  == 0) { //客户端关闭
  99. printf("client close \n");
  100. FD_CLR(conn, &allset);
  101. client[i] = -1;
  102. close(conn);
  103. }
  104. fputs(recvbuf, stdout);
  105. write(conn, recvbuf, strlen(recvbuf));
  106. if (--nready <= 0)
  107. break;
  108. }
  109. }
  110. }
  111. return 0;
  112. }
  113. /* select所能承受的最大并发数受
  114. * 1.一个进程所能打开的最大文件描述符数,可以通过ulimit -n来调整
  115. *   但一个系统所能打开的最大数也是有限的,跟内存有关,可以通过cat /proc/sys/fs/file-max 查看
  116. * 2.FD_SETSIZE(fd_set)的限制,这个需要重新编译内核
  117. */

huangcheng@ubuntu:~$ ./serv  
count = 1  
recv connect ip=127.0.0.1 port=48370  
count = 2  
recv connect ip=127.0.0.1 port=48371  
count = 3  
recv connect ip=127.0.0.1 port=48372  
count = 4  
recv connect ip=127.0.0.1 port=48373  
....................................  
recv connect ip=127.0.0.1 port=49389  
count = 1020  
recv connect ip=127.0.0.1 port=49390

accept error: Too many open files  
[cpp] view plaincopyprint?
huangcheng@ubuntu:~$ ./cli  
ip=127.0.0.1 port=46327  
count = 1  
ip=127.0.0.1 port=46328  
count = 2  
ip=127.0.0.1 port=46329  
count = 3  
ip=127.0.0.1 port=46330  
count = 4  
ip=127.0.0.1 port=46331  
count = 5  
ip=127.0.0.1 port=46332  
count = 6  
ip=127.0.0.1 port=46333  
.......................  
ip=127.0.0.1 port=47345  
count = 1020  
ip=127.0.0.1 port=47346  
count = 1021  
socket: Too many open files

输出太多条目,上面只截取最后几条,从中可以看出对于客户端,最多只能开启1021个连接套接字,因为总共是1024个,还得除去0、1、2。而服务器端只能accept 返回1020个已连接套接字,因为除了0、1、2之外还有一个监听套接字,客户端某一个套接字(不一定是最后一个)虽然已经建立了连接,在已完成连接队列中,但accept 返回时达到最大描述符限制,返回错误,打印提示信息。

也许有人会注意到上面有一行 sleep(4);当客户端调用socket准备创建第1022个套接字时,如上所示也会提示错误,此时socket函数返回-1出错,如果没有睡眠4s后再退出进程会有什么问题呢?如果直接退出进程,会将客户端所打开的所有套接字关闭掉,即向服务器端发送了很多FIN段,而此时也许服务器端还一直在accept ,即还在从已连接队列中返回已连接套接字,此时服务器端除了关心监听套接字的可读事件,也开始关心前面已建立连接的套接字的可读事件,read 返回0,所以会有很多 client close 字段 参杂在条目的输出中,还有个问题就是,因为read 返回0,服务器端会将自身的已连接套接字关闭掉,那么也许刚才说的客户端某一个连接会被accept 返回,即测试不出服务器端真正的并发容量。

  1. huangcheng@ubuntu:~$ ./serv
  2. count = 1
  3. recv connect ip=127.0.0.1 port=50413
  4. count = 2
  5. ....................................
  6. client close
  7. client close
  8. client close
  9. client close
  10. ...................................
  11. recv connect ip=127.0.0.1 port=51433
  12. client close
  13. count = 1021
  14. recv connect ip=127.0.0.1 port=51364
  15. client close
  16. client close

可以看到输出参杂着client close,且这次的count 达到了1021,原因就是服务器端前面已经有些套接字关闭了,所以accept 创建套接字不会出错,服务器进程也不会因为出错而退出,可以看到最后接收到的一个连接端口是51364,即不一定是客户端的最后一个连接。

二、poll 函数应用举例

[cpp] view plaincopy
  1. #include <poll.h>
  2. int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数1:结构体数组指针

[cpp] view plaincopy
  1. struct pollfd {
  2. int   fd;         /* file descriptor */
  3. short events;     /* requested events */
  4. short revents;    /* returned events */
  5. };

结构体中的fd 即套接字描述符,events 即感兴趣的事件,如下图所示,revents 即返回的事件。

参数2:结构体数组的成员个数,即文件描述符个数。

参数3:即超时时间,若为-1,表示永不超时。

poll 跟 select 还是很相似的,比较重要的区别在于poll 所能并发的个数跟FD_SETSIZE无关,只跟一个进程所能打开的文件描述符个数有关,可以在select 程序的基础上修改成poll 程序,在运行服务器端程序之前,使用ulimit -n 2048 将限制改成2048个,注意在运行客户端进程的终端也需更改,因为客户端也会有所限制,这只是临时性的更改,因为子进程会继承这个环境参数,而我们是在bash命令行启动程序的,故在进程运行期间,文件描述符的限制为2048个。

使用poll 函数的服务器端程序如下:

[cpp] view plaincopy
  1. #include<stdio.h>
  2. #include<sys/types.h>
  3. #include<sys/socket.h>
  4. #include<unistd.h>
  5. #include<stdlib.h>
  6. #include<errno.h>
  7. #include<arpa/inet.h>
  8. #include<netinet/in.h>
  9. #include<string.h>
  10. #include<signal.h>
  11. #include<sys/wait.h>
  12. #include<poll.h>
  13. #define ERR_EXIT(m) \
  14. do { \
  15. perror(m); \
  16. exit(EXIT_FAILURE); \
  17. } while (0)
  18. int main(void)
  19. {
  20. int count = 0;
  21. signal(SIGPIPE, SIG_IGN);
  22. int listenfd; //被动套接字(文件描述符),即只可以accept, 监听套接字
  23. if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
  24. //  listenfd = socket(AF_INET, SOCK_STREAM, 0)
  25. ERR_EXIT("socket error");
  26. struct sockaddr_in servaddr;
  27. memset(&servaddr, 0, sizeof(servaddr));
  28. servaddr.sin_family = AF_INET;
  29. servaddr.sin_port = htons(5188);
  30. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  31. /* servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
  32. /* inet_aton("127.0.0.1", &servaddr.sin_addr); */
  33. int on = 1;
  34. if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
  35. ERR_EXIT("setsockopt error");
  36. if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
  37. ERR_EXIT("bind error");
  38. if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前
  39. ERR_EXIT("listen error");
  40. struct sockaddr_in peeraddr; //传出参数
  41. socklen_t peerlen = sizeof(peeraddr); //传入传出参数,必须有初始值
  42. int conn; // 已连接套接字(变为主动套接字,即可以主动connect)
  43. int i;
  44. struct pollfd client[2048];
  45. int maxi = 0; //client[i]最大不空闲位置的下标
  46. for (i = 0; i < 2048; i++)
  47. client[i].fd = -1;
  48. int nready;
  49. client[0].fd = listenfd;
  50. client[0].events = POLLIN;
  51. while (1)
  52. {
  53. /* poll检测[0, maxi + 1) */
  54. nready = poll(client, maxi + 1, -1);
  55. if (nready == -1)
  56. {
  57. if (errno == EINTR)
  58. continue;
  59. ERR_EXIT("poll error");
  60. }
  61. if (nready == 0)
  62. continue;
  63. if (client[0].revents & POLLIN)
  64. {
  65. conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen); //accept不再阻塞
  66. if (conn == -1)
  67. ERR_EXIT("accept error");
  68. for (i = 1; i < 2048; i++)
  69. {
  70. if (client[i].fd < 0)
  71. {
  72. client[i].fd = conn;
  73. if (i > maxi)
  74. maxi = i;
  75. break;
  76. }
  77. }
  78. if (i == 2048)
  79. {
  80. fprintf(stderr, "too many clients\n");
  81. exit(EXIT_FAILURE);
  82. }
  83. printf("count = %d\n", ++count);
  84. printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),
  85. ntohs(peeraddr.sin_port));
  86. client[i].events = POLLIN;
  87. if (--nready <= 0)
  88. continue;
  89. }
  90. for (i = 1; i <= maxi; i++)
  91. {
  92. conn = client[i].fd;
  93. if (conn == -1)
  94. continue;
  95. if (client[i].revents & POLLIN)
  96. {
  97. char recvbuf[1024] = {0};
  98. int ret = read(conn, recvbuf, 1024);
  99. if (ret == -1)
  100. ERR_EXIT("readline error");
  101. else if (ret  == 0)   //客户端关闭
  102. {
  103. printf("client  close \n");
  104. client[i].fd = -1;
  105. close(conn);
  106. }
  107. fputs(recvbuf, stdout);
  108. write(conn, recvbuf, strlen(recvbuf));
  109. if (--nready <= 0)
  110. break;
  111. }
  112. }
  113. }
  114. return 0;
  115. }
  116. /* poll 只受一个进程所能打开的最大文件描述符限制,这个可以使用ulimit -n调整 */

参照前面对 select 函数 的解释不难理解上面的程序,就不再赘述了。来看一下输出:

[cpp] view plaincopy
  1. root@ubuntu:/home/huangcheng# ulimit -n 2048
  2. root@ubuntu:/home/huangcheng# su - huangcheng
  3. huangcheng@ubuntu:~$ ulimit -n
  4. 2048
  5. huangcheng@ubuntu:~$ ./serv
  6. ...........................
  7. count = 2042
  8. recv connect ip=127.0.0.1 port=54499
  9. count = 2043
  10. recv connect ip=127.0.0.1 port=54500
  11. count = 2044
  12. recv connect ip=127.0.0.1 port=54501
  13. accept error: Too many open files
[cpp] view plaincopy
  1. root@ubuntu:/home/huangcheng# ulimit -n 2048
  2. root@ubuntu:/home/huangcheng# su - huangcheng
  3. huangcheng@ubuntu:~$ ulimit -n
  4. 2048
  5. huangcheng@ubuntu:~$./cli
  6. ..........................
  7. ip=127.0.0.1 port=54499
  8. count = 2043
  9. ip=127.0.0.1 port=54500
  10. count = 2044
  11. ip=127.0.0.1 port=54501
  12. count = 2045
  13. socket: Too many open files

可以看到现在最大的连接数已经是2045个了,虽然服务器端有某个连接没有accept 返回。即poll 比 select 能够承受更多的并发连接,只受一个进程所能打开的最大文件描述符个数限制。可以通过ulimit -n  修改,但一个系统所能打开的文件描述符个数也是有限的,这跟系统的内存大小有关系,所以说也不是可以无限地并 发,可以查看一下本机的容量:

[cpp] view plaincopy
  1. huangcheng@ubuntu:~$ cat /proc/sys/fs/file-max
  2. 101598

本机是虚拟机,内存2G,能够打开的文件描述符个数大约在10w个左右。

UNIX网络编程——select函数的并发限制和 poll 函数应用举例相关推荐

  1. 并发编程应用场景_linux网络编程之select函数的并发限制和poll函数应用举例

    一.用select实现的并发服务器,能达到的并发数,受两方面限制 1.一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n来调整或者使用setrlimit函数设置, ...

  2. select函数的并发限制和 poll 函数应用举例

    一.用select实现的并发服务器,能达到的并发数,受两方面限制 1.一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n来调整或者使用setrlimit函数设置,  ...

  3. linux网络编程之多路I/O转接服务器poll函数

    (1)poll函数 头文件:#include<poll.h> int  poll(struct  pollfd*fds, nfds_t nfds,int timeout); struct  ...

  4. UNIX网络编程:I/O复用技术(select、poll、epoll)

    http://blog.csdn.net/dandelion_gong/article/details/51673085 Unix下可用的I/O模型一共有五种:阻塞I/O .非阻塞I/O .I/O复用 ...

  5. UNIX网络编程-listen函数及其包裹函数介绍

    UNIX网络编程-listen函数及其包裹函数介绍 函数简介 #include<sys/socket.h>int listen(int sockfd,int backlog);返回:若成功 ...

  6. 网编编程必看书籍:unix网络编程

    unix网络编程被誉为圣经,该书主要讲socket套接字相关,socket API,从底层剖析网络编程.网络编程中需要用到的一些经典函数,多路复用函数,这些都值得去反复学习研究. 目录: 录 Part ...

  7. UNIX网络编程之旅-配置unp.h头文件环境

    最近在学习Unix网络编程(UNP),书中steven在处理网络编程时只用了一个#include "unp.h"  相当有个性并且也很便捷 于是我把第三版的源代码编译实现了这个过程 ...

  8. UNIX网络编程学习笔记(代码超详细解析)(持续更新)

    1. 其他函数准备 1. TCP 回射服务器程序: str_echo 函数 #include "unp.h"void str_echo(int sockfd) {ssize_t n ...

  9. 【LINUX/UNIX网络编程】之使用消息队列,信号量和命名管道实现的多进程服务器(多人群聊系统)...

    RT,使用消息队列,信号量和命名管道实现的多人群聊系统. 本学期Linux.unix网络编程的第三个作业. 先上实验要求: 实验三  多进程服务器 [实验目的] 1.熟练掌握进程的创建与终止方法: 2 ...

最新文章

  1. Windows Phone开发(16):样式和控件模板
  2. Atom编辑Markdown文件保存后行尾的空格自动消失的问题解决
  3. 2019年低延迟直播技术展望
  4. Spoken English(001)
  5. python2.7教程 pdf_PYTHON基础教程至60课(2.7版本)整理
  6. 如何做漂亮实用的UI界面?UI/UX设计模板,帮你入手!
  7. 题目1022:游船出租(结构体使用)
  8. 【EDAS问题】轻量级EDAS部署hsf服务出现找不到类的解决方案
  9. 20180513 实参 形参
  10. html5新增的一个input属性
  11. 百度云盘群组中资源文件实时同步更新保存到自己群组的方法
  12. 新东方雅思词汇(List 41 ~ List 45)
  13. 盗版 Win7 试用到期后黑屏咋办
  14. 对于时间管理初识--时间管理入门
  15. cubemx stm32 陶晶驰 串口屏 基于YXY通信原理的串口屏驱动代码
  16. 视觉SLAM14讲笔记分享——第四章【李群与李代数】
  17. EDUCODER---计算机硬件基础---计算机系统测试 5.16.17.19.1 合集
  18. LP Wizard10.5破解笔记
  19. Java各种加密方式集锦(AES,DES,RSA,DSA,MD5,SHA)
  20. CSS字体、行高等其他样式

热门文章

  1. css中position初解
  2. Object-C,NSArraySortTest,数组排序3种方式
  3. android一键分享功能不使用任何第三方sdk
  4. .net 笔记尝试(二)
  5. c 给定字符串中查找_面试 | 查找类算法精析
  6. android 勿扰模式代码,Android N Zen Mode (勿扰模式)设置流程
  7. java缓冲输入流_java _io_字符缓冲流的输入、输出
  8. 我的python学习笔记全集_我的python学习笔记
  9. java月历组件_vue之手把手教你写日历组件
  10. 可消费消息数量_17 个方面,综合对比 主流消息队列