Socket网络编程和多路复用select/poll/epoll

一:TCP通信的CS模型

        将TCP通信的CS(服务端、客户端)模型按照解答电话的过程做一个介绍

ServerClient
Socket()买电话Socket()买电话
bind()绑定电话卡bind()绑定电话卡
listen()监听
accept()接电话Connect()打电话
send()/recv()接发消息send()/recv()接发消息
close()挂电话close()挂电话

二:io多路复用

        网络编程多路复用主要技术点select、poll、epoll;其中select和poll底层实现是一致的。

常用宏定义:

FD_CLR(inr fd, fd_set *fdset);用来清除描述符集合fdset中的描述符fd

FD_ISSET(int fd,f d_set *fdset);用来检测描述符集合fdset中的描述符fd是否发生了变化

FD_SET(int fd, fd_set *fdset);用来将描述符fd添加到描述符集合fdset中

FD_ZERO(fd_set *fdset);用来清除描述符集合fdset

1:select、pool

函数原型及参数解析

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
//nfds:文件描述符的范围,因为文件描述符从0开始(0、1、2被标准输入、标准输出、标准错误占用),所以这里的参数为最大文件描述符+1

/*
    readfds:用来监控可读类型文件描述符的集合
    writefds:用来监控可写类型文件描述符的集合
    exceptfds:监控错误文件类型文件描述符的集合,判断io是否出错
    timeout:超时时间
        NULL:函数阻塞,直到监控的文件描述符集合中某个文件描述符发生变化为止;
        0:函数不阻塞,无论是否有文件描述符发生变化,都会返回继续执行后续操作;
        >0:超时时间,在超时时间内阻塞
*/
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

/*
    fds:监听的文件描述符数组
        struct pollfd {
            int    fd;       //待监听的文件描述符
            short  events;   //待监听的文件描述符对应的监听事件,取值为:POLLIN、POLLOUT、POLLERR
            short  revents;  //返回的事件信息,传入时,给0。如果满足对应事件的话,返回非0-->POLLIN、POLLOUT、POLLERR
         };

        
    nfds:要监控的文件描述符范围,最大文件描述符+1
    timeout:超时时间
        -1:阻塞等待,有事件之前,永远等待;
        0:不阻塞,立即返回
        >0:超时时长。单位:毫秒;
*/

select、pool底层分析:

        select\poll将需要监控的集合copy到内核当中,内核会轮循监控的集合直到有文件描述符发生变化返回。

select缺点:

  • select函数参数多;
  • 需要将整个reset集合拷贝到内核,reset是个位图,会有无效拷贝;
  • 对io的数量有限制,限制在1024(位图);
  • 每次要遍历集合,而返回就绪集合。

poll缺点:

        因为poll和select的底层实现是一样的,所以其缺点是一致的;

        但底层中select使用位图,而poll使用链表,所以对io的数量没有限制。

select示例

poll示例

2:epoll

API及参数分析

int epoll_create(int size);
/*
    这里的size无效,只要>0即可
    返回值:epoll模型的文件描述符,也可以说是底层红黑树根节点的值
*/

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/*
    epfd:    epoll模型的返回文件描述符
    op:      操作类型
        EPOLL_CTL_ADD:向epfd中添加一个待监听的文件描述符fd(将fd添加到红黑树中)
        EPOLL_CTL_MOD:修改已经存在于epfd中的文件描述符fd的事件
        EPOLL_CTL_DEL:从epfd中删除一个监听的文件描述符fd。
    fd:       需要监听的文件描述符
    event:    需要监听的事件
        events:表示需要监听的事件类型,可以是EPOLLIN(可读)、EPOLLOUT(可写)等等组合。
        data:用户数据,在事件触发时会被返回给调用者。
    返回值:0:成功  -1:失败
*/



int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
/*
    epfd:      epoll模型的返回文件描述符
    events:    用来接收返回的事件,一般都是一个数组,数组长度大于等于maxevents。
    maxevents: 期望监听的最大事件数量
    timeout:   超时时间,同select中的timeout

    返回值:
        成功:有事件发生的IO个数
        0:  无事件发生或超时
        -1: 发生错误
*/

  底层分析

        从底层来看,epoll_create创建了一个epoll模型,epoll模型由红黑树来维护;其中rbr为红黑树的根节点节点,红黑树的每个节点表示一个文件描述符,其中根节点文件描述符符为4(0-3被标准输入、输出、错误,以及要监听的socketfd占用);

        epoll_ctl函数将需要监听的文件描述符加入到红黑树中,当监听的某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用 epoll_wait 函数时,只会返回有事件发生的文件描述符的个数;

   epoll示例

epoll相较于select和poll的优点

  •  epoll不需要像select和poll一样将数据集合copy到内核,内核轮询监听的文件集合,再将文件集合copy到用户,减少了内核态和用户态的交互;
  • epoll也没有select的io数量限制
  • 底层使用红黑树维护,效率更高

水平触发EPOLLLT和边沿触发EPOLLET

        epoll默认是LT模式。

水平触发:

        只要缓冲区有数据,水平触发会一直读取,直到缓冲区数据为空;

边沿触发:

        只触发一次。

使用epoll水平触发模式,当socket可写时,会不停触发socket可写事件,如何处理?

1:需要向 socket 写数据的时候才把 socket 加入 epoll ,等待可写事件。接受到可写事件后,调用 write 或者 send 发送数据。当所有数据都写完后,把 socket 移出 epoll。

这种方式的缺点是,即使发送很少的数据,也要把 socket 加入 epoll,写完后在移出 epoll,有一定操作代价。

2:开始不把 socket 加入 epoll,需要向 socket 写数据的时候,直接调用 write 或者 send 发送数据。如果返回 EAGAIN,把 socket 加入 epoll,在 epoll 的驱动下写数据,全部数据发送完毕后,再移出 epoll。

这种方式的优点是:数据不多的时候可以避免 epoll 的事件处理,提高效率。

  • 20
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值