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
三、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)耗費成本大,程序建立的越多,導致程序切換的週期越長,成本也變大,進而也影響了伺服器的效能。