linux socket学习(二)

来源:互联网 发布:幽灵行动未来战士java 编辑:程序博客网 时间:2024/05/17 06:19

四.使用select

select这个系统调用,是一种多路复用IO方案,可以同时对多个文件描述符进行监控,从而知道哪些文件描述符可读,可写或者出错,不过select方法是阻塞的,可以设定超时时间。 select使用的步骤如下:

  • 1.创建一个fd_set变量(fd_set实为包含了一个整数数组的结构体),用来存放所有的待检查的文件描述符
  • 2.清空fd_set变量,并将需要检查的所有文件描述符加入fd_set
  • 3.调用select。若返回-1,则说明出错;返回0,则说明超时,返回正数,则为发生状态变化的文件描述符的个数
  • 4.若select返回大于0,则依次查看哪些文件描述符变的可读,并对它们进行处理
  • 5.返回步骤2,开始新一轮的检测

若上面的聊天程序使用select进行改写,则是下面这样的


服务器端

[cpp] view plain copy
 print?
  1. #include <stdio.h>  
  2.  #include <stdlib.h>  
  3.  #include <netinet/in.h>  
  4.  #include <sys/socket.h>  
  5.  #include <arpa/inet.h>  
  6.  #include <string.h>  
  7.  #include <unistd.h>  
  8.  #define BACKLOG 5 //完成三次握手但没有accept的队列的长度  
  9.  #define CONCURRENT_MAX 8 //应用层同时可以处理的连接  
  10.  #define SERVER_PORT 11332  
  11.  #define BUFFER_SIZE 1024  
  12.  #define QUIT_CMD ".quit"  
  13.  int client_fds[CONCURRENT_MAX];  
  14.  int main (int argc, const char * argv[])  
  15.  {  
  16.      char input_msg[BUFFER_SIZE];  
  17.      char recv_msg[BUFFER_SIZE];     
  18.      //本地地址  
  19.      struct sockaddr_in server_addr;  
  20.      server_addr.sin_len = sizeof(struct sockaddr_in);  
  21.      server_addr.sin_family = AF_INET;  
  22.      server_addr.sin_port = htons(SERVER_PORT);  
  23.      server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  24.      bzero(&(server_addr.sin_zero),8);  
  25.      //创建socket  
  26.      int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);  
  27.      if (server_sock_fd == -1) {  
  28.          perror("socket error");  
  29.          return 1;  
  30.      }  
  31.      //绑定socket  
  32.      int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));  
  33.      if (bind_result == -1) {  
  34.          perror("bind error");  
  35.          return 1;  
  36.      }  
  37.      //listen  
  38.      if (listen(server_sock_fd, BACKLOG) == -1) {  
  39.          perror("listen error");  
  40.          return 1;  
  41.      }  
  42.      //fd_set  
  43.      fd_set server_fd_set;  
  44.      int max_fd = -1;  
  45.      struct timeval tv;  
  46.      tv.tv_sec = 20;  
  47.      tv.tv_usec = 0;  
  48.      while (1) {  
  49.          FD_ZERO(&server_fd_set);  
  50.          //标准输入  
  51.          FD_SET(STDIN_FILENO, &server_fd_set);  
  52.          if (max_fd < STDIN_FILENO) {  
  53.              max_fd = STDIN_FILENO;  
  54.          }  
  55.          //服务器端socket  
  56.          FD_SET(server_sock_fd, &server_fd_set);  
  57.          if (max_fd < server_sock_fd) {  
  58.              max_fd = server_sock_fd;  
  59.          }  
  60.          //客户端连接  
  61.          for (int i = 0; i < CONCURRENT_MAX; i++) {  
  62.              if (client_fds[i]!=0) {  
  63.                  FD_SET(client_fds[i], &server_fd_set);  
  64.   
  65.                  if (max_fd < client_fds[i]) {  
  66.                      max_fd = client_fds[i];  
  67.                  }  
  68.              }  
  69.          }  
  70.          int ret = select(max_fd+1, &server_fd_set, NULL, NULL, &tv);  
  71.          if (ret < 0) {  
  72.              perror("select 出错\n");  
  73.              continue;  
  74.          }else if(ret == 0){  
  75.              printf("select 超时\n");  
  76.              continue;  
  77.          }else{  
  78.              //ret为未状态发生变化的文件描述符的个数  
  79.              if (FD_ISSET(STDIN_FILENO, &server_fd_set)) {  
  80.                  //标准输入  
  81.                  bzero(input_msg, BUFFER_SIZE);  
  82.                  fgets(input_msg, BUFFER_SIZE, stdin);  
  83.                  //输入 ".quit" 则退出服务器  
  84.                  if (strcmp(input_msg, QUIT_CMD) == 0) {  
  85.                      exit(0);  
  86.                  }  
  87.                  for (int i=0; i<CONCURRENT_MAX; i++) {  
  88.                      if (client_fds[i]!=0) {  
  89.                          send(client_fds[i], input_msg, BUFFER_SIZE, 0);  
  90.                      }  
  91.                  }  
  92.              }  
  93.              if (FD_ISSET(server_sock_fd, &server_fd_set)) {  
  94.                  //有新的连接请求  
  95.                  struct sockaddr_in client_address;  
  96.                  socklen_t address_len;  
  97.                  int client_socket_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);  
  98.                  if (client_socket_fd > 0) {  
  99.                      int index = -1;  
  100.                      for (int i = 0; i < CONCURRENT_MAX; i++) {  
  101.                          if (client_fds[i] == 0) {  
  102.                              index = i;  
  103.                              client_fds[i] = client_socket_fd;  
  104.                              break;  
  105.                          }  
  106.                      }  
  107.                      if (index >= 0) {  
  108.                          printf("新客户端(%d)加入成功 %s:%d \n",index,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));  
  109.                      }else{  
  110.                          bzero(input_msg, BUFFER_SIZE);  
  111.                          strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");  
  112.                          send(client_socket_fd, input_msg, BUFFER_SIZE, 0);  
  113.                          printf("客户端连接数达到最大值,新客户端加入失败 %s:%d \n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));  
  114.                      }  
  115.                  }  
  116.              }  
  117.              for (int i = 0; i <CONCURRENT_MAX; i++) {  
  118.                  if (client_fds[i]!=0) {  
  119.                      if (FD_ISSET(client_fds[i], &server_fd_set)) {  
  120.                          //处理某个客户端过来的消息  
  121.                          bzero(recv_msg, BUFFER_SIZE);  
  122.                          long byte_num = recv(client_fds[i],recv_msg,BUFFER_SIZE,0);  
  123.                          if (byte_num > 0) {  
  124.                              if (byte_num > BUFFER_SIZE) {  
  125.                                  byte_num = BUFFER_SIZE;  
  126.                              }  
  127.                              recv_msg[byte_num] = '\0';  
  128.                              printf("客户端(%d):%s\n",i,recv_msg);  
  129.                          }else if(byte_num < 0){  
  130.                              printf("从客户端(%d)接受消息出错.\n",i);  
  131.                          }else{  
  132.                              FD_CLR(client_fds[i], &server_fd_set);  
  133.                              client_fds[i] = 0;  
  134.                              printf("客户端(%d)退出了\n",i);  
  135.                          }  
  136.                      }  
  137.                  }  
  138.              }  
  139.          }  
  140.      }  
  141.      return 0;  
  142.  }  


