对select函数的理解

1. 处理多个socket连接的方法

阻塞模式下服务端要解决多个客户连接的问题的3个思路:linux

  1. 每一个客户端的socket对应一个内核线程,在这个线程内部进行阻塞的read
  2. 单线程,本身记录一个socket列表,循环去内核中查询socket是否ready
  3. 单线程,系统提供一个ready状态的socket列表,主线程从这个列表中处理socket

思路1,若是连接不少(C10k)线程就会不少,消耗系统资源,并增长调度成本(Java BIO)。数组

思路2,每次都要遍历一边全部socket,连接不少时效率低,可能大部分连接都没数据(select)。socket

思路3,比较理想(epoll)。函数

2. select函数

2.1. 使用方法

函数原型:性能

select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);

select() 检查 readfds, writefds 中的 io描符是否可读、可写了,若是有ready状态的,函数就返回。
nfds是总共fd的个数,而不是最大的fd。线程

使用select函数步骤:指针

  1. 初始化 fd_set,把要监控的fd仍进去
  2. 调用select,阻塞
  3. 阻塞结束后,遍历查看fd_set中的ready的socket

fd_set 是什么?
一个结构体:rest

typedef    struct fd_set {
    __int32_t    fds_bits[__DARWIN_howmany(__DARWIN_FD_SETSIZE, __DARWIN_NFDBITS)];
} fd_set;

结构体中有一个数组,默认是是1024,这就是linux中select的函数限制最大连接数的缘由。
从新编译内核才能提升这个数字。
FD_ISSET 就是取对应位置状态值(0,1),而且在用户空间,
用户须要遍历编一遍这个数组来检查是哪一个socket有数据。code

select的内部实现:队列

  1. readfds 从用户空间传递到内核空间
  2. 将当前进程加入到 readfds 中的每一个socket的等待队列
  3. 当socket来数据了就把 线程唤醒(移出等待队列)
  4. 把有数据的fds 从内核空间搞到用户空间
  5. 用户空间看一遍fds,知道哪一个socket有数据了,而后read、accept

select的问题:

  1. 每次调用select就要把readfds 传到内核,wake的时候再拿回来须要传递1024 * 4 bytes
  2. 每次须要把当前线程加入到全部socket的等待队列
  3. 每次须要遍历一遍readfds来查看那个socket有数据

每次调用select都会有以上两次传递和两次遍历,当连接个数多时,性能降低比较快:

select可能的改进:

  1. readfds一直都在内核中维护,不要每次都送进来
  2. 能够动态单个变动内核中的readfds
  3. 就绪列表,传给内核一个指针,内核把这个指针指向就绪的sockets (避免来回传递全部的socket)
相关文章
相关标签/搜索