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 4096
int 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