客户端

[cpp] view plain copy
 print?
  1. #include <stdio.h>  
  2. #include <netinet/in.h>  
  3. #include <sys/socket.h>  
  4. #include <arpa/inet.h>  
  5. #include <string.h>  
  6. #include <unistd.h>  
  7. #include <stdlib.h>  
  8.   
  9. #define BUFFER_SIZE 1024  
  10.   
  11. int main (int argc, const char * argv[])  
  12. {  
  13.     struct sockaddr_in server_addr;  
  14.     server_addr.sin_len = sizeof(struct sockaddr_in);  
  15.     server_addr.sin_family = AF_INET;  
  16.     server_addr.sin_port = htons(11332);  
  17.     server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  18.     bzero(&(server_addr.sin_zero),8);  
  19.   
  20.     int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);  
  21.     if (server_sock_fd == -1) {  
  22.         perror("socket error");  
  23.         return 1;  
  24.     }  
  25.     char recv_msg[BUFFER_SIZE];  
  26.     char input_msg[BUFFER_SIZE];  
  27.   
  28.     if (connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))==0) {  
  29.         fd_set client_fd_set;  
  30.         struct timeval tv;  
  31.         tv.tv_sec = 20;  
  32.         tv.tv_usec = 0;  
  33.   
  34.   
  35.         while (1) {  
  36.             FD_ZERO(&client_fd_set);  
  37.             FD_SET(STDIN_FILENO, &client_fd_set);  
  38.             FD_SET(server_sock_fd, &client_fd_set);  
  39.   
  40.             int ret = select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);  
  41.             if (ret < 0 ) {  
  42.                 printf("select 出错!\n");  
  43.                 continue;  
  44.             }else if(ret ==0){  
  45.                 printf("select 超时!\n");  
  46.                 continue;  
  47.             }else{  
  48.                 if (FD_ISSET(STDIN_FILENO, &client_fd_set)) {  
  49.                     bzero(input_msg, BUFFER_SIZE);  
  50.                     fgets(input_msg, BUFFER_SIZE, stdin);  
  51.                     if (send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1) {  
  52.                         perror("发送消息出错!\n");  
  53.                     }  
  54.                 }  
  55.   
  56.                 if (FD_ISSET(server_sock_fd, &client_fd_set)) {  
  57.                     bzero(recv_msg, BUFFER_SIZE);  
  58.                     long byte_num = recv(server_sock_fd,recv_msg,BUFFER_SIZE,0);  
  59.                     if (byte_num > 0) {  
  60.                         if (byte_num > BUFFER_SIZE) {  
  61.                             byte_num = BUFFER_SIZE;  
  62.                         }  
  63.                         recv_msg[byte_num] = '\0';  
  64.                         printf("服务器:%s\n",recv_msg);  
  65.                     }else if(byte_num < 0){  
  66.                         printf("接受消息出错!\n");  
  67.                     }else{  
  68.                         printf("服务器端退出!\n");  
  69.                         exit(0);  
  70.                     }  
  71.   
  72.                 }  
  73.             }  
  74.         }  
  75.   
  76.     }  
  77.   
  78.     return 0;  
  79. }  


