在上一节的程序中,服务端在进行到accept()环节会等待客户端的请求到来,若客户端一直不发生请求,则服务端会一直阻塞。
因此,引入并发服务器的概念。
一、并发服务器
同一时刻可以响应多个客户端的请求,多任务完成服务每个客户端的请求,每个客户端不需要排队等待,可以立即进行服务。
并发服务器设计技术一般有:多进程服务器、多线程服务器、I/O复用服务器(循环服务器)等。
(图片来源)
1、多线程服务器
父进程监听客户端的连接请求,创建子线程,进行线程分离
子线程与客户端进行数据交互
多线程服务器是对多进程服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建。
优点: 服务效率高,客户端无需等待。
缺点: 复杂、较多地占用了系统的资源,一旦客户端过多会严重浪费系统资源。
TCP多线程实例:
1 #include "net.h" 2 void cli_data_handle(void *arg); 3 4 int main() 5 { 6 int fd = -1; 7 struct sockaddr_in sin; //Internet环境下套接字的地址形式,对其进行操作以建立信息 8 9 /*1、创建套接字描述符fd */ 10 if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) 11 { 12 perror("socket"); 13 exit(1); 14 } 15 16 /*2、将套接字绑定到一个本地地址与端口上*/ 17 18 /*2.1 填充struct sockaddr_in结构体变量*/ 19 bzero(&sin, sizeof(sin)); //初始值置零 20 //sin_port和sin_addr都必须是NBD,且可视化的数字一般都是HBD,如端口号23 21 sin.sin_family = AF_INET; //协议,ipv4 22 sin.sin_port = htons(SERV_PORT); //将端口号转化为NBD 23 24 /*优化1:让服务器能绑定在任意IP上*/ 25 sin.sin_addr.s_addr = htonl(INADDR_ANY); 26 27 /*2.2 绑定 */ 28 if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) 29 { 30 perror("bind"); 31 exit(1); 32 } 33 34 /*3. 把套接字设为监听模式,准备接收客户请求*/ 35 if(listen(fd, BACKLOG) < 0) 36 { 37 perror("listen"); 38 exit(1); 39 } 40 printf("Severing start...OK! "); 41 42 /*4.等待客户请求到来,当请求到来后,接收请求,返回一个基于此次的新的套接字 */ 43 int newfd = -1; 44 45 /*优化3:用多进程/多线程处理已经建立好连接的客户端数据*/ 46 pthread_t tid; 47 48 struct sockaddr_in cin; 49 socklen_t addrlen = sizeof(cin); 50 while(1) 51 { 52 /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址与端口号 */ 53 //获取客户端信息 54 if((newfd = accept(fd, (struct sockaddr *)&cin, &addrlen))<0) 55 { 56 perror("accept"); 57 exit(1); 58 } 59 //读出客户端信息,并将HBD转为NBD 60 char ipv4_addr[16]; 61 if(!inet_ntop(AF_INET, (void *)&cin.sin_addr,ipv4_addr,sizeof(cin))) 62 { 63 perror("inet_ntop"); 64 exit(1); 65 } 66 //打印客户端的IP和端口号 67 printf("Client(%s,%d) is connected! ",ipv4_addr,ntohs(cin.sin_port)); 68 //打印完之后创建一个线程 69 pthread_create(&tid, NULL, (void *)cli_data_handle, (void *)&newfd); 70 } 71 72 close(fd); 73 return 0; 74 75 } 76 77 void cli_data_handle(void *arg) 78 { 79 int newfd = *(int *)arg; 80 81 printf("thread:newfd = %d ", newfd); 82 83 /*5. 读写数据*/ 84 int ret = -1; 85 char buf[BUFSIZ]; 86 87 while(1) 88 { 89 bzero(buf, BUFSIZ); 90 do{ 91 ret = read(newfd, buf, BUFSIZ-1); 92 }while(ret < 0 && EINTR == errno); 93 if(ret < 0) 94 { 95 perror("read"); 96 exit(1); 97 } 98 99 if(!ret) //对方已经关闭 100 { 101 break; 102 } 103 printf("Receive data: %s",buf); 104 105 if(!strncasecmp(buf,QUIT_STR, strlen(QUIT_STR))) 106 { 107 printf("Client is exiting! "); 108 break; 109 } 110 } 111 close(newfd); 112 113 }
1 /*运行方式: ./client serv_ip serv_port */ 2 #include "net.h" 3 4 void usage(char *s) 5 { 6 printf(" %s serv_ip serv_port ",s); 7 printf(" serv_ip:serv ip address"); 8 printf(" serv_port: sever port(>5000) "); 9 } 10 11 int main(int argc, char **argv) 12 { 13 int fd = -1; 14 15 int port = -1; 16 struct sockaddr_in sin; 17 18 if(argc != 3)//参数错误检测 19 { 20 usage(argv[0]); 21 exit(1); 22 } 23 /*1.创建sock fd */ 24 if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) 25 { 26 perror("socket"); 27 exit(1); 28 } 29 30 port = atoi(argv[2]); 31 if(port < 5000) 32 { 33 usage(argv[0]); 34 exit(1); 35 } 36 /*2.连接服务器 */ 37 /*2.1 填充struct sockaddr_in结构体变量*/ 38 bzero(&sin, sizeof(sin)); //初始值置零 39 sin.sin_family = AF_INET; // 40 sin.sin_port = htons(port); //转化为NBD 41 #if 0 42 sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); 43 #else 44 if(inet_pton(AF_INET, argv[1],(void *)&sin.sin_addr.s_addr) != 1) 45 { 46 perror("inet_pton"); 47 exit(1); 48 } 49 #endif 50 51 if(connect(fd,(struct sockaddr *)&sin, sizeof(sin)) < 0) 52 { 53 perror("connect"); 54 exit(1); 55 } 56 57 printf("Client starting ... "); 58 59 /*3.读写数据*/ 60 char buf[BUFSIZ]; 61 int ret = -1; 62 while(1) 63 { 64 bzero(buf,BUFSIZ); 65 if(fgets(buf, BUFSIZ-1, stdin) == NULL) 66 { 67 continue; 68 } 69 do{ 70 ret = write(fd, buf, strlen(buf)); 71 }while(ret < 0 && EINTR == errno); 72 if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR))) 73 { 74 printf("Clinet is exiting! "); 75 break; 76 } 77 78 } 79 80 /*4.关闭套接字 */ 81 close(fd); 82 83 return 0; 84 }
1 all: 2 gcc -o client client.c -lpthread 3 gcc -o pthread_tcp pthread_tcp.c -lpthread 4 5 clean: 6 rm *.elf
测试结果:
2、多进程服务器
在多进程处理模型中
父进程主要用来监听客户端的连接请求,当有客户端请求连接时,accept()返回用于通信的文件描述符fd,父进程fock创建子进程,并负责回收子进程。
子进程获得父进程数据空间、堆和栈的复制品,拿到文件描述符fd后,和客户端进行数据交互。
通信结束后,子进程exit()结束,主进程进程资源的回收。
TCP多进程实例:
1 #include "net.h" 2 void cli_data_handle(void *arg); 3 void sig_child_handle(int signo) 4 { 5 if(SIGCHLD == signo) 6 { 7 waitpid(-1, NULL, WNOHANG); 8 } 9 } 10 11 int main() 12 { 13 int fd = -1; 14 struct sockaddr_in sin; //Internet环境下套接字的地址形式,对其进行操作以建立信息 15 /*当客户端退出时,会通过信号机制回收子进程,防止变成僵尸进程*/ 16 signal(SIGCHLD, sig_child_handle); //回收子进程 17 18 /*1、创建套接字描述符fd */ 19 if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) 20 { 21 perror("socket"); 22 exit(1); 23 } 24 25 /*优化4:允许绑定地址快速重用*/ 26 int b_reuse = 1; 27 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); 28 29 /*2、将套接字绑定到一个本地地址与端口上*/ 30 31 /*2.1 填充struct sockaddr_in结构体变量*/ 32 bzero(&sin, sizeof(sin)); //初始值置零 33 //sin_port和sin_addr都必须是NBD,且可视化的数字一般都是HBD,如端口号23 34 sin.sin_family = AF_INET; //协议,ipv4 35 sin.sin_port = htons(SERV_PORT); //将端口号转化为NBD 36 37 /*优化1:让服务器能绑定在任意IP上*/ 38 sin.sin_addr.s_addr = htonl(INADDR_ANY); 39 40 /*2.2 绑定 */ 41 if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) 42 { 43 perror("bind"); 44 exit(1); 45 } 46 47 /*3. 把套接字设为监听模式,准备接收客户请求*/ 48 if(listen(fd, BACKLOG) < 0) 49 { 50 perror("listen"); 51 exit(1); 52 } 53 printf("Severing start...OK! "); 54 55 /*4.等待客户请求到来,当请求到来后,接收请求,返回一个基于此次的新的套接字 */ 56 int newfd = -1; 57 58 struct sockaddr_in cin; 59 socklen_t addrlen = sizeof(cin); 60 while(1) 61 { 62 pid_t pid = -1; 63 //获取客户端信息 64 if((newfd = accept(fd, (struct sockaddr *)&cin, &addrlen)) < 0) 65 { 66 perror("accept"); 67 break; 68 } 69 /*创建一个子进程用于处理已经建立连接的客户的交互数据 */ 70 if((pid = fork()) < 0) 71 { 72 perror("fork"); 73 break; 74 } 75 76 if(0 == pid) //子进程 77 { 78 close(fd); //关闭不需要的文件描述符,节省资源 79 //读出客户端信息,并将HBD转为NBD 80 char ipv4_addr[16]; 81 if(!inet_ntop(AF_INET, (void *)&cin.sin_addr,ipv4_addr,sizeof(cin))) 82 { 83 perror("inet_ntop"); 84 exit(1); 85 } 86 87 //打印客户端的IP和端口号 88 printf("Client(%s,%d) is connected! ",ipv4_addr,ntohs(cin.sin_port)); 89 cli_data_handle(&newfd); 90 return 0; //客户端数据处理完毕,return跳出循环 91 92 }else //pid > 0, 父进程 93 { 94 close(newfd); //关闭不需要的文件描述符 95 } 96 } 97 close(fd); 98 return 0; 99 100 } 101 102 void cli_data_handle(void *arg) 103 { 104 int newfd = *(int *)arg; 105 106 printf("Child handle process:newfd = %d ", newfd); 107 108 /*5. 读写数据*/ 109 int ret = -1; 110 char buf[BUFSIZ]; 111 112 while(1) 113 { 114 bzero(buf, BUFSIZ); 115 do{ 116 ret = read(newfd, buf, BUFSIZ-1); 117 }while(ret < 0 && EINTR == errno); 118 if(ret < 0) 119 { 120 perror("read"); 121 exit(1); 122 } 123 124 if(!ret) //对方已经关闭 125 { 126 break; 127 } 128 printf("Receive data: %s",buf); 129 130 if(!strncasecmp(buf,QUIT_STR, strlen(QUIT_STR))) 131 { 132 printf("Client is exiting! "); 133 break; 134 } 135 } 136 close(newfd); 137 138 }
1 /*运行方式: ./client serv_ip serv_port */ 2 #include "net.h" 3 4 void usage(char *s) 5 { 6 printf(" %s serv_ip serv_port ",s); 7 printf(" serv_ip:serv ip address"); 8 printf(" serv_port: sever port(>5000) "); 9 } 10 11 int main(int argc, char **argv) 12 { 13 int fd = -1; 14 15 int port = -1; 16 struct sockaddr_in sin; 17 18 if(argc != 3)//参数错误检测 19 { 20 usage(argv[0]); 21 exit(1); 22 } 23 /*1.创建sock fd */ 24 if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) 25 { 26 perror("socket"); 27 exit(1); 28 } 29 30 port = atoi(argv[2]); 31 if(port < 5000) 32 { 33 usage(argv[0]); 34 exit(1); 35 } 36 /*2.连接服务器 */ 37 /*2.1 填充struct sockaddr_in结构体变量*/ 38 bzero(&sin, sizeof(sin)); //初始值置零 39 sin.sin_family = AF_INET; // 40 sin.sin_port = htons(port); //转化为NBD 41 #if 0 42 sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); 43 #else 44 if(inet_pton(AF_INET, argv[1],(void *)&sin.sin_addr.s_addr) != 1) 45 { 46 perror("inet_pton"); 47 exit(1); 48 } 49 #endif 50 51 if(connect(fd,(struct sockaddr *)&sin, sizeof(sin)) < 0) 52 { 53 perror("connect"); 54 exit(1); 55 } 56 57 printf("Client starting ... "); 58 59 /*3.读写数据*/ 60 char buf[BUFSIZ]; 61 int ret = -1; 62 while(1) 63 { 64 bzero(buf,BUFSIZ); 65 if(fgets(buf, BUFSIZ-1, stdin) == NULL) 66 { 67 continue; 68 } 69 do{ 70 ret = write(fd, buf, strlen(buf)); 71 }while(ret < 0 && EINTR == errno); 72 if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR))) 73 { 74 printf("Clinet is exiting! "); 75 break; 76 } 77 78 } 79 80 /*4.关闭套接字 */ 81 close(fd); 82 83 return 0; 84 }
1 all: 2 gcc -o client client.c 3 gcc -o process_tcp process_tcp.c 4 5 clean: 6 rm *.elf
测试结果: