之前,我们实现了TCP的单进程模式,在这种模式下,服务器每次只能给一个客户端提供服务,在正常情况下,是不合逻辑的,所以,我们对单进程TCP做了改进,实现TCP的多进程和多线程模式。
1. 多进程TCP
分析
(1)要实现多进程TCP,我们就要调用系统函数fork来创建进程,简单一点,我们可以让父进程监听客户端的请求连接,子进程对已经建立连接的客户端提供服务。
(2)在创建子进程后,子进程必须回收,否则就会造成内存泄漏(尤其对于服务器这种一直处于运行的进程)。但是父进程不知道子进程何时完成它的工作并退出,所以父进程就要一直阻塞式的等待,如果父进程一直等待,则父进程原来的工作就不能进行了,造成问题。
(3)我们可以想一种不用父进程阻塞式等待的方式,一种是利用子进程退出时会发送SINGCHID信号,我们利用SINGCHID信号回收子进程;另一种是让子进程创建自己的子进程(孙子进程),让孙子进程执行子进程的任务(对已经建立连接的客户端提供服务),子进程退出,则它的父进程就不用在等待它了,只要子进程一退出,父进程就可以回收子进程;而孙子进程此时成为孤儿进程,在退出时,由操作系统自动回收,这样就不会造成内存泄漏了。
服务器端:
头文件
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <netinet/in.h>
5 #include <errno.h>
6 #include <unistd.h>
7 #include <sys/types.h>
8 #include <sys/socket.h>
9
10 #define _BACKLOG 10
创建socket并监听
12 int mysocket(char* IP,int port)
13 {
14 int listen_sock=socket(AF_INET,SOCK_STREAM,0);
15 if(listen_sock<0)
16 {
17 printf("perror socket\n");
18 exit(2);
19 }
20 struct sockaddr_in local;
21 local.sin_family=AF_INET;
22 local.sin_port=htons(port);
23 local.sin_addr.s_addr=inet_addr(IP);
24 int binding=bind(listen_sock,(struct sockaddr*)&local,\
25 sizeof(struct sockaddr_in));
26 if(binding<0)
27 {
28 printf("perror bind\n");
29 exit(3);
30 }
31
32 if(listen(listen_sock,_BACKLOG)<0)
33 {
34 printf("perror listen\n");
35 close(listen_sock);
36 exit(4);
37 }
38
提供服务
41 void service(int new_sock,struct sockaddr_in*peer)
42 {
43 char buf[1024];
44 memset(buf,0,sizeof(buf));
45 while(1)
46 {
47 ssize_t s=read(new_sock,buf,sizeof(buf));
48 if(s<0)
49 {
50 printf("perror read\n");
51 exit(5);
52 }
53 else if(s==0)
54 {
55 close(new_sock);
56 break;
57 }
58 buf[s]=0;
59 printf("Client:[ip:] %s,[port]:%d,say:%s\n",\
60 inet_ntoa(peer->sin_addr),ntohs(peer->sin_port),buf);
61 write(new_sock,buf,strlen(buf));
62 }
}
创建子进程(子进程提供服务)
65 void createworker(int listen_sock,int new_sock,struct sockaddr_in* peer)
66 {
67 pid_t pid=fork();
68 if(pid<0)
69 {
70 printf("perror fork\n");
71 return;
72 }
73 else if(pid==0)
74 {
75 //child;
76 if(fork()==0)
77 {
78 //grandchild;
79 service(new_sock,peer);
80 }
81 else if(fork()>0)
82 {
83 exit(0);
84 }
85 }
86 else
87 {
88 //parent
89 close(new_sock); //关闭服务端口
90 waitpid(pid,NULL,0); //回收子进程
91 }
92 }
主逻辑
94 int main(int argc,char*argv[])
95 {
96 if(argc!=3)
97 {
98 printf("Usage:[IP],[port]!\n");
99 exit(1);
100 }
101
102 char* IP=argv[1];
103 int port=atoi(argv[2]);
104 int listen_sock=mysocket(IP,port);
105 printf("server:%d\n",listen_sock);
106
107 struct sockaddr_in peer;
108 socklen_t len=sizeof(peer);
109 for(;;)
110 {
111 int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
112 if(new_sock<0)
113 {
114 printf("perror accept\n");
115 continue;
116 }
117
118 char bufip[32];
119 bufip[0]=0;
120 inet_ntop(AF_INET,&peer.sin_addr,bufip,sizeof(bufip));
121 printf("get connect,ip is:%s port is:%d\n",\
122 bufip,ntohs(peer.sin_port));
123
124 // service(new_sock);
125 createworker(listen_sock,new_sock,&peer);
126 }
127
128 close(listen_sock);
129
130 return 0;
}
客户端:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <string.h>
5 #include <errno.h>
6 #include <netinet/in.h>
7 #include <arpa/inet.h>
8 int main(int argc,char*argv[])
9 {
10 if(argc!=2)
11 {
12 printf("usage:[IP]\n");
13 return 1;
14 }
15 char *IP=argv[1];
16 int sock=socket(AF_INET,SOCK_STREAM,0);
17 if(sock<0)
18 {
19 printf("perror sock!\n");
20 return 2;
21 }
22
23 struct sockaddr_in local;
24 local.sin_family=AF_INET;
25 local.sin_port=htons(8080);
26 local.sin_addr.s_addr=inet_addr(IP);
27 // inet_pton(AF_INET,IP,&local.sin_addr);
28 int ret=connect(sock,(const struct sockaddr*)&local,sizeof(local));
29
30 if(ret<0){
31
32 printf("perror connect!\n");
33 return 3;
34 }
35
36 printf("connect success!\n");
37
38 char buf[1024];
39 while(1)
40 {
41 memset(buf,0,sizeof(buf));
42 printf("client:please enter!\n");
43 fflush(stdout);
44 ssize_t s=read(0,buf,sizeof(buf));
45 buf[s-1]=0;
46 if(strcmp(buf,"quit")==0)
47 {
48 printf("client quit!\n");
49 break;
50 }
51 write(sock,buf,sizeof(buf));
52 s=read(sock,buf,sizeof(buf));
53 if(s>0)
54 {
55 buf[s]=0;
56 printf("server:%s\n",buf);
57 }
58
59 }
60 close(sock);
61 return 0;
62 }
2. 多线程TCP
分析
(1)要实现多线程的TCP,我们必须调用pthread_create函数创建新线程。
(2)新线程在退出后我们也必须回收,否则会造成资源浪费。我们可以利用分离线程解决这个问题(分离后的线程不需要回收)。
多线程TCP只需要创建线程就可以了,其他主逻辑不变。
服务器端:
94 typedef struct Arg{
95 int fd;
96 struct sockaddr_in addr;
97 }Arg;
98
99 void *mythread(void* arg)
100 {
101 Arg* ptr=(Arg*)arg;
102 service(ptr->fd,&ptr->addr);
103 free(ptr);
104 return NULL;
105 }
106 int main(int argc,char*argv[])
107 {
108 if(argc!=3)
109 {
110 printf("Usage:[IP],[port]!\n");
111 exit(1);
112 }
113
114 char* IP=argv[1];
115 int port=atoi(argv[2]);
116 int listen_sock=mysocket(IP,port);
117 printf("server:%d\n",listen_sock);
118
119 struct sockaddr_in peer;
120 socklen_t len=sizeof(peer);
121 for(;;)
122 {
123 int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
124 if(new_sock<0)
125 {
126 printf("perror accept\n");
127 continue;
128 }
127 continue;
128 }
129
130 char bufip[32];
131 bufip[0]=0;
132 inet_ntop(AF_INET,&peer.sin_addr,bufip,sizeof(bufip));
133 printf("get connect,ip is:%s port is:%d\n",\
134 bufip,ntohs(peer.sin_port));
135
136 // service(new_sock);
137 // createworker(listen_sock,new_sock,&peer);
138
139 pthread_t tid=0;
140 pthread_t tid1=0;
141 Arg*arg=(Arg*)malloc(sizeof(Arg));
142 arg->fd=new_sock;
143 arg->addr=peer;
144 pthread_create(&tid,NULL,mythread,(void*)arg);
145 pthread_create(&tid1,NULL,mythread,(void*)arg);
146
147 pthread_detach(tid);
148 pthread_detach(tid1);
149 }
151 close(listen_sock);
152 return 0;
153 }
3. 多进程和多线程TCP的优缺点
多进程TCP
优点:(1)稳定性好,因为各进程之间保持独立性,一个进程的退出不会影响其他进程。
(2)相比单进程可以处理多用户。
(3)程序简单,易于编写。
缺点:(1)只有连接来才创建进程,性能差,服务效率低。(可以利用进程池解决这个问题);
(2)资源要求比较高,服务有上限;
(3)当进程增多时,切换成本变大,CPU调度资源压力变大,影响性能。
多线程TCP
优点:相比多进程TCP来说(1)资源要求比较低,服务上限时间到达较长;
(2)切换成本较低,CPU调度资源压力较小,性能可以提升一些;
(3)程序简单,易于编写。
缺点:(1)稳定性不好,容易引发线程安全问题;
(3)只有连接来才创建进程,性能较差,服务效率较低。(可以利用线程池解决这个问题);