众所周知,Linux下的多路复用函数select采用描述符集表示处理的描述符。描述符集的大小就是它所能处理的最大描述符限制。通常情况下该值为1024,等同于每个进程所能打开的描述符个数。

增大描述符集大小的唯一方法是先增大FD_SETSIZE的值,然后重新编译内核,不重新编译内核而改变其值时不够的。

在阅读Libev源码时,发现它实现了一种突破这种限制的方法。该方法本质上而言,就是自定义fd_set结构,以及FD_SET,FD_CLR,FD_ISSET宏。

首先看一下Linux中原fd_set结构的实现细节,我的虚拟机的系统版本是Ubuntu 14.10,内核为3.16.0-23-generic。在其中的/usr/include/i386-linux-gnu/目录下的<sys/select.h>、<bits/select.h>、<bits/typesizes.h>文件中找到了fd_set结构的实现细节,代码如下:

//<bits/typesizes.h>
#define __FD_SETSIZE 1024 //<sys/select.h>
typedef long int __fd_mask;
typedef __fd_mask fd_mask; #define FD_SETSIZE __FD_SETSIZE #define __NFDBITS (8 * (int) sizeof (__fd_mask))
#define NFDBITS __NFDBITS #define __FD_ELT(d) ((d) / __NFDBITS)
#define __FD_MASK(d) ((__fd_mask) 1 << ((d) % __NFDBITS)) typedef struct
{
/* XPG4.2 requires this member name. Otherwise avoid the name
from the global namespace. */
#ifdef __USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
} fd_set; //<bits/select.h>
#define __FD_SET(d, set) \
((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))
#define __FD_CLR(d, set) \
((void) (__FDS_BITS (set)[__FD_ELT (d)] &= ~__FD_MASK (d)))
#define __FD_ISSET(d, set) \
((__FDS_BITS (set)[__FD_ELT (d)] & __FD_MASK (d)) != 0) #if defined __GNUC__ && __GNUC__ >= 2
# define __FD_ZERO(fdsp) \
do { \
int __d0, __d1; \
__asm__ __volatile__ ("cld; rep; " __FD_ZERO_STOS \
: "=c" (__d0), "=D" (__d1) \
: "a" (0), "0" (sizeof (fd_set) \
/ sizeof (__fd_mask)), \
"1" (&__FDS_BITS (fdsp)[0]) \
: "memory"); \
} while (0) #else /* ! GNU CC */ # define __FD_ZERO(set) \
do { \
unsigned int __i; \
fd_set *__arr = (set); \
for (__i = 0; __i < sizeof (fd_set) / sizeof (__fd_mask); ++__i) \
__FDS_BITS (__arr)[__i] = 0; \
} while (0) #endif /* GNU CC */ //<sys/select.h>
#define FD_SET(fd, fdsetp) __FD_SET (fd, fdsetp)
#define FD_CLR(fd, fdsetp) __FD_CLR (fd, fdsetp)
#define FD_ISSET(fd, fdsetp) __FD_ISSET (fd, fdsetp)
#define FD_ZERO(fdsetp) __FD_ZERO (fdsetp)

代码比较简单,就不一一分析了,总结一下就是:fd_set是个结构体,它内部包含一个long  int类型的数组,数组长度为__FD_SETSIZE /__NFDBITS。也就是说该数组一共包含__FD_SETSIZE个位。每一位就代表一个描述符。

FD_SET就是将该数组相应的位置1,FD_CLR就是将该数组相应的位置0,FD_ISSET就是判断某位是否为1.

以上就是fd_set的具体实现。Libev采用的方法是动态申请fd_set的空间,并实现相应的宏。下面是模仿Libev的代码,实现的一个简单的echo服务器:

# define NFDBYTES (NFDBITS / 8)

