Socket
目录
I/O模型
[UNIX: registered: Network Programming] 提供了5种IO模型
Blocking I/O - 阻塞I/O
Nonblocking I/O - 非阻塞I/O
I/O Multiplexing - I/O多路复用
Signal-Driven I/O - 信号驱动I/O
Asynchronous I/O - 异步I/O
五种 I/O 模型比较
select
io多路复用是为了解决一个进程同时处理多个socket问题
一个简单的思路是
假设有N个socket链接,检测有socket接收到数据,遍历所有socket进行处理
// fds = file decriptors
for {
select (fds) // wait while fds poll callback POLL_IN
for fd range fds{
if fd has data{
read fd
}
}
}
- 被监控的fds需要从用户空间拷贝到内核空间 为了减少数据拷贝带来的性能损坏,内核对被监控的fds集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024)。
- 被监控的fds集合中,只要有一个有数据可读,整个socket集合就会被遍历一次调用sk的poll函数收集可读事件 由于当初的需求是朴素,仅仅关心是否有数据可读这样一个事件,当事件通知来的时候,由于数据的到来是异步的,我们不知道事件来的时候,有多少个被监控的socket有数据可读了,于是,只能挨个遍历每个socket来收集可读事件。
select遇到的问题
总共有三个问题需要解决
- 被监控的fds集合大小被限制了1024,不够用
- fds集合需要从用户空间拷贝到内核空间的问题,耗费性能
- 需要遍历fds集合才能知道有数据接收的fds列表
epoll
epoll 是对 select 和 poll 的改进,避免了“性能开销大”和“文件描述符数量少”两个缺点。
epoll 有以下几个特点:
- 使用红黑树存储文件描述符集合
- 使用队列存储就绪的文件描述符
- 每个文件描述符只需在添加时传入一次;通过事件更改文件描述符状态
epoll一共有3个接口
- epoll_create创建epoll实例,其实例内部存储:
监听列表:所有要监听的文件描述符,使用红黑树
就绪列表:所有就绪的文件描述符,使用队列
- epoll_ctl用来维护监视列表,可以添加或删除所要监听的 socket
epoll_ctl 会将文件描述符 fd 添加到 epoll 实例的监听列表里,同时为 fd 设置一个回调函数,并监听事件event。当fd上发生相应事件时,会调用回调函数,将 fd 添加到 epoll 实例的就绪队列上。
- 当调用epoll_wait时,如果就绪列表中存在socket,则直接返回,如果没有,则阻塞进程
epoll是如何解决select的三个问题
被监控的fds集合大小被限制了1024,不够用
select 使用整型数组存储文件描述符集合,而 epoll 使用红黑树存储,数量较大。
fds集合需要从用户空间拷贝到内核空间的问题,耗费性能
epoll通过内核与用户空间使用mmap(内存映射),将用户空间的一块地址和内核空间的一块地址同时映射到相同的一块物理内存地址,减少用户态和内核态之间的数据交换。
epoll 对于每个描述符,只需要在 epoll_ctl 传递一次,之后 epoll_wait 不需要再次传递这也大大提高了效率。
需要遍历fds集合才能知道有数据接收的fds列表
epoll_ctl 中为每个文件描述符指定了回调函数,并在就绪时将其加入到就绪列表,因此 epoll 不需要像 select 那样遍历检测每个文件描述符,只需要判断就绪列表是否为空即可。这样,在没有描述符就绪时,epoll 能更早地让出系统资源。
相当于时间复杂度从 O(n) 降为 O(1)
epoll的伪码描述
for{
active_fd = epoll_wait(fds)
read fd
}
参考
Chapter 6. I/O Multiplexing: The select and poll Functions
【操作系统】I/O 多路复用,select / poll / epoll 详解