TCP服务器的单进程、多进程实现

一、socket编程

在理解TCP服务器时,我们必须了解socket编程,在上篇博客中,我们知道在TCP/IP协议中,“IP地址+TCP端口号/UDP端口号”唯一标识网络通讯中的唯一一个进程,我们把“IP地址+端口号”就成为socket。

在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么这两个socket组成 的socketpair就唯一标识一个连接。socket本身有“插座”的意思,因此用来描述网络连接的一对一关系。

二、socket地址数据类型及相关函数

IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址⽤sockaddr_in结构体表⽰,包括16位端口号和32位IP地址,IPv6地址⽤sockaddr_in6结构体表⽰,包括16位端口号、 128位IP地址和⼀些控制字段。
socket API是一层抽象的⽹网络编程接口,适⽤用于各种底层⽹网络协议,如IPv4、IPv6,以及UNIX Domain Socket。然⽽,各种网络协议的地址格式并不相同,如下图所示:
TCP服务器的单进程、多进程实现_第1张图片
由上图可以看出:各种socket地址结构体的开头都是相同的,前16位表示整个结构体的长度(并不是所有UNIX的实现 都有长度字段,如Linux就没有),后16位表示地址类型。IPv4、IPv6和UNIXDomain Socket的地 址类型分别定义为常数AF_INET、AF_INET6、AF_UNIX。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的 内容。因此,socket API可以接受各种类型的sockaddr结构体指针做参数,例如bind、accept、connect等函数,这些函数的参数应该设计成void 类型以便接受各种类型的指针,但是sock API的实现早于ANSI C标准化,那时还没有void 类型,因此这些函数的参数都用struct sockaddr *类型表示,在传递参数之前要强制类型转换一下。

三、TCP通讯协议的基本流程

在实现TCP服务器之前,我们必须了解TCP通讯的基本过程,这样才能有一个比较清晰的思路实现我们的代码,基本流程图如下:
TCP服务器的单进程、多进程实现_第2张图片
其基本过程如下: 服务器调⽤socket()、 bind()、 listen() 完成初始化后,调⽤accept()阻塞等待,处于监听端口的状态,客户端调⽤socket()初始化后,调⽤connect()发出SYN段并阻塞等待服务器应答,服务器应答⼀个SYN-ACK段,客户端收到后从connect()返回,同时应答⼀个ACK段,服务器收到后 从accept()返回。

四、服务器端与客户端通讯步骤

1、基于TCP的socket编程的服务器程序流程如下:

(1)创建套接字
(2)将套接字绑定到本地地址和端口上
(3)将创建的套接字设为监听模式,等待接收客户端的请求
(4)等待客户请求的到来,当请求到来后,接收连接请求,返回一个新的对于与此次连接的套接字
(5)用返回的套接字和客户端进行通信
(6).返回,等待另一个客户的请求
(7)关闭套接字

2、基于TCP的socket编程的客户度程序流程如下:

(1)创建套接字
(2)向服务器发出连接请求
(3)和服务器进行通信
(4)关闭套接字
客户端不需要绑定,如果绑定了,如果在同一台机器上启动多个客户端,就会出现端口号被占用导致不能正确连接。

五、客户端与服务器端程序(单进程)

1、客户端程序

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 #include
  8 #include
  9 #include
 10 
 11 
 12 int main(int argc,char*argv[])
 13 {
 14     if(argc!=2)
 15     {
 16         printf("Usage:client IP\n");
 17         return 1;
 18     }
 19     char *str=argv[1];
 20     int sock=socket(AF_INET,SOCK_STREAM,0);
 21     if(sock<0)
 22     {
 23         printf("sock error\n");
 24         return 2;
 25     }
 26     struct sockaddr_in server_sock;
 27     server_sock.sin_family=AF_INET;
 28     server_sock.sin_port=htons(8080);
 29     server_sock.sin_addr.s_addr=inet_addr(str);
 30 
 31     int ret=connect(sock,(const struct sockaddr*)&server_sock,sizeof(server_sock));
 32     if(ret<0)
 33     {
 34         printf("connect failed...,error is:%d,errstring is:%s\n",errno,strerror(errno));
 35         return 3;
 36     }
 37     printf("connect success...\n");
 38     char buf[1024];
 39     while(1)
 40     {
 41         memset(buf,0,sizeof(buf));
 42         printf("client:#");
 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 
 53         s=read(sock,buf,sizeof(buf));
 54         if(s>0)
            {  
55             buf[s]=0;  
56             printf("server:%s\n",buf);  
57         }  
58             
59     }  
60     close(sock);  
61     return 0;  
62 }                   

2、服务器端程序

 1 #include                                                             
 2 #include   
 3 #include   
 4 #include   
 5 #include   
 6 #include   
 7 #include   
 8 #include   
 9   