当然select也有其局限性。当fd_set中的文件描述符较少,或者大都数文件描述符都比较活跃的时候,select的效率还是不错的。Mac系统中已经定义了fd_set 最大可以容纳的文件描述符的个数为1024

[cpp] view plain copy
 print?
  1. //sys/_structs.h  
  2. #define __DARWIN_FD_SETSIZE 1024  
  3. /////////////////////////////////////////////  
  4. //Kernel.framework sys/select.h  
  5. #define FD_SETSIZE  __DARWIN_FD_SETSIZE  

每一次select 调用的时候,都涉及到user space和kernel space的内存拷贝,且会对fd_set中的所有文件描述符进行遍历,如果所有的文件描述符均不满足,且没有超时,则当前进程便开始睡眠,直到超时或者有文件描述符状态发生变化。当文件描述符数量较大的时候,将耗费大量的CPU时间。所以后来有新的方案出现了,如windows2000引入的IOCP,Linux Kernel 2.6中成熟的epoll,FreeBSD4.x引入的kqueue。



五.使用kqueue

Mac是基于BSD的内核,所使用的是kqueue(kernel event notification mechanism,详细内容可以Mac中 man 2 kqueue),kqueue比select先进的地方就在于使用事件触发的机制,且其调用无需每次对所有的文件描述符进行遍历,返回的时候只返回需要处理的事件,而不像select中需要自己去一个个通过FD_ISSET检查。
kqueue默认的触发方式是level 水平触发,可以通过设置event的flag为EV_CLEAR 使得这个事件变为边沿触发,可能epoll的触发方式无法细化到单个event,需要查证。

