一、分析
要实现TCP通信服务器处理并发的任务,使用多线程或者多进程来解决。
思路:
1、一个父进程,多个子进程;
2、父进程负责等待并接受客户端的连接;
3、子进程:完成通信,接受一个客户端连接,就创建一个子进程用于通信。
二、代码实现
设计意图
1)服务器阻塞接收若干个客户端的连接请求,一旦有一个客户端connect,则服务器端fork一个子进程与之连接。
2)在服务器端,子进程的任务是接收并处理客户端的数据,而父进程的任务是监听新的客户端请求(accept),创建子进程,并负责回收子进程(可通过处理SIGCHLD信号的方式)。
server_process.c
1 #include <stdio.h>
2 #include <arpa/inet.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <signal.h>
7 #include <wait.h>
8 #include <errno.h>
9 void recyChild(int argc)
10 {
11 while(1)
12 {
13 int ret=waitpid(-1,NULL,WNOHANG);
14 if(ret==-1)
15 {
16 //所有进程都运行完了
17 break;
18 }
19 else if(ret==0)
20 {
21 //还有子进程活着
22 break;
23 }
24 else if(ret>0)
25 {
26 //还有子进程没有回收
27 printf("%d 进程被回收了\n",ret);
28
29 }
30 }
31 }
32 int main()
33 {
34 //注册捕捉函数
35 struct sigaction act;
36 act.sa_flags=0;
37 sigemptyset(&act.sa_mask);
38 act.sa_handler=recyChild;
39 sigaction(SIGCHLD,&act,NULL);
40 //1、socket
41 int lfd=socket(AF_INET,SOCK_STREAM,0);
42 if(lfd==-1)
43 {
44 perror("socket");
45 exit(-1);
46 }
47 //2、绑定bind
48 struct sockaddr_in saddr;
49 saddr.sin_family=AF_INET;
50 saddr.sin_port=htons(9999);
51 saddr.sin_addr.s_addr=INADDR_ANY;
52 int ret=bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
53 if(ret==-1)
54 {
55 perror("bind");
56 exit(-1);
57 }
58 //3、监听listen
59 ret=listen(lfd,128);
60 if(ret==-1)
61 {
62 perror("listen");
63 exit(-1);
64 }
65 //4、循环接收accept
66 while(1)
67 {
68 struct sockaddr_in cliaddr;
69 int len=sizeof(cliaddr);
70 int num=accept(lfd,(struct sockaddr*)&cliaddr,&len);
71 if(num==-1)
72 {
73 if(errno==EINTR)
74 {
75 continue;
76 }
77 perror("accept");
78 exit(-1);
79 }
80
81 //创建子进程
82 pid_t pid=fork();
83 if(pid==0)
84 {
85 char cliIP[16];
86
inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,cliIP,sizeof(cliIP));
87 unsigned short cliPort=ntohs(cliaddr.sin_port);
88 printf("client ip:%s,proc :%d\n",cliIP,cliPort);
89
90 //接收客户端发来的信息
91 char recvBuf[1024]={0};
92 while(1)
93 {
94 int fd=read(num,&recvBuf,sizeof(recvBuf));
95 if(fd==-1)
96 {
97 perror("read");
98 exit(-1);
99 }
100 else if(fd>0)
101 {
102 printf("recv data:%s\n",recvBuf);
103 }
104 else
105 {
106 printf("client closed...");
107 }
108 write(num,recvBuf,strlen(recvBuf));
109 // exit(0);
110 // close(fd);
111 }
112 }
113 // close(num);
114 }
115 // close(ret);
116 return 0;
117 }
client_process.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <arpa/inet.h>
6 int main()
7 { //1、socket连接
8 int cld=socket(AF_INET,SOCK_STREAM,0);
9 if(cld==-1)
10 {
11 perror("socket");
12 exit(-1);
13 }
14 //2、连接
15 struct sockaddr_in caddr;
16 caddr.sin_family=AF_INET;
17 inet_pton(AF_INET,"192.168.17.136",&caddr.sin_addr.s_addr);
18 caddr.sin_port=htons(9999);
19 int ret=connect(cld,(struct sockaddr *)&caddr,sizeof(caddr));
20 if(ret==-1)
21 {
22 perror("connect");
23 exit(-1);
24 }
25 //3、通信 给客户端发送数据
26 char recvBuf[1024]={0};
27 int i=0;
28 while(1)
29 {
30 sprintf(recvBuf,"%d\n",i++);
31 write(cld,recvBuf,strlen(recvBuf)+1);
32 int fd=read(cld,recvBuf,sizeof(recvBuf));
33 sleep(1);
34 if(fd==-1)
35 {
36 perror("read");
37 exit(-1);
38 }
39 else if(fd>0)
40 {
41 printf("recv server data:%s\n",recvBuf);
42 }
43 else
44 {
45 printf("connect closed ...");
46 }
47 // close(fd);//如果不把这块删掉只能传送10个数据,删除后可以连续发送数据
48 }
49 close(cld);
50 return 0;
51 }
运行:
一定要先运行服务器,再运行客户端,这样不会报错,首先客户端会先主动给服务器发送信号,这个时候就开始三次握手了(这个时间是非常快的),三次握手结束后就可以正常给服务器发送数据了,此时可以连续打开多个客户端用于实现多进程,此时观察服务器的状态,你会发现服务器不断接受不同的客户端发来的信号。当你关闭一个客户端后(此时会出现四次挥手,速度很快),服务器会显示“已经回收进程”后继续接受别的进程发来的信号,如果你重新运行刚才关闭的客户端,此时服务器会重新接受这个客户端发来的信号。如果全部关闭客户端的话,此时的服务器会出现阻塞等待,直到下一个客户端发来信号。
大家可以复制程序在自己的设备上运行。