10 #define _BACKLOG 10  
11   
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   
39     return listen_sock;  
40 }  
41 void service(int new_sock)  
 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: %s\n",buf);  
 60         write(new_sock,buf,strlen(buf));  
 61     }  
 62 }  
 68 int main(int argc,char*argv[])  
 69 {  
 70     if(argc!=3)  
 71     {  
 72         printf("Usage:[IP],[port]!\n");  
 73         exit(1);  
 74     }  
 75   
 76     char* IP=argv[1];  
 77     int port=atoi(argv[2]);   
 78     int listen_sock=mysocket(IP,port);  
 79     printf("server:%d\n",listen_sock);  
 80   
 81     struct sockaddr_in peer;  
 82     socklen_t len=sizeof(peer);  
 83     for(;;)  
 84     {  
 85         int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);  
 86         if(new_sock<0)  
 87         {  
 88             printf("perror accept\n");  
 89             continue;  
 91         char bufip[32];  
 92         bufip[0]=0;  
 93         inet_ntop(AF_INET,&peer.sin_addr,bufip,sizeof(bufip));  
 94         printf("get connect,ip is:%s port is:%d\n",\  
 95                 bufip,ntohs(peer.sin_port));  
 96         service(new_sock);  
 97     }  
 98     close(listen_sock);  
 99     return 0;  
100   }   

3、运行结果显示

要使程序成功通信,我们必须确保服务器端先运行,并且本机是开着的状态:
TCP服务器的单进程、多进程实现_第3张图片

六、多进程服务器端版本

1、程序代码

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 #include
  8 #include
  9 
 10 void Usage()
 11 {
 12     printf("usage: ./server[ip][port]\n");
 13 }
 14 
 15 void ProcessRequest(int client_fd,struct sockaddr_in*client_addr)
 16 {
 17     char buf[1024]={0};
 18     for(;;)
 19     {
 20         ssize_t read_size=read(client_fd,buf,sizeof(buf));
 21         if(read_size<0)
 22         {
 23             perror("read");
 24             continue;
 25         }
 26         if(read_size==0)
 27         {
 28             printf("client:%s say bye!\n",inet_ntoa(client_addr->sin_addr));
 29             close(client_fd);
 30             break;
 31         }
 32         buf[read_size]='\0';
 33         printf("client %s say:%s\n",inet_ntoa(client_addr->sin_addr),buf);
 34         write(client_fd,buf,strlen(buf));
 35     }
 36     return;
 37 }
 38 
 39 void CreateWorker(int client_fd,struct sockaddr_in*client_addr)
 40 {
 41     pid_t pid=fork();
 42     if(pid<0)
 43     {
 44         perror("fork");
 45         return;
 46     }
 47     else if(pid==0)
 48     {
 49         //child
 50         if(fork()==0)
 51         {
 52             ProcessRequest(client_fd,client_addr);
 53         }
 54         exit(0);
 55     }
 56     else{
 57         //father
 58         close(client_fd);
 59         waitpid(pid,NULL,0);
 60     }
 61 }
 62 
 63 int main(int argc,char*argv[])
 64 {
 65     if(argc!=3){
 66         Usage();
 67         return 1;
 68     }
 69     struct sockaddr_in addr;
 70     addr.sin_family=AF_INET;
 71     addr.sin_addr.s_addr=inet_addr(argv[1]);
 72     addr.sin_port=htons(atoi(argv[2]));
 73 
 74     int fd=socket(AF_INET,SOCK_STREAM,0);
 75     if(fd<0)
 76     {
 77         perror("socket");
 78         return 1;
 79     }
 80 
 81     int ret=bind(fd,(struct sockaddr*)&addr,sizeof(addr));
 82     if(ret<0)
 83     {
 84         perror("bind");
 85         return 1;
 86     }
 87     ret=listen(fd,10);
 88     if(ret<0)
 89     {
 90         perror("listen");
 91         return 1;
 92     }
 93     for(;;){
 94     struct sockaddr_in client_addr;
 95     socklen_t len=sizeof(client_addr);
 96     int client_fd=accept(fd,(struct sockaddr*)&client_addr,&len);
 97     if(client_fd<0)
 98     {
 99         perror("accept");
100         continue;
101     }
102     CreateWorker(client_fd,&client_addr);
103     }
104     return 0;
105 }

2、优缺点

优点:
(1)可以处理多个进程
(2)代码易于编写
(3)多进程服务器稳定,因为进程运行具有独立性
缺点:
(1)服务器性能比较差:因为只有在有新的连接进入时,系统才创建进程。
(2)占用资源较多,同时服务的连接有上限,并且上限很容易达到。
(3)耗费成本大,进程创建的越多,导致进程切换的周期越长,成本也变大,进而也影响了服务器的性能。

你可能感兴趣的:(Linux)