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

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

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

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

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

int main(void)
{
    int count = 0;
    while(1)
    {
        int sock;
        if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        {
            sleep(4);
            ERR_EXIT("socket");
        }

struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
            ERR_EXIT("connect");

struct sockaddr_in localaddr;
        socklen_t addrlen = sizeof(localaddr);
        if (getsockname(sock, (struct sockaddr *)&localaddr, &addrlen) < 0)
            ERR_EXIT("getsockname");

printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));
        printf("count = %d\n", ++count);

}

return 0;
}

启动select 的服务器端程序,再启动客户端测试程序:

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echoser_select

......................................................................

count = 1015
recv connect ip=127.0.0.1 port=51299
count = 1016
recv connect ip=127.0.0.1 port=51300
count = 1017
recv connect ip=127.0.0.1 port=51301
count = 1018
recv connect ip=127.0.0.1 port=51302
count = 1019
recv connect ip=127.0.0.1 port=51303
count = 1020
recv connect ip=127.0.0.1 port=51304
accept error: Too many open files

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./conntest

....................................................................................................

count = 1015
ip=127.0.0.1 port=51299
count = 1016
ip=127.0.0.1 port=51300
count = 1017
ip=127.0.0.1 port=51301
count = 1018
ip=127.0.0.1 port=51302
count = 1019
ip=127.0.0.1 port=51303
count = 1020
ip=127.0.0.1 port=51304
count = 1021
socket: Too many open files

输出太多条目,上面只截取最后几条,从中可以看出对于客户端,最多只能开启1021个连接套接字,因为总共是1024个,还得除去0,1,2。而服务器端只能accept

返回1020个已连接套接字,因为除了012之外还有一个监听套接字,客户端某一个套接字(不一定是最后一个)虽然已经建立了连接,在已完成连接队列中,但accept
返回时达到最大描述符限制,返回错误,打印提示信息。

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

将 sleep(4); 注释掉,观察服务器端的输出如下:

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echoser_select

...........................................................

count = 1018
recv connect ip=127.0.0.1 port=52323
client close 
count = 1019
recv connect ip=127.0.0.1 port=52324
client close 
count = 1020
recv connect ip=127.0.0.1 port=52325
client close 
count = 1021
recv connect ip=127.0.0.1 port=52234
client close 
client close

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

二、poll 函数应用举例

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数1:结构体数组指针,struct pollfd {

int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };

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

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

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

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

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

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
 
/*************************************************************************
    > File Name: echoser.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Fri 01 Mar 2013 06:15:27 PM CST
 ************************************************************************/

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<signal.h>
#include<sys/wait.h>
#include<poll.h>
#include "read_write.h"

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while (0)

int main(void)
{
    int count = 0;
    signal(SIGPIPE, SIG_IGN);
    int listenfd; //被动套接字(文件描述符),即只可以accept, 监听套接字
    if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        //  listenfd = socket(AF_INET, SOCK_STREAM, 0)
        ERR_EXIT("socket error");

struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(5188);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    /* servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
    /* inet_aton("127.0.0.1", &servaddr.sin_addr); */

int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
        ERR_EXIT("setsockopt error");

if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
        ERR_EXIT("bind error");

if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前
        ERR_EXIT("listen error");

struct sockaddr_in peeraddr; //传出参数
    socklen_t peerlen = sizeof(peeraddr); //传入传出参数,必须有初始值

int conn; // 已连接套接字(变为主动套接字,即可以主动connect)
    int i;

struct pollfd client[2048];
    int maxi = 0; //client[i]最大不空闲位置的下标

for (i = 0; i < 2048; i++)
        client[i].fd = -1;

int nready;
    client[0].fd = listenfd;
    client[0].events = POLLIN;

while (1)
    {
        /* poll检测[0, maxi + 1) */
        nready = poll(client, maxi + 1, -1);
        if (nready == -1)
        {
            if (errno == EINTR)
                continue;
            ERR_EXIT("poll error");
        }

if (nready == 0)
            continue;

if (client[0].revents & POLLIN)
        {

conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen); //accept不再阻塞
            if (conn == -1)
                ERR_EXIT("accept error");

for (i = 1; i < 2048; i++)
            {
                if (client[i].fd < 0)
                {
                    client[i].fd = conn;
                    if (i > maxi)
                        maxi = i;
                    break;
                }
            }

if (i == 2048)
            {
                fprintf(stderr, "too many clients\n");
                exit(EXIT_FAILURE);
            }

printf("count = %d\n", ++count);
            printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),
                   ntohs(peeraddr.sin_port));

client[i].events = POLLIN;

if (--nready <= 0)
                continue;
        }

for (i = 1; i <= maxi; i++)
        {
            conn = client[i].fd;
            if (conn == -1)
                continue;
            if (client[i].revents & POLLIN)
            {

char recvbuf[1024] = {0};
                int ret = readline(conn, recvbuf, 1024);
                if (ret == -1)
                    ERR_EXIT("readline error");
                else if (ret  == 0)   //客户端关闭
                {
                    printf("client  close \n");
                    client[i].fd = -1;
                    close(conn);
                }

fputs(recvbuf, stdout);
                writen(conn, recvbuf, strlen(recvbuf));

if (--nready <= 0)
                    break;
            }
        }

}

return 0;
}

/* poll 只受一个进程所能打开的最大文件描述符限制,这个可以使用ulimit -n调整 */

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

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ulimit -n 2048

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echoser_select

.............................................................................................

