前言
本文使用C語言編寫一個簡單服務(wù)器,旨在更好的理解服務(wù)端/客戶端程序,迭代服務(wù)器,并發(fā)服務(wù)器等概念,僅供學習參考。這篇文章的例子很簡單,就是當客戶端連接上服務(wù)端之后,服務(wù)端給出一個“Hello World”回應(yīng)。
C/S結(jié)構(gòu)流程圖
整個客戶端,服務(wù)端交互流程可以用下圖表示,服務(wù)端是優(yōu)先啟動進程并監(jiān)聽某一個端口,并且進程一直阻塞,直到有客戶端連接進來,才開始處理客戶端連接。
服務(wù)端
通過流程圖可以看出,服務(wù)端涉及的Socket函數(shù)有socket, bind, listen, accept, read, write, close。使用這7個函數(shù)就可以編寫出一個簡易服務(wù)器。
socket函數(shù)
為了執(zhí)行網(wǎng)絡(luò)I/O,一個進程必須做的第一件事情就是創(chuàng)建一個socket函數(shù),函數(shù)原型
# family 表示協(xié)議族
# type 表示套接字類型
# protocol 表示傳輸協(xié)議
# 若成功返回非負描述符,若出錯返回-1
int socket(int family, int type, int protocol);
這個函數(shù)需要傳入?yún)f(xié)議族,套接字類型,傳輸層協(xié)議三個參數(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ù)報套接字 |
| 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ù)把一個本地協(xié)議地址賦予一個套接字,對于網(wǎng)際協(xié)議,協(xié)議地址就是IP加端口的組合,函數(shù)原型
# sockfd 初始化的套接字
# myaddr 協(xié)議地址
# addrlen 協(xié)議地址長度
# 若成功返回0 出錯返回-1
int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen)
注意,這個函數(shù)不是必須的,如果不使用這個函數(shù)綁定一個特定的端口,那么內(nèi)核會幫我們的套接字選擇一個臨時端口。作為服務(wù)器,一般不會這么做,需要指定特定的端口。
這個函數(shù)的第二個參數(shù)是協(xié)議地址,注意,這個協(xié)議地址已經(jīng)有定義好的結(jié)構(gòu)體,使用IPv4套接字結(jié)構(gòu)地址時候,地址結(jié)構(gòu)體定義如下
struct sockaddr_in {
uint8_t sin_len; /*結(jié)構(gòu)體長度*/
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;/*聲明一個變量,類型為協(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);/*綁定本機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)用,它完成兩件事情:
- 當使用socket函數(shù)創(chuàng)建一個套接字時,它被假設(shè)為一個主動套接字,也就是說,它是一個將發(fā)送connect發(fā)起連接的客戶端套接字。當調(diào)用listen函數(shù)之后,它被轉(zhuǎn)成一個被動套接字,只是內(nèi)核應(yīng)該接受連接請求。所以,調(diào)用listen之后套接字由CLOSED狀態(tài)轉(zhuǎn)到LISTEN狀態(tài)
- 這個函數(shù)規(guī)定內(nèi)核應(yīng)該為相應(yīng)套接字排隊的最大連接數(shù)
函數(shù)原型
/*失敗時返回-1*/
int listen(int sockfd, int backlog)
backlog參數(shù)的設(shè)定其實是表示兩個隊列的總和,這兩個隊列分別是
- 未完成連接隊列,在客戶端發(fā)送一個SYN直到三次握手完成,都是這個狀態(tài),SYN_RCVD狀態(tài)。
- 已完成連接隊列,這個表示三次握手完成的狀態(tài),ESTABLISHED狀態(tài)
因為我們是測試,這個值設(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;/*聲明一個變量,類型為協(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);/*綁定本機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)用,用于從已完成連接隊列的隊頭返回下一個已完成連接,如果已完成連接隊列為空,那么進程進入睡眠模式,函數(shù)原型
# sockdf 服務(wù)器套接字莫描述符
# cliaddr 已連接的客戶端協(xié)議地址
# addrlen 已連接的客戶端協(xié)議地址長度
# 成功返回非負描述符,出錯返回-1
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
當accept成功時,返回值是由內(nèi)核自動生成的全新描述符,代表與所返回的客戶端TCP連接。所以,在我們討論accept函數(shù)時,我們稱第一個參數(shù)為監(jiān)聽套接字,它的返回值是已連接套接字,一個服務(wù)器通常指創(chuàng)建一個監(jiān)聽套接字(通常是80端口),內(nèi)核為每個由服務(wù)器進程接受的客戶端連接創(chuàng)建一個已連接套接字,當服務(wù)器完成對某個給定的客戶端服務(wù)時,連接就會被關(guān)閉。
函數(shù)的第二個參數(shù)也是一個協(xié)議地址結(jié)構(gòu)體,這個結(jié)構(gòu)體和服務(wù)端協(xié)議地址是同一個結(jié)構(gòu)體。我們可以不關(guān)心客戶端的協(xié)議,直接傳空,我們關(guā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;/*聲明一個變量,類型為協(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);/*綁定本機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的傳輸是全雙工的,這時候客戶端和服務(wù)端都可以向?qū)Ψ桨l(fā)送數(shù)據(jù)。這里為了簡化,我們實現(xiàn)服務(wù)端發(fā)送“Hello World”給請求連接的客戶端。給客戶端發(fā)送數(shù)據(jù)很簡單,就是對返回的客戶端描述符進行寫操作就可以了
# sockfd socket文件描述符
# buf 文件內(nèi)容
# count 內(nèi)容長度
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;/*聲明一個變量,類型為協(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);/*綁定本機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ù)器進行通信,從流程圖上可以看出,需要使用socket, connect, write, read, close這5個函數(shù)
socket函數(shù)
客戶端要和服務(wù)端進行網(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é)議地址長度
int connect(int sockfd, const struct sockaddr * servaddr, socklen_t addrlen);
這個函數(shù)將觸發(fā)客戶端和服務(wù)端三次握手,函數(shù)的第一個參數(shù)sockfd表示客戶端返回的描述符,這里不需要調(diào)用bind函數(shù)綁定端口,系統(tǒng)會自動分配。函數(shù)的第二個參數(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);/*需要連接的遠程服務(wù)器端口*/
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*需要連接的遠程服務(wù)器IP*/
if(connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1){
printf("connect error");
return -1;
}
}
read函數(shù)
客戶端連接上服務(wù)器之后返回的是一個Socket文件描述符,既然是文件描述符,就可以通過簡單的read函數(shù)獲取網(wǎng)絡(luò)數(shù)據(jù),read函數(shù)原型
# sockdf 文件描述符
# buf 文件內(nèi)容存放地址
# count 內(nèi)容長度
ssize_t read(int sockfd,void *buf,size_t count)
這里我們讀取64個字節(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);/*需要連接的遠程服務(wù)器端口*/
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*需要連接的遠程服務(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);
}
運行客戶端服務(wù)端
將我們的服務(wù)端代碼保存為server.c,將我們的客戶端代碼保存為client.c。分別編譯客戶端和服務(wù)端代碼
[root@iZ940ofmvruZ socket]# gcc server.c -o server
[root@iZ940ofmvruZ socket]# gcc client.c -o client
然后會分別生成兩個可執(zhí)行文件server和client。在一個窗口中先執(zhí)行server
[root@iZ940ofmvruZ socket]# ./server
執(zhí)行server之后,我們知道accept函數(shù)會阻塞,所以程序一直運行,等待客戶端連接進來。這時候在另一個窗口執(zhí)行客戶端
[root@iZ940ofmvruZ socket]# ./client
Hello World
可以看到服務(wù)端給我們發(fā)送的Hello World,我們再回到服務(wù)端執(zhí)行窗口時,服務(wù)端也終止了進程,這個交互完成。然后我們會發(fā)現(xiàn)一個問題,服務(wù)端在提供完服務(wù)之后,它自己也關(guān)閉了,很顯然,我們希望服務(wù)器是一致運行的提供服務(wù),所以我們需要實現(xiàn)一直運行的服務(wù)器
不間斷提供服務(wù)
讓服務(wù)器一直運行的方式很簡單,就是死循環(huán)。循環(huán)的過程是accept一個客戶端連接,然后處理數(shù)據(jù)請求,最后關(guān)閉客戶端連接。注意,我們不能關(guān)閉服務(wù)端連接。所以我們改進這個程序,讓server不間斷的調(diào)用accept,因為accept總是從已連接的隊列中返回一個連接,然后處理。改進內(nèi)容片斷
/**
* 進入死循環(huán)調(diào)用accept,給每一個連接上來的客戶端發(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);*/
}
這樣修改之后,這個服務(wù)端程序就是一直不間斷提供服務(wù)了
并發(fā)服務(wù)器
迭代服務(wù)器
我們首先來看下什么是迭代服務(wù)器,因為我們剛才所寫的就是一個迭代服務(wù)器,思考一個問題,假如我們的服務(wù)器不是輸出Hello World這么簡單,而是需要經(jīng)過一系列復(fù)雜邏輯計算甚至網(wǎng)絡(luò)調(diào)用,那我們的程序執(zhí)行起來就不會怎么快了,為了模擬這種場景,我們在服務(wù)端程序中假如sleep函數(shù),我們讓程序睡眠3秒鐘,模擬服務(wù)器處理復(fù)雜邏輯時間
#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;/*聲明一個變量,類型為協(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);/*綁定本機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);*/
}
}
運行這個服務(wù)端程序之后,我們同時執(zhí)行10個客戶端
for(( i=0; i< 10; i++ ))
do
{
./client
}&
done
在shell中執(zhí)行這段代碼,你會發(fā)現(xiàn)每隔3秒鐘輸出一個Hello World。這是因為我們的服務(wù)端程序是阻塞的,在處理一個請求的同時,其他請求只能等。所以最后一個客戶端連接需要等到30秒才能收到服務(wù)端的輸出。我們稱這種服務(wù)器為迭代服務(wù)器,迭代服務(wù)器會依次處理客戶端的連接,只要當前連接的任務(wù)沒有完成,服務(wù)器的進程就會一直被占用,直到任務(wù)完成后,服務(wù)器關(guān)閉這個socket,釋放連接。這顯然不是我們想要的,我們希望每一個Hello Wrold都在3秒鐘后馬上輸出。
并發(fā)服務(wù)器
當一個服務(wù)處理客戶端請求需要花費較長時間,但是我們又不希望整個服務(wù)器被單個客戶端長期占用,而是希望同時服務(wù)多個客戶。Unix中編寫并發(fā)服務(wù)器最簡單的辦法就是fork一個子進程來服務(wù)每個客戶。利用fork函數(shù)可以把處理客戶端請求的任務(wù)交接到子進程,這樣就實現(xià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一個子進程
* 子進程處理單獨處理完請求之后退出
*/
if( (pid = fork()) == 0 ){
close(server_sockfd);/*子進程不需要監(jiān)聽,關(guān)閉*/
doit(clnt_sock);/*針對已連接的客戶端套接字進行讀寫*/
close(clnt_sock);/*處理完畢,關(guān)閉客戶端連接*/
exit(0);/*自覺退出*/
}
close(clnt_sock); /*連接已經(jīng)交由子進程處理,父進程可以關(guān)閉客戶端連接了*/
/*close(server_sockfd);*/
}
其中,doit函數(shù)我們先不實現(xiàn),我們來看一下一個并發(fā)服務(wù)器處理一個客戶端連接的流程
- 服務(wù)器阻塞于accept調(diào)用且來自客戶的連接請求到達時的客戶端與服務(wù)器的狀態(tài)
- 從accept返回后,連接已經(jīng)在內(nèi)核中注冊,并且新的套接口connfd被創(chuàng)建。這是一個已建起連接的套接口,可以進行數(shù)據(jù)的讀寫。
3.并發(fā)服務(wù)器在調(diào)用fork之后,listenfd和connfd這兩個描述字在父進程以及子進程之間共享(實際為其中一份為copy)
- 接下來是由父進程關(guān)閉已連接套接口(connfd),由子進程關(guān)閉監(jiān)聽套接口(listenfd)。然后由子進程負責為客戶端提供服務(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;/*聲明一個變量,類型為協(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);/*綁定本機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);/*子進程不需要監(jiān)聽,關(guān)閉*/
doit(clnt_sock);/*針對已連接的客戶端套接字進行讀寫*/
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));
}
這個時候再次利用shell并行執(zhí)行我們的客戶端,就會發(fā)現(xiàn),所有的Hello World是同時輸出來的。這種服務(wù)器就可以做到快速同時處理多個客戶端連接。