六十、linux 编程—— I/O 多路复用 select

60.1 介绍

  

  

  

  

  

60.2 例子

  

  echo_tcp_server_select.c

  1 #include <netdb.h>
  2 #include <netinet/in.h>
  3 #include <sys/socket.h>
  4 #include <sys/wait.h>
  5 #include <unistd.h>
  6 #include <string.h>
  7 #include <stdio.h>
  8 #include <stdlib.h>
  9 #include <memory.h>
 10 #include <signal.h>
 11 #include <fcntl.h>
 12 #include <time.h>
 13 #include <arpa/inet.h>
 14 #include <errno.h>
 15 #include <pthread.h>
 16 #include "vector_fd.h"
 17 
 18 vector_fd *vfd;
 19 int sockfd;
 20 
 21 void sig_handler(int signo)
 22 {
 23     if(signo == SIGINT){
 24         printf("server close\n");
 25         /** 步骤6: 关闭 socket */
 26         close(sockfd);
 27         /** 销毁动态数组 */
 28         destroy_vector_fd(vfd);
 29         exit(1);
 30     }
 31 }
 32 
 33 /**
 34  * fd 对应于某个连接的客户端,和某一个连接的客户端进行双向通信(非阻塞方式)
 35  */
 36 void do_service(int fd)
 37 {
 38     char buff[512];
 39     memset(buff, 0, sizeof(buff));
 40 
 41     /**
 42      *  因为采用非阻塞方式,若读不到数据直接返回,
 43      *  直接服务于下一个客户端,
 44      *  因此不需要判断 size < 0 的情况 */
 45     ssize_t size = read(fd, buff, sizeof(buff));
 46 
 47     if(size == 0){
 48         /** 客户端已经关闭连接 */
 49         printf("client closed\n");
 50         /** 从动态数组中删除对应的 fd */
 51         remove_fd(vfd, fd);
 52         /** 关闭对应客户端的 socket */
 53         close(fd);
 54     }
 55     else if(size > 0){
 56         printf("%s\n", buff);
 57         if(write(fd, buff, size) < 0){
 58             if(errno == EPIPE){
 59                 /** 客户端关闭连接 */
 60                 perror("write error");
 61                 remove_fd(vfd, fd);
 62                 close(fd);
 63             }
 64             perror("protocal error");
 65         }
 66     }
 67 }
 68 
 69 void out_addr(struct sockaddr_in *clientaddr)
 70 {
 71     char ip[16];
 72     memset(ip, 0, sizeof(ip));
 73     int port = ntohs(clientaddr->sin_port);
 74     inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
 75     printf("%s(%d) connected!\n", ip, port);
 76 }
 77 
 78 /** 遍历出动态数组中所有的描述符并加入到描述符集 set
 79  * 中,同时此函数返回动态数组中最大的那个描述符 */
 80 int add_set(fd_set *set)
 81 {
 82     FD_ZERO(set);   ///< 清空描述符集
 83     int max_fd = vfd->fd[0];
 84     int i = 0;
 85     for(; i < vfd->counter; i++){
 86         int fd = get_fd(vfd, i);
 87         if(fd > max_fd) max_fd = fd;
 88         FD_SET(fd, set);    ///< 将 fd 加入到描述符集中
 89     }
 90 
 91     return max_fd;
 92 }
 93 
 94 void *th_fn(void *arg)
 95 {
 96     /** 设置超时时间 2s */
 97     struct timeval t;
 98     t.tv_sec = 2;
 99     t.tv_usec = 0;
100 
101     int n = 0;
102     int maxfd;
103     fd_set set; ///< 描述符集
104     maxfd = add_set(&set);
105 
106     /**
107      * 调用 select 函数会阻塞,委托内核去检查传入的描述符是否准备好,
108      * 若有则返回准备好的描述符;超时则返回 0
109      * 第一个参数为描述符集中的描述符的范围(最大描述符 + 1)
110      */
111     while((n = select(maxfd + 1, &set, NULL, NULL, &t)) >= 0){
112         /** 检测哪些描述符准备好, 并和这些准备好的描述符对应的客户端进行数据的双向通信 */
113         if(n > 0){
114             int i = 0;
115             for(; i < vfd->counter; i++){
116                 int fd = get_fd(vfd, i);
117                 if(FD_ISSET(fd, &set)){
118                     do_service(fd);
119                 }
120             }
121         }
122         /** 重新设置时间和清空描述符集 */
123         t.tv_sec = 2;
124         t.tv_usec = 0;
125         /** 重新遍历动态数组中最新的描述符放置到描述符集中 */
126         maxfd = add_set(&set);
127     }
128 
129     return (void *)0;
130 }
131 
132 int main(int argc, char *argv[])
133 {
134     if(argc < 2){
135         printf("usage: %s #port\n", argv[0]);
136         exit(1);
137     }
138 
139     if(signal(SIGINT, sig_handler) == SIG_ERR){
140         perror("signal sigint error");
141         exit(1);
142     }
143 
144 
145     /** 步骤1: 创建 socket(套接字)
146      *  注: socket 创建在内核中,是一个结构体.
147      *  AF_INET: IPV4
148      *  SOCK_STREAM: tcp 协议
149      *  AF_INET6: IPV6
150      */
151     sockfd = socket(AF_INET, SOCK_STREAM, 0);
152     if(sockfd < 0){
153         perror("socket error");
154         exit(1);
155     }
156 
157     /**
158      * 步骤2: 调用 bind 函数将 socket 和地址(包括 ip、port)进行绑定
159      */
160     struct sockaddr_in  serveraddr;
161     memset(&serveraddr, 0, sizeof(struct sockaddr_in));
162     /** 往地址中填入 ip、port、internet 地址族类型 */
163     serveraddr.sin_family = AF_INET;    ///< IPV4
164     serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口
165     serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址
166     if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
167         perror("bind error");
168         exit(1);
169     }
170 
171     /**
172      *  步骤3: 调用 listen 函数启动监听(指定 port 监听)
173      *         通知系统去接受来自客户端的连接请求
174      *         (将接受到的客户端连接请求放置到对应的队列中)
175      *  第二个参数: 指定队列的长度
176      */
177     if(listen(sockfd, 10) < 0){
178         perror("listen error");
179         exit(1);
180     }
181 
182     /** 创建放置套接字描述符 fd 的动态数组 */
183     vfd = create_vector_fd();
184 
185     /** 设置线程的分离属性 */
186     pthread_t th;
187     pthread_attr_t attr;
188     pthread_attr_init(&attr);
189     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
190     int err;
191     if((err = pthread_create(&th, &attr, th_fn, (void *)0)) != 0){
192         perror("pthread create error");
193         exit(1);
194     }
195     pthread_attr_destroy(&attr);
196 
197     /**
198      * 1)主控线程获得客户端的链接,将新的 socket 描述符放置到动态数组中
199      * 2) (a)启动的子线程调用 select 函数委托内核去检查传入到 select
200      *       中的描述符是否准备好.
201      *    (b)利用 FD_ISSET 来找出准备好的那些描述符,
202      *       并和对应的客户端进行双向通信(非阻塞)
203      */
204     struct sockaddr_in clientaddr;
205     socklen_t len = sizeof(clientaddr);
206 
207     while(1){
208         /**
209          *  步骤4: 调用 accept 函数从队列中获得一个客户端的请求连接, 并返回新的
210          *         socket 描述符
211          *  注意:  若没有客户端连接,调用此函数后会阻塞, 直到获得一个客户端的连接
212          */
213         /** 主控线程负责调用 accept 去获得客户端的连接 */
214         int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
215         if(fd < 0){
216             perror("accept error");
217             continue;
218         }
219 
220         out_addr(&clientaddr);
221 
222         /** 将返回的新的 socket 描述符加入到动态数组中 */
223         add_fd(vfd, fd);
224     }
225 
226     return 0;
227 }

  编译运行测试:

  

 

posted @ 2019-03-13 21:51  游戏进行中  阅读(295)  评论(0编辑  收藏  举报