Open JDK 在 1.6 版本以后就取消了 NIO 在 Linux 下的 select 模型,只提供 epoll 模型,那么这里我们就模拟一下 NIO 在 Linux 下调用 select 模型

Linux 下的 select 函数

  1. NAME
  2. select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing
  3. SYNOPSIS
  4. /* According to POSIX.1-2001, POSIX.1-2008 */
  5. #include <sys/select.h>
  6. /* According to earlier standards */
  7. #include <sys/time.h>
  8. #include <sys/types.h>
  9. #include <unistd.h>
  10. int select(int nfds, fd_set *readfds, fd_set *writefds,
  11. fd_set *exceptfds, struct timeval *timeout);
  12. void FD_CLR(int fd, fd_set *set);
  13. int FD_ISSET(int fd, fd_set *set);
  14. void FD_SET(int fd, fd_set *set);
  15. void FD_ZERO(fd_set *set);
  16. #include <sys/select.h>
  17. int pselect(int nfds, fd_set *readfds, fd_set *writefds,
  18. fd_set *exceptfds, const struct timespec *timeout,
  19. const sigset_t *sigmask);
  • nfds:ndfs 应该设置为三个集合中编号最高的文件描述符加1。表示集合中的每个文件描述符都会被检查,直到达到这个限制
  • readfds:监听需要读的文件描述符集合
  • writefds:监听需要写的文件描述符集合
  • exceptfds:监听异常事件的文件描述符集合
  • timeout:监听超时时间

select 代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8. void start() {
  9. // 创建服务端结构体
  10. struct sockaddr_in my_addr;
  11. my_addr.sin_family = AF_INET;
  12. my_addr.sin_port = htons(8080);
  13. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  14. // 创建客户端结构体
  15. struct sockaddr_in client_addr;
  16. char client_ip[INET_ADDRSTRLEN] = "";
  17. int clientfd = 0;
  18. // 创建 socket
  19. int listenfd = socket(AF_INET, SOCK_STREAM, 0);
  20. bind(listenfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
  21. listen(listenfd, 128);
  22. printf("listen client @port=%d...\n", 8080);
  23. int lastfd = listenfd;
  24. int i;
  25. // 创建读事件集合
  26. fd_set read_set, total_set;
  27. FD_ZERO(&read_set);
  28. FD_SET(listenfd, &total_set); // 将 listenfd 加入读事件集合
  29. while(1) {
  30. read_set = total_set;
  31. // 返回三种事件的集合
  32. // select 会阻塞监听
  33. int z = select(lastfd + 1, &read_set, NULL, NULL, NULL);
  34. if (z > 0) {
  35. // 判断 listenfd 是否在 read_set 集合中
  36. if (FD_ISSET(listenfd, &read_set)) {
  37. socklen_t client_len = sizeof(client_addr);
  38. clientfd = accept(listenfd, (struct sockaddr*)&client_addr, &client_len);
  39. // 打印连接信息
  40. inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
  41. printf("----------------------------------\n");
  42. printf("client ip=%s, port=%d\n", client_ip, ntohs(client_addr.sin_port));
  43. // 将新的 clientfd 加入集合
  44. FD_SET(clientfd, &total_set);
  45. lastfd = clientfd;
  46. // 只有一个连接事件的时候,不在往下执行
  47. if (0 == --z) {
  48. continue;
  49. }
  50. }
  51. for (i = listenfd + 1; i <= lastfd; i++) {
  52. // 判断 i 是否在 read_set 集合中,也就是 i 发送了信息,发生了读事件
  53. if (FD_ISSET(i, &read_set)) {
  54. char recv_buf[1024] = "";
  55. int rs = read(i, recv_buf, sizeof(recv_buf));
  56. // 0 表示对方关闭了连接
  57. if (rs == 0) {
  58. // 关闭 i
  59. close(i);
  60. // 将 i 从 total_set 中移除
  61. FD_CLR(i, &total_set);
  62. } else {
  63. printf("%s\n", recv_buf);
  64. // write(0, recv_buf, rs);
  65. }
  66. }
  67. }
  68. }
  69. }
  70. }
  71. int main() {
  72. start();
  73. return 0;
  74. }

编译

  1. gcc select.c -o select.out

执行

  1. ./select.out
  2. listen client @port=8080...

测试

  1. nc 127.0.0.1 8080
  2. test

服务端显示:

  1. listen client @port=8080...
  2. ----------------------------------
  3. client ip=127.0.0.1, port=6742
  4. test