之前,我们实现了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)只有连接来才创建进程,性能较差,服务效率较低。(可以利用线程池解决这个问题);