1. 程式人生 > >TCP伺服器的單程序、多程序實現

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。然⽽,各種網路協議的地址格式並不相同,如下圖所示:
這裡寫圖片描述


由上圖可以看出:各種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通訊的基本過程,這樣才能有一個比較清晰的思路實現我們的程式碼,基本流程圖如下:
這裡寫圖片描述
其基本過程如下: 伺服器調⽤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<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/socket.h>
  4 #include<sys/types.h>
  5 #include<string.h>
  6 #include<error.h>
  7 #include<errno.h>
  8 #include<netinet/in.h>
  9 #include<arpa/inet.h>
 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 <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  
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、執行結果顯示

要使程式成功通訊,我們必須確保伺服器端先執行,並且本機是開著的狀態:
這裡寫圖片描述

六、多程序伺服器端版本

1、程式程式碼

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<sys/socket.h>
  6 #include<sys/wait.h>
  7 #include<netinet/in.h>
  8 #include<arpa/inet.h>
  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)耗費成本大,程序建立的越多,導致程序切換的週期越長,成本也變大,進而也影響了伺服器的效能。