• 对select函数的理解


    对select函数的理解

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

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

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

    思路1,如果链接很多(C10k)线程就会很多,消耗系统资源,并增加调度成本(Java BIO)。

    思路2,每次都要遍历一边所有socket,链接很多时效率低,可能大部分链接都没数据(select)。

    思路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 是什么?
    一个结构体:

    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有数据。

    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)
  • 相关阅读:
    ICMP协议
    观察者模式-Observer
    模板方法模式-Template Method
    Java的演变过程
    汉诺塔-Hanoi
    外观模式-Facade
    JDK5-增强for循环
    JDK5-可变参数
    动态代理与AOP
    代理模式-Proxy
  • 原文地址:https://www.cnblogs.com/oaks/p/13170661.html
Copyright © 2020-2023  润新知