TCP网络常见调优参数之一:select/epoll限制

案例简述

        服务端接受高并发客户端TCP请求超过1000路后,容易异常崩溃,且请求路数越多,出现网络连接异常的概率越大。

案例分析:

         客户端与服务器端之间都需要保持长连接,客户端通过connect接口请求连接,使用select去检测socket是否连接成功,select接口原型:

select(int maxfdpl, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

当socket超过1024值时,将socket设置到文件描述符集fd_set中时,会出现数组越界,导致未知的内存崩溃问题。数组越界原因如下:

查看了Linux的内核源码linux-2.6.32
#define __NFDBITS    (8 * sizeof(unsigned long))    //__NFDBITS = 32

#define __FD_SETSIZE    1024

#define __FDSET_LONGS   (__FD_SETSIZE/__NFDBITS)  //_FDSET_LONGS=32

typedef struct {
    unsigned long fds_bits [__FDSET_LONGS]; //fd_set结构体中fds_bits数组只有32
} __kernel_fd_set;
 

static __inline__ void __FD_SET(unsigned long fd, __kernel_fd_set *fdsetp)
{
   
unsigned long _tmp = fd / __NFDBITS;
   
unsigned long _rem = fd % __NFDBITS;
    fdsetp->fds_bits[_tmp] |= (1UL<<_rem);
}

由上代码片段可推测出fd_set是实现方式如下:

fd_set 结构体中成员unsigned long fds_bits [__FDSET_LONGS]占用总字节

sizeof(unsigned long)* __FDSET_LONGS = 128B,即占用128*8=1024 bitfd_set是一个可以检测描述符集,可以往里添加任意0~1024之间的数(FD_SET操作),思路是使用位图bitmap,往集合了添加socket描述符n时只需将第n个bit位置1,例如socket描述fd为33,则 该fd被设置在34bit出,即fds_bits[1]元素第二个bit被设置为1,由此得知当socket描述符大于等于1024时已经超出了unsigned long fds_bits [__FDSET_LONGS]能够表示的位图范围,如socket1025,按计算公式将使用fds_bits[32]元素,此时数组下标越界了(数组下标应该是fds_bits[0] ~ fds_bits[31]),将会产生未知的内存问题。

解决过程:

                  客户端采用短连接,临时创建的socket描述符值假设为10,使用完后,close掉,下次再创建socket,系统分配的socket描述符依然是10,所以客户端短连接情况下,socket的值大小很难增长到1024以上。如客户端出现socket很大的情况,那需要重点排查程序中存在socket文件句柄泄漏问题。

                   如果客户端必须使用长连接,创建申请的socket不能及时释放,当请求很多时,socket描述符的值就会一直递增增长,进行select检测FD_SET产生内存崩溃。此时需要使用epoll代替,epoll可以突破socket描述符大于1024的限制。

经验总结:

         当程序中需要处理大量网络请求时,建议不要使用select来检测socket状态,而使用epoll或者poll模型。以下简单介绍select和epoll区别:

1. select

  • select的第一个参数nfds为fdset集合中最大描述符值加1,fdset是一个位数组,其大小限制为__FD_SETSIZE(1024),位数组的每一位代表其对应的描述符是否需要被检查;
  • select的第二三四个参数表示需要关注读、写、错误事件的文件描述符位数组,这些参数既是输入参数也是输出参数,可能会被内核修改用于标示哪些描述符上发生了关注的事件。所以每次调用select前都需要重新初始化fdset。
  • timeout参数为超时时间,该结构会被内核修改,其值为超时剩余的时间。
  • select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返回。相当于每一次select都要从fd_set数组从头开始遍历以下。
  • select返回后,需要逐一检查关注的描述符是否被SET(事件是否发生)。

2. epoll

  • epoll是Linux特有的I/O复用函数。它在实现上与select、poll有很大的差异。首先,epoll使用一组函数来完成任务,而不是单个函数;
  • 其次epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无需像select那样每次调用都要重复传入文件的事件放在内核里的一个事件表中。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表;
  • epoll通过epoll_create创建一个用于epoll轮询的描述符,通过epoll_ctl添加/修改/删除事件,通过epoll_wait检查事件,epoll_wait的第二个参数用于存放结果。
  • epoll与select不同,首先,其不用每次调用都向内核拷贝事件描述信息,在第一次调用后,事件信息就会与对应的epoll描述符关联起来。另外epoll不是通过轮询,而是通过在等待的描述符上注册回调函数,当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间。
  • epoll返回后,该参数指向的缓冲区中即为发生的事件,对缓冲区中每个元素进行处理即可,而不需要像select那样进行轮询检查。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值