Selector:Channel 的多路复用器,

Unix 网络编程定义的 5 种 IO 模型:

  • 阻塞 IO
  • 非阻塞 IO
  • IO 多路复用
  • 信号驱动 IO
  • 异步 IO

IO 多路复用:

select/poll/epoll

TCP 粘包

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

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <sys/types.h>
  6. #include <sys/socket.h>
  7. #include <netinet/in.h>
  8. #define MAXLEN 4096
  9. int main(int argc, char **argv)
  10. {
  11. int listenfd, sock_fd;
  12. struct sockaddr_in servaddr;
  13. char buff[MAXLEN];
  14. int n;
  15. if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  16. {
  17. printf("create socket error: %s(errno: %d)/n", strerror(errno), errno);
  18. exit(0);
  19. }
  20. memset(&servaddr, 0, sizeof(servaddr));
  21. servaddr.sin_family = AF_INET;
  22. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  23. servaddr.sin_port = htons(8001);
  24. if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
  25. {
  26. printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
  27. exit(0);
  28. }
  29. if (listen(listenfd, 10) == -1)
  30. {
  31. printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
  32. exit(0);
  33. }
  34. printf("waiting for client to connect\n");
  35. while (1)
  36. {
  37. // 接入客户端
  38. if ((sock_fd = accept(listenfd, (struct sockaddr *)NULL, NULL)) == -1)
  39. {
  40. printf("accept socket error: %s(errno: %d)", strerror(errno), errno);
  41. continue;
  42. }
  43. // 接收消息
  44. n = recv(sock_fd, buff, MAXLEN, 0);
  45. buff[n] = '\0';
  46. printf("recv msg from client: %s\n", buff);
  47. close(sock_fd);
  48. break;
  49. }
  50. close(listenfd);
  51. }

编译并运行:

gcc -o io io.c
./io