前言
本文使用C語言編寫一個(gè)簡(jiǎn)單服務(wù)器柄瑰,旨在更好的理解服務(wù)端/客戶端程序,迭代服務(wù)器剪况,并發(fā)服務(wù)器等概念教沾,僅供學(xué)習(xí)參考。這篇文章的例子很簡(jiǎn)單译断,就是當(dāng)客戶端連接上服務(wù)端之后授翻,服務(wù)端給出一個(gè)“Hello World”回應(yīng)。
C/S結(jié)構(gòu)流程圖
整個(gè)客戶端,服務(wù)端交互流程可以用下圖表示堪唐,服務(wù)端是優(yōu)先啟動(dòng)進(jìn)程并監(jiān)聽某一個(gè)端口巡语,并且進(jìn)程一直阻塞,直到有客戶端連接進(jìn)來淮菠,才開始處理客戶端連接男公。
服務(wù)端
通過流程圖可以看出,服務(wù)端涉及的Socket函數(shù)有socket, bind, listen, accept, read, write, close合陵。使用這7個(gè)函數(shù)就可以編寫出一個(gè)簡(jiǎn)易服務(wù)器枢赔。
socket函數(shù)
為了執(zhí)行網(wǎng)絡(luò)I/O,一個(gè)進(jìn)程必須做的第一件事情就是創(chuàng)建一個(gè)socket函數(shù)拥知,函數(shù)原型
# family 表示協(xié)議族
# type 表示套接字類型
# protocol 表示傳輸協(xié)議
# 若成功返回非負(fù)描述符踏拜,若出錯(cuò)返回-1
int socket(int family, int type, int protocol);
這個(gè)函數(shù)需要傳入?yún)f(xié)議族,套接字類型低剔,傳輸層協(xié)議三個(gè)參數(shù)速梗。
協(xié)議族可以有以下取值
family | 說明 |
---|---|
AF_INET | IPv4協(xié)議 |
AF_INET6 | IPv6協(xié)議 |
AF_LOCAL | Unix域協(xié)議 |
AF_ROUTE | 路由套接字 |
AF_KEY | 密鑰套接字 |
套接字類型可以有以下取值
type | 說明 |
---|---|
SOCK_STREAM | 字節(jié)流套接字 |
SOCK_DGRAM | 數(shù)據(jù)報(bào)套接字 |
SOCK_SEQPACKET | 有序分組套接字 |
SOCK_ROW | 原始套接字 |
傳輸層協(xié)議可以有以下取值
protocol | 說明 |
---|---|
IPPROTO_TCP | TCP傳輸協(xié)議 |
IPPROTO_UDP | UDP傳輸協(xié)議 |
IPPROTO_SCTP | SCTP傳輸協(xié)議 |
這里我們選擇IPv4協(xié)議,使用字節(jié)流套接字襟齿,傳輸層選擇TCP協(xié)議镀琉,所以第一段代碼:
#include <stdio.h>
#include <sys/socket.h>
int main()
{
int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
if(server_sockfd == -1){
printf("socket error");
return -1;
}
}
bind函數(shù)
bind函數(shù)把一個(gè)本地協(xié)議地址賦予一個(gè)套接字,對(duì)于網(wǎng)際協(xié)議蕊唐,協(xié)議地址就是IP加端口的組合,函數(shù)原型
# sockfd 初始化的套接字
# myaddr 協(xié)議地址
# addrlen 協(xié)議地址長(zhǎng)度
# 若成功返回0 出錯(cuò)返回-1
int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen)
注意烁设,這個(gè)函數(shù)不是必須的替梨,如果不使用這個(gè)函數(shù)綁定一個(gè)特定的端口,那么內(nèi)核會(huì)幫我們的套接字選擇一個(gè)臨時(shí)端口装黑。作為服務(wù)器副瀑,一般不會(huì)這么做,需要指定特定的端口恋谭。
這個(gè)函數(shù)的第二個(gè)參數(shù)是協(xié)議地址糠睡,注意,這個(gè)協(xié)議地址已經(jīng)有定義好的結(jié)構(gòu)體疚颊,使用IPv4套接字結(jié)構(gòu)地址時(shí)候狈孔,地址結(jié)構(gòu)體定義如下
struct sockaddr_in {
uint8_t sin_len; /*結(jié)構(gòu)體長(zhǎng)度*/
sa_family_t sin_family; /*AF_INET*/
in_port_t sin_port; /*端口(16-bie)*/
struct in_addr sin_addr; /*IPv4地址(32-bit)*/
char sin_zero[8]; /*沒啥用,設(shè)置0即可*/
}
我們讓我們的服務(wù)器綁定8887端口(80端口被web占用了材义,用8887端口代替),所以我們的第二段代碼
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
if(server_sockfd == -1){
printf("socket error");
return -1;
}
struct sockaddr_in server_sockaddr;/*聲明一個(gè)變量均抽,類型為協(xié)議地址類型*/
server_sockaddr.sin_family = AF_INET;/*使用IPv4協(xié)議*/
server_sockaddr.sin_port = htons(8887);/*監(jiān)聽8887端口*/
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*綁定本機(jī)IP,使用宏定義綁定*/
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
printf("bind error");
return -1;
}
}
listen函數(shù)
listen函數(shù)僅有服務(wù)器調(diào)用其掂,它完成兩件事情:
- 當(dāng)使用socket函數(shù)創(chuàng)建一個(gè)套接字時(shí)油挥,它被假設(shè)為一個(gè)主動(dòng)套接字,也就是說,它是一個(gè)將發(fā)送connect發(fā)起連接的客戶端套接字深寥。當(dāng)調(diào)用listen函數(shù)之后攘乒,它被轉(zhuǎn)成一個(gè)被動(dòng)套接字,只是內(nèi)核應(yīng)該接受連接請(qǐng)求惋鹅。所以则酝,調(diào)用listen之后套接字由CLOSED狀態(tài)轉(zhuǎn)到LISTEN狀態(tài)
- 這個(gè)函數(shù)規(guī)定內(nèi)核應(yīng)該為相應(yīng)套接字排隊(duì)的最大連接數(shù)
函數(shù)原型
/*失敗時(shí)返回-1*/
int listen(int sockfd, int backlog)
backlog參數(shù)的設(shè)定其實(shí)是表示兩個(gè)隊(duì)列的總和,這兩個(gè)隊(duì)列分別是
- 未完成連接隊(duì)列负饲,在客戶端發(fā)送一個(gè)SYN直到三次握手完成堤魁,都是這個(gè)狀態(tài),SYN_RCVD狀態(tài)返十。
- 已完成連接隊(duì)列妥泉,這個(gè)表示三次握手完成的狀態(tài),ESTABLISHED狀態(tài)
因?yàn)槲覀兪菧y(cè)試洞坑,這個(gè)值設(shè)置成20就可以了盲链。所以我們的第三段代碼
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
if(server_sockfd == -1){
printf("socket error");
return -1;
}
struct sockaddr_in server_sockaddr;/*聲明一個(gè)變量,類型為協(xié)議地址類型*/
server_sockaddr.sin_family = AF_INET;/*使用IPv4協(xié)議*/
server_sockaddr.sin_port = htons(8887);/*監(jiān)聽8887端口*/
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*綁定本機(jī)IP迟杂,使用宏定義綁定*/
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
printf("bind error");
return -1;
}
if(listen(server_sockfd, 20) == -1){
printf("listen error");
return -1;
}
}
accept函數(shù)
accept函數(shù)是由TCP服務(wù)器調(diào)用刽沾,用于從已完成連接隊(duì)列的隊(duì)頭返回下一個(gè)已完成連接,如果已完成連接隊(duì)列為空排拷,那么進(jìn)程進(jìn)入睡眠模式侧漓,函數(shù)原型
# sockdf 服務(wù)器套接字莫描述符
# cliaddr 已連接的客戶端協(xié)議地址
# addrlen 已連接的客戶端協(xié)議地址長(zhǎng)度
# 成功返回非負(fù)描述符,出錯(cuò)返回-1
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
當(dāng)accept成功時(shí)监氢,返回值是由內(nèi)核自動(dòng)生成的全新描述符布蔗,代表與所返回的客戶端TCP連接。所以浪腐,在我們討論accept函數(shù)時(shí)纵揍,我們稱第一個(gè)參數(shù)為監(jiān)聽套接字,它的返回值是已連接套接字议街,一個(gè)服務(wù)器通常指創(chuàng)建一個(gè)監(jiān)聽套接字(通常是80端口)泽谨,內(nèi)核為每個(gè)由服務(wù)器進(jìn)程接受的客戶端連接創(chuàng)建一個(gè)已連接套接字,當(dāng)服務(wù)器完成對(duì)某個(gè)給定的客戶端服務(wù)時(shí)特漩,連接就會(huì)被關(guān)閉吧雹。
函數(shù)的第二個(gè)參數(shù)也是一個(gè)協(xié)議地址結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體和服務(wù)端協(xié)議地址是同一個(gè)結(jié)構(gòu)體涂身。我們可以不關(guān)心客戶端的協(xié)議吮炕,直接傳空,我們關(guān)系的是這個(gè)函數(shù)的返回值访得,因?yàn)樗祷氐氖强蛻舳诉B接描述符龙亲,我們可以對(duì)這個(gè)描述符進(jìn)行寫操作陕凹,從而實(shí)現(xiàn)給客戶端傳輸數(shù)據(jù)。所以我們第四段代碼
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
if(server_sockfd == -1){
printf("socket error");
return -1;
}
struct sockaddr_in server_sockaddr;/*聲明一個(gè)變量鳄炉,類型為協(xié)議地址類型*/
server_sockaddr.sin_family = AF_INET;/*使用IPv4協(xié)議*/
server_sockaddr.sin_port = htons(8887);/*監(jiān)聽8887端口*/
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*綁定本機(jī)IP杜耙,使用宏定義綁定*/
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
printf("bind error");
exit(1);
}
if(listen(server_sockfd, 20) == -1){
printf("listen error");
exit(1);
}
struct sockaddr_in clnt_addr;/*只是聲明,并沒有賦值*/
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if(clnt_sock == -1){
printf("appect error");
return -1;
}
}
write函數(shù)
前面的操作都完成之后拂盯,說明服務(wù)端和客戶端已經(jīng)建立連接佑女,由于TCP的傳輸是全雙工的,這時(shí)候客戶端和服務(wù)端都可以向?qū)Ψ桨l(fā)送數(shù)據(jù)谈竿。這里為了簡(jiǎn)化团驱,我們實(shí)現(xiàn)服務(wù)端發(fā)送“Hello World”給請(qǐng)求連接的客戶端。給客戶端發(fā)送數(shù)據(jù)很簡(jiǎn)單空凸,就是對(duì)返回的客戶端描述符進(jìn)行寫操作就可以了
# sockfd socket文件描述符
# buf 文件內(nèi)容
# count 內(nèi)容長(zhǎng)度
ssize_t write(int sockfd, const void * buf, size_t count);
完整的服務(wù)器代碼
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
if(server_sockfd == -1){
printf("socket error");
return -1;
}
struct sockaddr_in server_sockaddr;/*聲明一個(gè)變量嚎花,類型為協(xié)議地址類型*/
server_sockaddr.sin_family = AF_INET;/*使用IPv4協(xié)議*/
server_sockaddr.sin_port = htons(8887);/*監(jiān)聽8887端口*/
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*綁定本機(jī)IP,使用宏定義綁定*/
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
printf("bind error");
return -1;
}
if(listen(server_sockfd, 20) == -1){
printf("listen error");
return -1;
}
struct sockaddr_in clnt_addr;/*只是聲明呀洲,并沒有賦值*/
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if(clnt_sock == -1){
printf("appect error");
return -1;
}
char str[] = "Hello World";
write(clnt_sock, str, sizeof(str));
close(clnt_sock);
close(server_sockfd);
}
客戶端
客戶端要和服務(wù)器進(jìn)行通信紊选,從流程圖上可以看出,需要使用socket, connect, write, read, close這5個(gè)函數(shù)
socket函數(shù)
客戶端要和服務(wù)端進(jìn)行網(wǎng)絡(luò)通訊道逗,首先也必須調(diào)用socket函數(shù)兵罢,這里客戶端也使用IPv4協(xié)議,使用字節(jié)流套接字滓窍,傳輸層選擇TCP協(xié)議卖词,所以第一段代碼
#include <stdio.h>
#include <sys/socket.h>
int main()
{
int sock_cli = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
if(sock_cli == -1){
printf("socket error");
return -1;
}
}
connect函數(shù)
TCP客戶端就是使用connect函數(shù)和服務(wù)端建立連接,函數(shù)原型
# sockfd 客戶端TCP描述符
# sockaddr 服務(wù)端協(xié)議地址
# addrlen 服務(wù)端協(xié)議地址長(zhǎng)度
int connect(int sockfd, const struct sockaddr * servaddr, socklen_t addrlen);
這個(gè)函數(shù)將觸發(fā)客戶端和服務(wù)端三次握手吏夯,函數(shù)的第一個(gè)參數(shù)sockfd表示客戶端返回的描述符坏平,這里不需要調(diào)用bind函數(shù)綁定端口,系統(tǒng)會(huì)自動(dòng)分配锦亦。函數(shù)的第二個(gè)參數(shù)需要配置服務(wù)端IP和端口信息,同樣有結(jié)構(gòu)體規(guī)范這些信息令境,結(jié)構(gòu)體也是和服務(wù)端一樣使用sockaddr_in類型杠园。所以第二段代碼
#include <stdio.h>
#include <sys/socket.h>
int main()
{
int sock_cli = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
if(sock_cli == -1){
printf("socket error");
return -1;
}
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;/*使用IPv4協(xié)議*/
servaddr.sin_port = htons(8887);/*需要連接的遠(yuǎn)程服務(wù)器端口*/
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*需要連接的遠(yuǎn)程服務(wù)器IP*/
if(connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1){
printf("connect error");
return -1;
}
}
read函數(shù)
客戶端連接上服務(wù)器之后返回的是一個(gè)Socket文件描述符,既然是文件描述符舔庶,就可以通過簡(jiǎn)單的read函數(shù)獲取網(wǎng)絡(luò)數(shù)據(jù)抛蚁,read函數(shù)原型
# sockdf 文件描述符
# buf 文件內(nèi)容存放地址
# count 內(nèi)容長(zhǎng)度
ssize_t read(int sockfd,void *buf,size_t count)
這里我們讀取64個(gè)字節(jié)就夠了,不需要太多
char str[64];
read(sock_cli, str, 64);
完整的客戶端代碼
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
int sock_cli = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
if(sock_cli == -1){
printf("socket error");
return -1;
}
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;/*使用IPv4協(xié)議*/
servaddr.sin_port = htons(8887);/*需要連接的遠(yuǎn)程服務(wù)器端口*/
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*需要連接的遠(yuǎn)程服務(wù)器IP*/
if(connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1){
printf("connect error");
return -1;
}
char str[64];
read(sock_cli, str, 64);
printf(str);
close(sock_cli);
}
運(yùn)行客戶端服務(wù)端
將我們的服務(wù)端代碼保存為server.c惕橙,將我們的客戶端代碼保存為client.c瞧甩。分別編譯客戶端和服務(wù)端代碼
[root@iZ940ofmvruZ socket]# gcc server.c -o server
[root@iZ940ofmvruZ socket]# gcc client.c -o client
然后會(huì)分別生成兩個(gè)可執(zhí)行文件server和client。在一個(gè)窗口中先執(zhí)行server
[root@iZ940ofmvruZ socket]# ./server
執(zhí)行server之后弥鹦,我們知道accept函數(shù)會(huì)阻塞肚逸,所以程序一直運(yùn)行爷辙,等待客戶端連接進(jìn)來。這時(shí)候在另一個(gè)窗口執(zhí)行客戶端
[root@iZ940ofmvruZ socket]# ./client
Hello World
可以看到服務(wù)端給我們發(fā)送的Hello World朦促,我們?cè)倩氐椒?wù)端執(zhí)行窗口時(shí)膝晾,服務(wù)端也終止了進(jìn)程,這個(gè)交互完成务冕。然后我們會(huì)發(fā)現(xiàn)一個(gè)問題血当,服務(wù)端在提供完服務(wù)之后,它自己也關(guān)閉了禀忆,很顯然臊旭,我們希望服務(wù)器是一致運(yùn)行的提供服務(wù),所以我們需要實(shí)現(xiàn)一直運(yùn)行的服務(wù)器
不間斷提供服務(wù)
讓服務(wù)器一直運(yùn)行的方式很簡(jiǎn)單箩退,就是死循環(huán)离熏。循環(huán)的過程是accept一個(gè)客戶端連接,然后處理數(shù)據(jù)請(qǐng)求乏德,最后關(guān)閉客戶端連接撤奸。注意,我們不能關(guān)閉服務(wù)端連接喊括。所以我們改進(jìn)這個(gè)程序胧瓜,讓server不間斷的調(diào)用accept,因?yàn)閍ccept總是從已連接的隊(duì)列中返回一個(gè)連接郑什,然后處理府喳。改進(jìn)內(nèi)容片斷
/**
* 進(jìn)入死循環(huán)調(diào)用accept,給每一個(gè)連接上來的客戶端發(fā)送Hello World
*/
for( ; ; ){
struct sockaddr_in clnt_addr;/*只是聲明蘑拯,并沒有賦值*/
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if(clnt_sock == -1){
printf("appect error");
return -1;
}
char str[] = "Hello World";
write(clnt_sock, str, sizeof(str));
close(clnt_sock);
/*close(server_sockfd);*/
}
這樣修改之后钝满,這個(gè)服務(wù)端程序就是一直不間斷提供服務(wù)了
并發(fā)服務(wù)器
迭代服務(wù)器
我們首先來看下什么是迭代服務(wù)器,因?yàn)槲覀儎偛潘鶎懙木褪且粋€(gè)迭代服務(wù)器申窘,思考一個(gè)問題弯蚜,假如我們的服務(wù)器不是輸出Hello World這么簡(jiǎn)單,而是需要經(jīng)過一系列復(fù)雜邏輯計(jì)算甚至網(wǎng)絡(luò)調(diào)用剃法,那我們的程序執(zhí)行起來就不會(huì)怎么快了碎捺,為了模擬這種場(chǎng)景,我們?cè)诜?wù)端程序中假如sleep函數(shù)贷洲,我們讓程序睡眠3秒鐘收厨,模擬服務(wù)器處理復(fù)雜邏輯時(shí)間
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
if(server_sockfd == -1){
printf("socket error");
return -1;
}
struct sockaddr_in server_sockaddr;/*聲明一個(gè)變量,類型為協(xié)議地址類型*/
server_sockaddr.sin_family = AF_INET;/*使用IPv4協(xié)議*/
server_sockaddr.sin_port = htons(8887);/*監(jiān)聽8887端口*/
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*綁定本機(jī)IP优构,使用宏定義綁定*/
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
printf("bind error");
return -1;
}
if(listen(server_sockfd, 20) == -1){
printf("listen error");
return -1;
}
for( ; ; ){
struct sockaddr_in clnt_addr;/*只是聲明诵叁,并沒有賦值*/
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if(clnt_sock == -1){
printf("appect error");
return -1;
}
char str[] = "Hello World";
sleep(3);//3秒之后再向客戶端發(fā)送數(shù)據(jù)
write(clnt_sock, str, sizeof(str));
close(clnt_sock);
/*close(server_sockfd);*/
}
}
運(yùn)行這個(gè)服務(wù)端程序之后,我們同時(shí)執(zhí)行10個(gè)客戶端
for(( i=0; i< 10; i++ ))
do
{
./client
}&
done
在shell中執(zhí)行這段代碼钦椭,你會(huì)發(fā)現(xiàn)每隔3秒鐘輸出一個(gè)Hello World拧额。這是因?yàn)槲覀兊姆?wù)端程序是阻塞的碑诉,在處理一個(gè)請(qǐng)求的同時(shí),其他請(qǐng)求只能等势腮。所以最后一個(gè)客戶端連接需要等到30秒才能收到服務(wù)端的輸出联贩。我們稱這種服務(wù)器為迭代服務(wù)器,迭代服務(wù)器會(huì)依次處理客戶端的連接捎拯,只要當(dāng)前連接的任務(wù)沒有完成泪幌,服務(wù)器的進(jìn)程就會(huì)一直被占用师倔,直到任務(wù)完成后袖裕,服務(wù)器關(guān)閉這個(gè)socket鼠渺,釋放連接萝嘁。這顯然不是我們想要的暴拄,我們希望每一個(gè)Hello Wrold都在3秒鐘后馬上輸出啸臀。
并發(fā)服務(wù)器
當(dāng)一個(gè)服務(wù)處理客戶端請(qǐng)求需要花費(fèi)較長(zhǎng)時(shí)間烹俗,但是我們又不希望整個(gè)服務(wù)器被單個(gè)客戶端長(zhǎng)期占用窘奏,而是希望同時(shí)服務(wù)多個(gè)客戶禁荸。Unix中編寫并發(fā)服務(wù)器最簡(jiǎn)單的辦法就是fork一個(gè)子進(jìn)程來服務(wù)每個(gè)客戶右蒲。利用fork函數(shù)可以把處理客戶端請(qǐng)求的任務(wù)交接到子進(jìn)程,這樣就實(shí)現(xiàn)多進(jìn)程并發(fā)赶熟,我們可以寫出這樣服務(wù)器的輪廓
pid_t pid;
for( ; ; ){
struct sockaddr_in clnt_addr;/*只是聲明瑰妄,并沒有賦值*/
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if(clnt_sock == -1){
printf("appect error");
return -1;
}
/**
* 這一段直接fork一個(gè)子進(jìn)程
* 子進(jìn)程處理單獨(dú)處理完請(qǐng)求之后退出
*/
if( (pid = fork()) == 0 ){
close(server_sockfd);/*子進(jìn)程不需要監(jiān)聽,關(guān)閉*/
doit(clnt_sock);/*針對(duì)已連接的客戶端套接字進(jìn)行讀寫*/
close(clnt_sock);/*處理完畢映砖,關(guān)閉客戶端連接*/
exit(0);/*自覺退出*/
}
close(clnt_sock); /*連接已經(jīng)交由子進(jìn)程處理间坐,父進(jìn)程可以關(guān)閉客戶端連接了*/
/*close(server_sockfd);*/
}
其中,doit函數(shù)我們先不實(shí)現(xiàn)邑退,我們來看一下一個(gè)并發(fā)服務(wù)器處理一個(gè)客戶端連接的流程
- 服務(wù)器阻塞于accept調(diào)用且來自客戶的連接請(qǐng)求到達(dá)時(shí)的客戶端與服務(wù)器的狀態(tài)
- 從accept返回后竹宋,連接已經(jīng)在內(nèi)核中注冊(cè),并且新的套接口connfd被創(chuàng)建地技。這是一個(gè)已建起連接的套接口蜈七,可以進(jìn)行數(shù)據(jù)的讀寫。
3.并發(fā)服務(wù)器在調(diào)用fork之后莫矗,listenfd和connfd這兩個(gè)描述字在父進(jìn)程以及子進(jìn)程之間共享(實(shí)際為其中一份為copy)
- 接下來是由父進(jìn)程關(guān)閉已連接套接口(connfd)飒硅,由子進(jìn)程關(guān)閉監(jiān)聽套接口(listenfd)。然后由子進(jìn)程負(fù)責(zé)為客戶端提供服務(wù)
最終我們的并發(fā)服務(wù)器代碼為
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
void doit(int sockfd);
int main()
{
int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
pid_t pid;
if(server_sockfd == -1){
printf("socket error");
return -1;
}
struct sockaddr_in server_sockaddr;/*聲明一個(gè)變量趣苏,類型為協(xié)議地址類型*/
server_sockaddr.sin_family = AF_INET;/*使用IPv4協(xié)議*/
server_sockaddr.sin_port = htons(8887);/*監(jiān)聽8887端口*/
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*綁定本機(jī)IP,使用宏定義綁定*/
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
printf("bind error");
return -1;
}
if(listen(server_sockfd, 20) == -1){
printf("listen error");
return -1;
}
for( ; ; ){
struct sockaddr_in clnt_addr;/*只是聲明梯轻,并沒有賦值*/
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if(clnt_sock == -1){
printf("appect error");
return -1;
}
if( (pid = fork()) == 0 ){
close(server_sockfd);/*子進(jìn)程不需要監(jiān)聽食磕,關(guān)閉*/
doit(clnt_sock);/*針對(duì)已連接的客戶端套接字進(jìn)行讀寫*/
close(clnt_sock);/*處理完畢,關(guān)閉客戶端連接*/
exit(0);/*自覺退出*/
}
close(clnt_sock);
/*close(server_sockfd);*/
}
}
void doit(int sockfd){
char str[] = "Hello World";
sleep(3);//3秒之后再向客戶端發(fā)送數(shù)據(jù)
write(sockfd, str, sizeof(str));
}
這個(gè)時(shí)候再次利用shell并行執(zhí)行我們的客戶端喳挑,就會(huì)發(fā)現(xiàn)彬伦,所有的Hello World是同時(shí)輸出來的滔悉。這種服務(wù)器就可以做到快速同時(shí)處理多個(gè)客戶端連接。