#define IFERR(res, msg)             \
if(res < 0) \
{ \
perror(#msg " error"); \
return; \
} #define MYFDSET(fd) \
word = fd / NFDBITS; \
mask = 1UL << (fd % NFDBITS); \
if(word+1 > fdsize) \
{ \
roset = realloc(roset, (word+1) * NFDBYTES); \
rset = realloc(rset, (word+1) * NFDBYTES); \
for(; fdsize < word+1; fdsize++) \
{ \
((fd_mask *)rset)[fdsize] = ((fd_mask *)roset)[fdsize] = 0; \
} \
} \
((fd_mask *)roset)[word] |= mask; #define MYFDCLR(fd) \
word = fd / NFDBITS; \
mask = 1UL << (fd % NFDBITS); \
((fd_mask *)roset)[word] &= ~mask; void echoserver()
{
int listenfd = -1, confd = -1;
int res = -1;
int val = 1;
int i; char rbuf[1024];
char sbuf[1024]; void *roset = NULL;
void *rset = NULL;
int fdsize = 0; int word;
fd_mask mask; struct sockaddr_in serveraddr;
int addrlen = sizeof(serveraddr); listenfd = socket(AF_INET, SOCK_STREAM, 0);
IFERR(listenfd, socket); serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8898);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); res = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&val, sizeof(val));
IFERR(res, setsockopt); res = bind(listenfd, (struct sockaddr *)&serveraddr, addrlen);
IFERR(res, bind); MYFDSET(listenfd); res = listen(listenfd, 5);
IFERR(res, "listen error") for(;;)
{
memcpy(rset, roset, fdsize*NFDBYTES); res = select(fdsize * NFDBITS, (fd_set *)rset, NULL, NULL, NULL);
IFERR(res, select) for(i = fdsize-1; i >= 0; i--)
{
if(((fd_mask *)rset)[i] == 0) continue; int bit = 0;
fd_mask bitmask = 0;
int fd = 0;
for(bit = NFDBITS-1; bit >= 0; bit--)
{
bitmask = 1UL << bit;
if(((fd_mask *)rset)[i] & bitmask)
{
fd = i*NFDBITS + bit;
if(fd == listenfd)
{
confd = accept(listenfd, NULL, NULL);
IFERR(confd, accept)
MYFDSET(confd)
}
else
{
bzero(rbuf, 1024);
res = read(fd, rbuf, 1024);
if(res <= 0)
{
if(res < 0)perror("read error");
else printf("client over\n"); MYFDCLR(fd);
close(fd);
}
else
{
snprintf(sbuf, 1024, "server echo: %s", rbuf);
write(fd ,sbuf, strlen(sbuf));
}
}
}
}
}
}
}

该代码中,rset和roset是表示描述符集的动态数组,fdsize * NFDBITS就是该描述符集所能表示的最大位数。当需要添加新的描述符时,如果空间不够,则调用realloc动态增加。并置相应的位为1。除了动态增长之外,MYFDSET和MYFDCLR实现思路与原来的FD_SET和FD_CLR一样的。

下面是简单的测试代码,用python实现的TCP客户端:

from socket import *
import sys
from time import sleep serverAddr = ('localhost', 8898) clientlist = []
errlist = []
clientcnts = int(sys.argv[1]) for i in xrange(clientcnts):
clientlist.insert(i, socket(AF_INET, SOCK_STREAM)) for index, client in enumerate(clientlist):
try:
client.connect(serverAddr)
except Exception as e:
print 'exception catched : ' ,e
errlist.append(client) for i in errlist:
clientlist.remove(i) for index, client in enumerate(clientlist):
data = "this is %d client"%index
client.send(data)
data = client.recv(100)
print 'recieve : ', data sleep(10) for index, client in enumerate(clientlist):
client.close()

测试时,运行如下命令即可,3000表示并发量。

# python tcpclient.py 3000

当然,在运行服务端和客户端之前,需要增加每个进程所能打开的描述符的限制,否则的话,客户端会出现错误:socket.error: [Errno 24] Too many open files,服务端会出现错误:accept error: Too many open files。   增加限制的命令如下:

# ulimit -n 65535

总结: 虽然select的最大描述符限制是可以突破的,但是与poll或者epoll相比,select在处理大量描述符时依然性能有限。select的优势在于它的跨平台特性;以及在处理少量描述符时,它可能是最快的;而且如果多数的描述符都处于活跃状态的话,它的性能也会非常好。

参考:

http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod

http://codemacro.com/2014/06/01/select-limit/

