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 "); 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 "); 50 /** 从动态数组中删除对应的 fd */ 51 remove_fd(vfd, fd); 52 /** 关闭对应客户端的 socket */ 53 close(fd); 54 } 55 else if(size > 0){ 56 printf("%s ", 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! ", 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 ", 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 }
编译运行测试: