Selector:Channel 的多路复用器,
Unix 网络编程定义的 5 种 IO 模型:
- 阻塞 IO
- 非阻塞 IO
- IO 多路复用
- 信号驱动 IO
- 异步 IO
IO 多路复用:
select/poll/epoll
IO 模型:
阻塞 IO:
执行系统调用 recv,会阻塞线程直至有数据返回
非阻塞 IO:
执行系统调用 recv,会立刻返回,给出一个状态码
多路复用 IO:
一个 socket 在 unix 中是一个文件,int file descriptor 文件描述符 fd
可以往 fd 直接读取和写入
系统调用:
select-vs-poll-vs-epoll:https://devarea.com/linux-io-multiplexing-select-vs-poll-vs-epoll
https://blog.csdn.net/drdairen/article/details/53694550
select:
- 判断多个 fd 是否有事件产生
缺点:
- 无法知道是哪个 fd 产生的事件
- 使用 1024bit 的位图来保存 fd 的状态,必须循环 1024 来判断 fd 有事件
- 需要复制大量的句柄数据结构,从内核空间到用户空间
- 触发方式是水平触发,如果对一个 fd 没有完成 IO 操作,那么之后的 select 还是会设置这个 fd
poll:
- 使用链表保存 fd,所以没有 1024 的数量限制,但缺点依旧。应该是数组?
- 所有 Unix 系统都支持
epoll:
- 可以直接返回产生了事件的 fd,不需要再去循环位图
- 红黑树保存需要监控的事件,双链表保存返回满足条件的事件
- 只有 Linux 系统支持
中断:
CPU 被硬件中断,执行中断向量,中断向量在中断向量表中,中断向量是一小段程序
比如网卡有数据,会通过硬件触发 CPU 中断,CPU 响应中断,需要保存当前的上下文,来执行中断向量
问题:
CPU 空转:当没有事件发生时,selector.select() 有时候不会阻塞
C 语言写一个 SocketServer:
io.c
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#define MAXLEN 4096int main(int argc, char **argv){int listenfd, sock_fd;struct sockaddr_in servaddr;char buff[MAXLEN];int n;if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){printf("create socket error: %s(errno: %d)/n", strerror(errno), errno);exit(0);}memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(8001);if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1){printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);exit(0);}if (listen(listenfd, 10) == -1){printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);exit(0);}printf("waiting for client to connect\n");while (1){// 接入客户端if ((sock_fd = accept(listenfd, (struct sockaddr *)NULL, NULL)) == -1){printf("accept socket error: %s(errno: %d)", strerror(errno), errno);continue;}// 接收消息n = recv(sock_fd, buff, MAXLEN, 0);buff[n] = '\0';printf("recv msg from client: %s\n", buff);close(sock_fd);break;}close(listenfd);}
编译并运行:
gcc -o io io.c
./io
