select和epoll的实现机制

1. TCP三次握手和监听套接字的两个队列

 

1.1 未完成队列(SYN_RCVD状态)

当第一次握手之后,在未完成队列上建立条目

1.2 已完成队列(ESTABLISHED状态)

三次握手成功之后,该条目转移到已完成队列,accept能够返回。

int listen(int sockfd, int backlog);

其中backlog这个参数大概为这两个队列总和的最大值,最大值可能是1.5*backlog,这是一个模糊因子。每次握手之间的时间RTT通常为187ms左右。

accept由tcp服务器调用,用于从已完成队列的对头返回下一个已完成的连接,如果队列为空,则阻塞。

2. select的实现机制

accept每次只能返回一个连接,这个连接相应的网络io(socket)是否可读可写我们并不知道,read时很可能会阻塞住。而select可以解决这个问题,我们把关注的io和对应事件,通过select注册,当事件发生时,select会通知我们。

int select(int nfds, fd_set *readfds, fd_set *writefds,

fd_set *exceptfds, struct timeval *timeout);

timeout用来是设置等待时间(永远等待/等待间隔/不等待)

nfds 最大的文件描述符+1

readfds 检测读操作,如果想要接收客户端的数据

writefds 检测写数据的操作,服务器给客户端发数据

excepefds 检测文件描述符是否异常

文件描述符集合的操作

文件描述符集类型: fd_set rdset;

  • void FD_ZERO(fd_set *set); - 全部清空

  • void FD_CLR(int fd, fd_set *set) - 从集合中删除某一项
  • void FD_SET(int fd, fd_set *set); - 将某个文件描述符添加到集合
  • int FD_ISSET(int fd, fd_set *set); - 判断某个文件描述符是否在集合中

select允许进程指示内核等待多个事件中的任何一个发生,并只在一个或多个事件发生之后才唤醒它,否则堵塞休眠。换句话说就是,我们调用select时,可以通过参数告知内核对哪些描述符(读、写或者异常)感兴趣以及要等多长时间。

windows和linux上对select都有实现,可以说这是一个跨平台的函数。

但是它的缺点也很明显

  • 调用select需要把fd集合从用户态拷贝到内核态,开销大
  • 同时也需要在内核遍历传递所有fd,开销大
  • select支持的文件描述符数量很少,默认是1024,这对于高并发很不友好

 

 

3. epoll的实现机制

epoll可以看作是Linux在select/poll或者说io多路复用的一个重大改进,有了它之后,才是Linux在网络操作系统上占据重要份额。

3.1 epoll的数据结构

 

int epoll_create(int size);

epoll_create()创建一个新的epoll实例。返回参数为epoll示例的文件描述符,参数max_size 从Linux 2.6.8开始被忽略,但必须大于零。使用完epoll后,要close()掉,否则会造成fd资源泄漏。

这个实例核心的两个数据结构是要给红黑树和一个双向链表。红黑树位于内核空间管理 socket,双向链表存放就绪的 socket。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll_ctl 用于控制epoll实例事件,可以注册、修改、删除关注的socket。

int epoll_wait(int epfd, struct epoll_event *events,

int maxevents, int timeout);

epoll_wait 等待IO事件发生,当rdlist为空(无就绪fd)时挂起当前进程,直到rdlist不空时进程才被唤醒。返回就绪队列,还有队列的长度。这样就知道具体的哪些fd就绪了。

3.2 线程安全

epoll是线程安全的,它是通过锁来实现的。

3.3 ET和LT

EPOLL事件有两种模型:

Edge Triggered(ET) // 边沿触发,数据来了就触发一次,只支持非阻塞io

LevelTriggered(LT) //缺省方式,水平触发,有数据就出发,支持非阻塞io和阻塞io

3.4 EPOLLONESHOT

在多线程服务中,一个网络io到来时,数据开始处理,这时候这个网络io也就是socket又来了一个同样的事件,那么另一个线程来处理新的事件,这就会出现一个问题,不同的线程处理同一个socket,这会让程序变得复杂。

EPOLLONESHOT就是来解决这个问题的,在epoll上注册这个事件后,如果已经在处理中的socket,将不再重新注册相关事件。这样就保证了同一SOCKET只能被一个线程处理,避免了上述问题。

 

参考:

[1] https://www.cnblogs.com/love-yh/p/7425031.html

[2] UNIX网络编程

[3] https://blog.csdn.net/u013051748/article/details/108804064

[4] https://zhuanlan.zhihu.com/p/165162146

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值