背景知识
网络通信过程
- TCP 和 UDP 协议通信都是通过操作系统的 Socket 来实现。
- 服务端调用 socket()函数,指定网络协议和传输协议创建 Socket。
- 服务端调用 bind()函数,将 Socket 绑定到 IP 地址和端口号。
- 可能存在多个网卡,所以需要指定 IP 地址。
- 服务端调用 listen()函数,将 Socket 设置为监听模式,等待客户端连接。
- 可以通过 netstat 命令查看端口号是否被监听。
- 服务端调用 accept()函数,从内核获取客户端连接,如果没有连接则阻塞。
- 客户端调用 socket()函数,指定网络协议和传输协议创建 Socket。
- 客户端调用 connect()函数,传入 IP 地址和端口号,向服务端发起连接请求。
- 客户端与服务端通过三次握手建立TCP 连接。
- 双方通过read()和write()函数读写数据。
IO多路复用
- IO多路复用是一种高效的 IO 处理方式,允许一个进程同时监听多个 IO 事件,在事件就绪时进行处理,避免阻塞。
- IO多路复用技术可以提高并发性能,减少资源占用,在网络编程中应用广泛。
- IO:输入输出的数据传输行为。
- 磁盘 IO:磁盘的输入输出。
- 网络 IO:网络的输入输出。
网络IO模型
同步阻塞IO
- IO操作时会阻塞线程,直到数据从内核空间复制到用户空间才恢复执行。
- linux默认所有 socket 操作都是阻塞的。
- 高并发时会导致创建大量线程,耗尽系统资源。
同步非阻塞IO
- IO操作时不阻塞线程,如果数据没有准备好,会立即返回一个错误。
- 通过设置 socket 选项,将 socket 设置为非阻塞模式。
- 需要用户线程不断轮询,虽然避免阻塞,但是会消耗大量的 CPU 资源。
同步多路复用IO
- 通过一个线程监听多个IO,哪个就绪就通知程序处理哪一个。
select
- 将描述符集合通过 select()传给内核,内核返回就绪的描述符数量。
- 是早期的多路复用技术,存在以下问题:
- 每次调用 select 都需要传入描述符集合,高并发时会消耗资源。
- 描述符集合数量受限,由宏FD_SETSIZE定义,如 1024。
- 当有就绪事件出现时需要遍历描述符集合。
poll
- 解决 select 的描述符集合数量受限的问题。
- 每次调用 poll 都需要传入描述符集合,高并发时同样会消耗资源。
epoll
- epoll_create:创建一个 epoll 句柄。
- epoll_ctl:向 epoll 对象添加/修改/删除所要监听的描述符。
- epoll_wait:等待返回就绪的文件描述符链表,类似于 select()。
- linux 内核 2.6 以后才支持,解决了 select 的所有问题,大部分高并发中间件都是使用 epoll。
- epoll 的文件描述符采用红黑树存储。
异步IO
- IO操作完成后会主动通知应用程序,应用程序可以继续执行其他任务。