recv connect ip=127.0.0.1 port=57430
count = 2039
recv connect ip=127.0.0.1 port=57431
count = 2040
recv connect ip=127.0.0.1 port=57432
count = 2041
recv connect ip=127.0.0.1 port=57433
count = 2042
recv connect ip=127.0.0.1 port=57434
count = 2043
recv connect ip=127.0.0.1 port=57435
count = 2044
recv connect ip=127.0.0.1 port=57436
accept error: Too many open files

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ulimit -n 2048

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./conntest

................................................................................

count = 2039
ip=127.0.0.1 port=57431
count = 2040
ip=127.0.0.1 port=57432
count = 2041
ip=127.0.0.1 port=57433
count = 2042
ip=127.0.0.1 port=57434
count = 2043
ip=127.0.0.1 port=57435
count = 2044
ip=127.0.0.1 port=57436
count = 2045
socket: Too many open files

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

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ cat /proc/sys/fs/file-max

101078

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

参考:

《Linux C 编程一站式学习》

《TCP/IP详解 卷一》

《UNP》

select函数的并发限制和 poll 函数应用举例的更多相关文章

  1. UNIX网络编程——select函数的并发限制和 poll 函数应用举例

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

  2. 详述socket编程之select()和poll()函数

    转自:http://www.cppblog.com/myjfm/archive/2011/10/26/159093.aspx select()函数和poll()函数均是主要用来处理多路I/O复用的情况 ...

  3. select与poll函数介绍

    select与poll函数介绍 在所有依从POSIX的平台上,select函数使我们可以执行I/O多路转接.传向select的参数告诉内核: 1)我们所关心的描述符 2)对于每个描述符我们所关心的状态 ...

  4. I/O多路复用——select函数与poll函数

    1 区别 同:(1)机制类似,本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理.(2)包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就 ...

  5. 【unix网络编程第三版】阅读笔记(五):I/O复用:select和poll函数

    本博文主要针对UNP一书中的第六章内容来聊聊I/O复用技术以及其在网络编程中的实现 1. I/O复用技术 I/O多路复用是指内核一旦发现进程指定的一个或者多个I/O条件准备就绪,它就通知该进程.I/O ...

  6. UNIX网络编程 第6章 I/O复用:select和poll函数

    UNIX网络编程 第6章 I/O复用:select和poll函数

  7. select()函数用法三之poll函数

    poll是Linux中的字符设备驱动中有一个函数,Linux 2.5.44版本后被epoll取代,作用是把当前的文件指针挂到等待队列,和select实现功能差不多. poll()函数:这个函数是某些U ...

  8. UNP学习笔记4——I/O复用:select和poll函数

    1 概述 之间的学习中发现,传统的阻塞式系统调用不仅浪费进程运行时间,而且会带来狠毒问题.因此进程需要有一种预先告知内核的能力,使得内核一旦发现进程指定的一个或者多个I/O条件就绪,它就通知进程.这个 ...

  9. poll函数

    poll函数与select函数的功能基本一样,其定义如下: #include <poll.h> int poll(struct pollfd fds[], nfds_t nfds, int ...

随机推荐

  1. IDEA是如何导入项目的,及启动导入项目遇到的问题:无法加载主类的一连串问题

    1.启动报错误: 找不到或无法加载主类 org.spring.springboot.Application 可能在工程下面有多个module,然后,module里面的iml配置文件不止一个,删除留主的 ...

  2. 理解JavaScript模仿块作用域

    1.JS没有块作用域 在C和Java中,一对大括号{}决定一个作用域,比如for循环.在js中,变量可以在函数任何一处定义,并且忽略重复定义.变量初始化之前使用,值永远是undefined. func ...

  3. HTML-<td> 标签的 valign 属性

    <html> <body> <table border="1" style="height:200px"> <tr&g ...

  4. 项目加入 TFS报错

      新建一个项目,然后在解决方案上右击,选择Add solution to source control的时候,总是失败,output窗口中出现的错误信息如下: An error was raised ...

  5. Font Awesome使用简介

      Font awesome是一种用字体来实现图标的CSS插件. 使用方法: 到http://fortawesome.github.io/Font-Awesome/ 下载代码包. 代码包解压到本地后, ...

  6. 让你看不懂的swift语法

    一.Swift杂谈 Swift语法出来时间不长,网络上的各种教程已经铺天盖地,可是基本上全部的教程都是来自官方翻译. 从Swift出来到如今.每天都在学习Swift.以下给出个人感受 Swift中的非 ...

  7. Ubuntu下配置jdk及maven等方法

    从/etc/profile文件代码得知系统启动会把/etc/profile.d目录下面所有的.sh文件进行加载,如果在其中新建.sh文件用来设立环境变量等,系统启动后也会加载到它们.另外一种方法就是修 ...

  8. 笔试题之javaweb

    Java web部分 1.Tomcat的优化经验      答:去掉对web.xml的监视,把jsp提前编辑成Servlet.      有富余物理内存的情况,加大tomcat使用的jvm的内存 2. ...

  9. STS 控制台 中文乱码(maven 中文乱码)

    用uriEncoding标签设置中文字符集就行了 <plugin> <groupId>org.apache.tomcat.maven</groupId> <a ...

  10. 开源 免费 java CMS - FreeCMS1.9 移动APP管理 网站配置

    项目地址:http://www.freeteam.cn/ 网站配置 管理员能够在这里设置当前管理网站是否同意移动app訪问,是否默认移动APP网站.首页的布局,首页数据最多载入页数. 从左側管理菜单点 ...