IO复用的学习和练习

echo_client_select.c

  1. /**
  2. * 说明:echo 程序的客户端
  3. * 命令行参数:[127.0.0.1]。如果没有参数,默认使用本地回环地址
  4. * 在输入EOF之后会半关闭:发送FIN
  5. * 使用select 来等待stdin的输入和sockfd的相应,可以处理批量输入
  6. */
  7. #include "../unp.h"
  8. void str_cli(FILE *fp, int sockfd);
  9. int main(int argc, char** argv) {
  10. int sock_fd; // 用于和服务器建立连接的套接字
  11. struct sockaddr_in servaddr; // 服务器的地址结构
  12. char ipAddrStr[16] = "127.0.0.1";
  13. char* ipAddrStrPtr;
  14. /* 命令行读取服务器IP地址 */
  15. if (argc == 1)
  16. ipAddrStrPtr = ipAddrStr;
  17. else
  18. ipAddrStrPtr = argv[1];
  19. /* 准备连接套接字 */
  20. sock_fd = Socket(AF_INET, SOCK_STREAM, 0);
  21. bzero(&servaddr, sizeof(servaddr));
  22. servaddr.sin_family = AF_INET;
  23. servaddr.sin_port = htons(SERV_PORT);
  24. Inet_pton(AF_INET, ipAddrStrPtr, &servaddr.sin_addr);
  25. Connect(sock_fd, (struct sockaddr*) &servaddr, sizeof(servaddr));
  26. str_cli(stdin, sock_fd);
  27. exit(0);
  28. }
  29. /**
  30. * echo客户端的功能实现
  31. * 参数:fp表示一个文件指针,从该文件指针中读取数据发送到服务器
  32. * 参数:sockfd表示与服务器连接的套接字描述符
  33. */
  34. void str_cli(FILE *fp, int sockfd)
  35. {
  36. int maxfdp1; // 最大文件描述符+1
  37. int stdineof; // stdin是否已经到达了EOF
  38. fd_set rset; // 需要select的文件描述符的集合
  39. char buf[MAXLINE]; // read 和 write 使用的缓冲
  40. int n; // 缓冲 buf 的有效长度
  41. stdineof = 0;
  42. FD_ZERO(&rset);
  43. for (;;) {
  44. /* 进行select */
  45. if (stdineof == 0)
  46. FD_SET(fileno(fp), &rset);
  47. FD_SET(sockfd, &rset);
  48. maxfdp1 = max(fileno(fp), sockfd) + 1;
  49. Select(maxfdp1, &rset, NULL, NULL, NULL); // 时间设置为NULL,表示阻塞
  50. /* sockfd已经可以读了 */
  51. if (FD_ISSET(sockfd, &rset)) {
  52. if((n = Read(sockfd, buf, MAXLINE)) == 0) {
  53. if (stdineof == 1)
  54. return;
  55. else
  56. err_quit("str_cli: server terminated permaturely");
  57. }
  58. Write(fileno(stdout), buf, n);
  59. }
  60. /* fp的数据已经准备好了 */
  61. if (FD_ISSET(fileno(fp), &rset)) {
  62. if ((n = Read(fileno(fp), buf, MAXLINE)) == 0) {
  63. stdineof = 1;
  64. Shutdown(sockfd, SHUT_WR); // 半关闭,不再写了,向服务器发送FIN
  65. FD_CLR(fileno(fp), &rset);
  66. continue;
  67. }
  68. Writen(sockfd, buf, n);
  69. }
  70. }
  71. }

echo_server_poll.c

  1. #include "../unp.h"
  2. # define OPEN_MAX 256 // <limits.h>中并没有OPEN_MAX的定义
  3. /**
  4. * echo服务器
  5. * 单线程使用 poll 来处理多个连接
  6. * 使用 client 数组表示关心的xx
  7. */
  8. int main(int argc, char**argv) {
  9. struct pollfd client[OPEN_MAX];
  10. int sockfd; // client 数组中的元素
  11. int i; // client 数组的临时下标
  12. int clientMaxValid_i; // client 数组中有效连接的最大下标
  13. int nready; // select 返回后,准备好的描述符的数量
  14. char buffer[MAXLINE]; // 存放临时数据的数组
  15. ssize_t bufferSize; // 临时数据的大小
  16. int connfd; // listen 到的新连接
  17. int listenfd;
  18. socklen_t clilen;
  19. struct sockaddr_in cliaddr, servaddr;
  20. /* 服务器Listen */
  21. listenfd = Socket(AF_INET, SOCK_STREAM, 0);
  22. bzero(&servaddr, sizeof(servaddr));
  23. servaddr.sin_family = AF_INET;
  24. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  25. servaddr.sin_port = htons(SERV_PORT);
  26. Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
  27. Listen(listenfd, LISTENQ);
  28. /* 初始化 client 集合 */
  29. client[0].fd = listenfd;
  30. client[0].events = POLLRDNORM; // 关心 listenfd 是否可读
  31. for (i = 1; i < OPEN_MAX; i++)
  32. client[i].fd = -1; // -1 表示poll不关心的描述符/无效的描述符
  33. clientMaxValid_i = 0;
  34. for (;;) {
  35. nready = Poll(client, clientMaxValid_i + 1, INFTIM); // INFTIM表示无限阻塞
  36. /* 每次只会监听最多一个连接请求,和若干个客户端数据发送 */
  37. /* 监听到了客户端的连接请求 */
  38. if (client[0].revents & POLLRDNORM) {
  39. clilen = sizeof(cliaddr);
  40. connfd = Accept(listenfd, (SA*)&cliaddr, &clilen);
  41. /* 将客户端的sockfd 加入到 client集合中 */
  42. for (i = 0; i < OPEN_MAX; i++) {
  43. if (client[i].fd < 0) {
  44. client[i].fd = connfd;
  45. break;
  46. }
  47. }
  48. if (i == OPEN_MAX)
  49. err_quit("too many clients");
  50. /* 让 poll 关心这个sockfd */
  51. client[i].events = POLLRDNORM;
  52. if (i > clientMaxValid_i)
  53. clientMaxValid_i = i;
  54. if (--nready <= 0)
  55. continue;
  56. }
  57. /* 客户端发来了数据 */
  58. for (i = 0; i <= clientMaxValid_i; i++) { // 遍历整个client集合,才能找到是哪一个客户端发来了数据
  59. sockfd = client[i].fd;
  60. if (sockfd < 0)
  61. continue;
  62. if (client[i].revents & (POLLRDNORM | POLLERR)) {
  63. bufferSize = Read(sockfd, buffer, MAXLINE);
  64. /* 客户端连接出现了错误 */
  65. if (bufferSize < 0) {
  66. if (errno == ECONNRESET) {
  67. Close(sockfd);
  68. client[i].fd = -1;
  69. }
  70. }
  71. /* 客户端连接 EOF */
  72. else if (bufferSize == 0) {
  73. Close(sockfd);
  74. client[i].fd = -1;
  75. }
  76. else
  77. Writen(sockfd, buffer, bufferSize);
  78. if (--nready <= 0)
  79. break;
  80. }
  81. }
  82. }
  83. }