kqueue中涉及两个系统调用,kqueue()和kevent()

  • kqueue() 创建kernel级别的事件队列,并返回队列的文件描述符
  • kevent() 往事件队列中加入订阅事件,或者返回相关的事件数组

kqueue使用的流程一般如下:

  • 创建kqueue
  • 创建struct kevent变量(注意这里的kevent是结构体类型名),可以通过EV_SET这个宏提供的快捷方式进行创建
  • 通过kevent系统调用将创建好的kevent结构体变量加入到kqueue队列中,完成对指定文件描述符的事件的订阅
  • 通过kevent系统调用获取满足条件的事件队列,并对每一个事件进行处理

 

[cpp] view plain copy
 print?
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <netinet/in.h>  
  4. #include <sys/socket.h>  
  5. #include <sys/event.h>  
  6. #include <sys/types.h>  
  7. #include <sys/time.h>  
  8. #include <arpa/inet.h>  
  9. #include <string.h>  
  10. #include <unistd.h>  
  11. #define BACKLOG 5 //完成三次握手但没有accept的队列的长度  
  12. #define CONCURRENT_MAX 8 //应用层同时可以处理的连接  
  13. #define SERVER_PORT 11332  
  14. #define BUFFER_SIZE 1024  
  15. #define QUIT_CMD ".quit"  
  16. int client_fds[CONCURRENT_MAX];  
  17. struct kevent events[10];//CONCURRENT_MAX + 2  
  18. int main (int argc, const char * argv[])  
  19. {  
  20.     char input_msg[BUFFER_SIZE];  
  21.     char recv_msg[BUFFER_SIZE];  
  22.     //本地地址  
  23.     struct sockaddr_in server_addr;  
  24.     server_addr.sin_len = sizeof(struct sockaddr_in);  
  25.     server_addr.sin_family = AF_INET;  
  26.     server_addr.sin_port = htons(SERVER_PORT);  
  27.     server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  28.     bzero(&(server_addr.sin_zero),8);  
  29.     //创建socket  
  30.     int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);  
  31.     if (server_sock_fd == -1) {  
  32.         perror("socket error");  
  33.         return 1;  
  34.     }  
  35.     //绑定socket  
  36.     int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));  
  37.     if (bind_result == -1) {  
  38.         perror("bind error");  
  39.         return 1;  
  40.     }  
  41.     //listen  
  42.     if (listen(server_sock_fd, BACKLOG) == -1) {  
  43.         perror("listen error");  
  44.         return 1;  
  45.     }  
  46.     struct timespec timeout = {10,0};  
  47.     //kqueue  
  48.     int kq = kqueue();  
  49.     if (kq == -1) {  
  50.         perror("创建kqueue出错!\n");  
  51.         exit(1);  
  52.     }  
  53.     struct kevent event_change;  
  54.     EV_SET(&event_change, STDIN_FILENO, EVFILT_READ, EV_ADD, 0, 0, NULL);  
  55.     kevent(kq, &event_change, 1, NULL, 0, NULL);  
  56.     EV_SET(&event_change, server_sock_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);  
  57.     kevent(kq, &event_change, 1, NULL, 0, NULL);  
  58.     while (1) {  
  59.         int ret = kevent(kq, NULL, 0, events, 10, &timeout);  
  60.         if (ret < 0) {  
  61.             printf("kevent 出错!\n");  
  62.             continue;  
  63.         }else if(ret == 0){  
  64.             printf("kenvent 超时!\n");  
  65.             continue;  
  66.         }else{  
  67.             //ret > 0 返回事件放在events中  
  68.             for (int i = 0; i < ret; i++) {  
  69.                 struct kevent current_event = events[i];  
  70.                 //kevent中的ident就是文件描述符  
  71.                 if (current_event.ident == STDIN_FILENO) {  
  72.                     //标准输入  
  73.                     bzero(input_msg, BUFFER_SIZE);  
  74.                     fgets(input_msg, BUFFER_SIZE, stdin);  
  75.                     //输入 ".quit" 则退出服务器  
  76.                     if (strcmp(input_msg, QUIT_CMD) == 0) {  
  77.                         exit(0);  
  78.                     }  
  79.                     for (int i=0; i<CONCURRENT_MAX; i++) {  
  80.                         if (client_fds[i]!=0) {  
  81.                             send(client_fds[i], input_msg, BUFFER_SIZE, 0);  
  82.                         }  
  83.                     }  
  84.                 }else if(current_event.ident == server_sock_fd){  
  85.                     //有新的连接请求  
  86.                     struct sockaddr_in client_address;  
  87.                     socklen_t address_len;  
  88.                     int client_socket_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);  
  89.                     if (client_socket_fd > 0) {  
  90.                         int index = -1;  
  91.                         for (int i = 0; i < CONCURRENT_MAX; i++) {  
  92.                             if (client_fds[i] == 0) {  
  93.                                 index = i;  
  94.                                 client_fds[i] = client_socket_fd;  
  95.                                 break;  
  96.                             }  
  97.                         }  
  98.                         if (index >= 0) {  
  99.                             EV_SET(&event_change, client_socket_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);  
  100.                             kevent(kq, &event_change, 1, NULL, 0, NULL);  
  101.                             printf("新客户端(fd = %d)加入成功 %s:%d \n",client_socket_fd,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));  
  102.                         }else{  
  103.                             bzero(input_msg, BUFFER_SIZE);  
  104.                             strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");  
  105.                             send(client_socket_fd, input_msg, BUFFER_SIZE, 0);  
  106.                             printf("客户端连接数达到最大值,新客户端加入失败 %s:%d \n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));  
  107.                         }  
  108.                     }  
  109.                 }else{  
  110.                     //处理某个客户端过来的消息  
  111.                     bzero(recv_msg, BUFFER_SIZE);  
  112.                     long byte_num = recv((int)current_event.ident,recv_msg,BUFFER_SIZE,0);  
  113.                     if (byte_num > 0) {  
  114.                         if (byte_num > BUFFER_SIZE) {  
  115.                             byte_num = BUFFER_SIZE;  
  116.                         }  
  117.                         recv_msg[byte_num] = '\0';  
  118.                         printf("客户端(fd = %d):%s\n",(int)current_event.ident,recv_msg);  
  119.                     }else if(byte_num < 0){  
  120.                         printf("从客户端(fd = %d)接受消息出错.\n",(int)current_event.ident);  
  121.                     }else{  
  122.                         EV_SET(&event_change, current_event.ident, EVFILT_READ, EV_DELETE, 0, 0, NULL);  
  123.                         kevent(kq, &event_change, 1, NULL, 0, NULL);  
  124.                         close((int)current_event.ident);  
  125.                         for (int i = 0; i < CONCURRENT_MAX; i++) {  
  126.                             if (client_fds[i] == (int)current_event.ident) {  
  127.                                 client_fds[i] = 0;  
  128.                                 break;  
  129.                             }  
  130.                         }  
  131.                         printf("客户端(fd = %d)退出了\n",(int)current_event.ident);  
  132.                     }  
  133.                 }  
  134.             }  
  135.         }  
  136.     }  
  137.     return 0;  
  138. }  

其实kqueue的应用场景非常的广阔,可以监控文件系统中文件的变化(对文件变化的事件可以粒度非常的细,具体可以查看kqueue的手册),监控系统进程的生命周期。GCD的事件处理便是建立在kqueue之上的。