Libev源码分析09:select突破处理描述符个数的限制的更多相关文章

  1. [转]Libev源码分析 -- 整体设计

    Libev源码分析 -- 整体设计 libev是Marc Lehmann用C写的高性能事件循环库.通过libev,可以灵活地把各种事件组织管理起来,如:时钟.io.信号等.libev在业界内也是广受好 ...

  2. 【MyBatis源码分析】select源码分析及小结

    示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...

  3. Libev源码分析10:libev中poll的用例

    在Libev中,使用poll作为backend时,涉及到下面几种数据结构: int *pollidxs; int pollidxmax; struct pollfd *polls; int pollm ...

  4. Libev源码分析02:Libev中的IO监视器

    一:代码流程 在Libev中,启动一个IO监视器,等待该监视器上的事件触发,然后调用该监视器的回调函数.整个的流程是这样的: 首先调用ev_default_loop初始化struct  ev_loop ...

  5. Libev源码分析01:Libev中的监视器结构(C结构体实现继承)

    在Libev的源码中,用到了一种用C实现类似C++中继承的技巧,主要是用宏和结构体实现. 在Libev中,最关键的数据结构就是各种监视器,比如IO监视器,信号监视器等等.这些监视器的多数成员都是一样的 ...

  6. Libev源码分析08:Libev中的信号监视器

    Libev中的信号监视器,用于监控信号的发生,因信号是异步的,所以Libev的处理方式是尽量的将异步信号同步化.异步信号的同步化方法主要有:signalfd.eventfd.pipe.sigwaiti ...

  7. Libev源码分析07:Linux下的eventfd简介

    #include <sys/eventfd.h> int eventfd(unsigned int initval, int flags); eventfd创建一个eventfd对象,该对 ...

  8. Libev源码分析06:异步信号同步化--sigwait、sigwaitinfo、sigtimedwait和signalfd

    一:信号简述 信号是典型的异步事件.内核在某个信号出现时有三种处理方式: a:忽略信号,除了SIGKILL和SIGSTOP信号不能忽略外,其他大部分信号都可以被忽略: b:捕捉信号,也就是在信号发生时 ...

  9. Libev源码分析08:Libev中的内存扩容方法

    在Libev中,如果某种结构的数组需要扩容,它使用array_needsize宏进行处理,比如: array_needsize (int, fdchanges, fdchangemax, fdchan ...

随机推荐

  1. Leetcode599.Minimum Index Sum of Two Lists

    假设Andy和Doris想在晚餐时选择一家餐厅,并且他们都有一个表示最喜爱餐厅的列表,每个餐厅的名字用字符串表示. 你需要帮助他们用最少的索引和找出他们共同喜爱的餐厅. 如果答案不止一个,则输出所有答 ...

  2. winform应用程序异常处理

    对于winform应用程序补抓异常信息,我们经常用到得try catch. 如果代码中在某个地方执行异常,但是没有加try catch,这个时候就需要做一些全局异常捕捉. 怎么做到全局异常捕捉.win ...

  3. MS17-010远程溢出漏洞 - 永恒之蓝 [CVE-2017-0143]

    MS17-010远程溢出漏洞(永恒之蓝) Ti:2019-12-25 By:Mirror王宇阳 MS17-010 CVE-2017-0143 MS17-010 CVE-2017-0144 MS17-0 ...

  4. arcgis投影测试

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  5. optim.py cs231n

    n如果有错误,欢迎指出,不胜感激 import numpy as np """ This file implements various first-order upda ...

  6. SQL语句问题具体什么意思呢?

    SQL语句问题 底下SQL查询语法中的 as A 和 as B 是什么意思?为什么A和B不用定义就能用? 程序代码:     Private Sub LoadFileList(ByVal strSub ...

  7. XML配置里的Bean自动装配与Bean之间的关系

    需要在<bean>的autowire属性里指定自动装配的模式 byType(根据类型自动装配) byName(根据名称自动装配) constructor(通过构造器自动装配) 名字须与属性 ...

  8. SiteMesh:一个优于Apache Tiles的Web页面布局、装饰框架

    一.SiteMesh项目简介 OS(OpenSymphony)的SiteMesh是一个用来在JSP中实现页面布局和装饰(layout and decoration)的框架组件,能够帮助网站开发人员较容 ...

  9. 2018年DDoS攻击全态势:战胜第一波攻击成“抗D” 关键

    2018年,阿里云安全团队监测到云上DDoS攻击发生近百万次,日均攻击2000余次.目前阿里云承载着中国40%网站,为全球上百万客户提供基础安全防御.可以说,阿里云上的攻防态势是整个中国攻防态势的缩影 ...

  10. Linux下C 更改字符在终端的显示颜色

    使用\033[01;04;32;41m之类的配色方案在需要输出显示的文本之前,可以改变应用程序输出文本的颜色或者背景颜色. 比如: #include <stdio.h> int main( ...