echo_server_select.c

  1. #include "../unp.h"
  2. /**
  3. * echo服务器
  4. * 单线程使用select来处理多个连接
  5. * 使用client数组,存放已经建立连接的sockfd
  6. * 使用allset/rset,存放需要select - read的集合:包括一众客户端sockfd,以及服务器的listen sockfd
  7. */
  8. int main(int argc, char**argv) {
  9. int client[FD_SETSIZE]; // 已经建立连接的客户端的 sockfd 的集合;-1表示没有使用
  10. int sockfd; // client 数组中的元素
  11. int i; // client 数组的临时下标
  12. int clientMaxIndex; // client 数组中有效连接的最大下标
  13. fd_set rset; // 需要select 的一系列描述符。包括listen sockfd,以及客户端连接的 sockfd
  14. fd_set allset; // allset 是 rset 第一个副本。因为rset可能会被select函数改变
  15. int maxfd; // rset/allset 集合中最大的描述符编号
  16. int nready; // select 返回后,准备好的描述符的数量
  17. char buffer[MAXLINE]; // 存放临时数据的数组
  18. ssize_t bufferSize; // 临时数据的大小
  19. int connfd; // listen 到的新连接
  20. int listenfd;
  21. socklen_t clilen;
  22. struct sockaddr_in cliaddr, servaddr;
  23. /* 服务器Listen */
  24. listenfd = Socket(AF_INET, SOCK_STREAM, 0);
  25. bzero(&servaddr, sizeof(servaddr));
  26. servaddr.sin_family = AF_INET;
  27. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  28. servaddr.sin_port = htons(SERV_PORT);
  29. Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
  30. Listen(listenfd, LISTENQ);
  31. /* 初始化各个集合 */
  32. maxfd = listenfd;
  33. clientMaxIndex = -1;
  34. for (i = 0; i < FD_SETSIZE; i++)
  35. client[i] = -1;
  36. FD_ZERO(&allset);
  37. FD_SET(listenfd, &allset);
  38. for (;;) {
  39. rset = allset;
  40. nready = Select(maxfd + 1, &rset, NULL, NULL, NULL);
  41. /* 每次只会监听最多一个连接请求,和若干个客户端数据发送 */
  42. /* 监听到了客户端的连接请求 */
  43. if (FD_ISSET(listenfd, &rset)) {
  44. clilen = sizeof(cliaddr);
  45. connfd = Accept(listenfd, (SA*)&cliaddr, &clilen);
  46. /* 将客户端的sockfd 加入到 client集合中 */
  47. for (i = 0; i < FD_SETSIZE; i++) {
  48. if (client[i] < 0) {
  49. client[i] = connfd;
  50. break;
  51. }
  52. }
  53. if (i == FD_SETSIZE)
  54. err_quit("too many clients");
  55. /* 将客户端的 sockfd 加入 allset集合中。同时更新maxfd,maxi */
  56. FD_SET(connfd, &allset);
  57. if (connfd > maxfd)
  58. maxfd = connfd;
  59. if (i > clientMaxIndex)
  60. clientMaxIndex = i;
  61. if (--nready <= 0)
  62. continue;
  63. }
  64. /* 客户端发来了数据 */
  65. for (i = 0; i <= clientMaxIndex; i++) { // 遍历整个client集合,才能找到是哪一个客户端发来了数据
  66. sockfd = client[i];
  67. if (sockfd < 0)
  68. continue;
  69. if (FD_ISSET(sockfd, &rset)) {
  70. bufferSize = Read(sockfd, buffer, MAXLINE);
  71. if (bufferSize == 0) {
  72. Close(sockfd);
  73. FD_CLR(sockfd, &allset);
  74. client[i] = -1;
  75. }
  76. else
  77. Writen(sockfd, buffer, bufferSize);
  78. if (--nready <= 0)
  79. break;
  80. }
  81. }
  82. }
  83. }

简单的echo服务器和客户端

  • 服务器将收到的内容反射回客户端
  • 服务器使用子进程来处理多个连接

echo_client.c
echo_server.c