如下介绍一个并发回射客户端/服务器的雏形,所谓回射:就是客户端输入一条数据,服务器端读取并显示,然后服务器端再把刚读取的信息发送回客户端进行显示。示意图如下:
所谓并发服务器:就是一个服务器可以同时为多个连入的客户端提供服务,示意图如下:
如下主要介绍两种实现并发回射服务器的方式,一种是通过子进程方式实现并发,一种是通过I/O多路转接实现并发。
并发服务器(1)[子进程方式]
1 [root@benxintuzi tcp]# cat echoserv_childprocess.c 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <errno.h> 10 #include <stdio.h> 11 12 #define ERR_EXIT(message) \ 13 do \ 14 { \ 15 perror(message); \ 16 exit(EXIT_FAILURE); \ 17 } while(0) 18 19 /* 回射服务 */ 20 void echoserv(int conn) 21 { 22 char recvbuf[1024]; 23 while (1) 24 { 25 memset(recvbuf, 0, sizeof(recvbuf)); 26 int ret; 27 if ((ret = read(conn, recvbuf, sizeof(recvbuf))) == -1) 28 ERR_EXIT("read"); 29 if (ret == 0) /* client closed */ 30 { 31 printf("client closed.\n"); 32 break; 33 } 34 35 fputs(recvbuf, stdout); 36 if (write(conn, recvbuf, ret) != ret) 37 ERR_EXIT("write"); 38 } 39 exit(EXIT_SUCCESS); 40 } 41 42 43 int main(void) 44 { 45 /* 创建一个监听套接字 */ 46 int listen_fd; 47 if ((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 48 ERR_EXIT("socket"); 49 50 /* 初始化服务器地址 */ 51 struct sockaddr_in serv_addr; 52 memset(&serv_addr, 0, sizeof(serv_addr)); 53 serv_addr.sin_family = AF_INET; 54 serv_addr.sin_port = htons(5188); 55 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 56 /** 57 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 58 inet_aton("127.0.0.0", &serv_addr.sin_addr); */ 59 60 /* 设置地址重复利用,使得服务器不必等待TIME_WAIT状态消失就可以重启 */ 61 int on = 1; 62 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) 63 ERR_EXIT("setsockopt"); 64 65 /* 将服务器地址绑定到监听套接字上 */ 66 if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 67 ERR_EXIT("bind"); 68 69 /* 监听进入的连接 */ 70 if (listen(listen_fd, SOMAXCONN) == -1) 71 ERR_EXIT("listen"); 72 73 /* 初始化一个客户端地址用于保存接入的客户端 */ 74 struct sockaddr_in clit_addr; 75 memset(&clit_addr, 0, sizeof(clit_addr)); 76 socklen_t clit_len = sizeof(clit_addr); 77 78 pid_t pid; 79 int conn; 80 while (1) 81 { 82 /* 从已连接队列(保存已完成三次握手的连接)中返回第一个连接 */ 83 /* 将返回的客户端连接保存在clit_addr中 */ 84 if ((conn = accept(listen_fd, (struct sockaddr*)&clit_addr, &clit_len)) == -1) 85 ERR_EXIT("accept"); 86 printf("client(ip = %s, port = %d) connected.\n", inet_ntoa(clit_addr.sin_addr), ntohs(clit_addr.sin_port)); 87 88 /* 创建子进程用于回射服务 */ 89 if (( pid = fork()) == -1) 90 ERR_EXIT("fork"); 91 if (pid == 0) /* 子进程,每接入一个客户端,就创建一个子进程进行回射服务,这样就可以实现并发处理了 */ 92 { 93 /* 子进程只负责回射服务,不负责连接客户端,因此需要关闭监听套接字 */ 94 close(listen_fd); 95 96 /* 进行回射服务 */ 97 echoserv(conn); 98 } 99 else /* 父进程 */ 100 close(conn); 101 } 102 103 return 0; 104 }