用C編寫一個簡單服務(wù)器

前言

本文使用C語言編寫一個簡單服務(wù)器,旨在更好的理解服務(wù)端/客戶端程序,迭代服務(wù)器,并發(fā)服務(wù)器等概念,僅供學習參考。這篇文章的例子很簡單,就是當客戶端連接上服務(wù)端之后,服務(wù)端給出一個“Hello World”回應(yīng)。

C/S結(jié)構(gòu)流程圖

整個客戶端,服務(wù)端交互流程可以用下圖表示,服務(wù)端是優(yōu)先啟動進程并監(jiān)聽某一個端口,并且進程一直阻塞,直到有客戶端連接進來,才開始處理客戶端連接。

image

服務(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)用,它完成兩件事情:

  1. 當使用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)
  2. 這個函數(shù)規(guī)定內(nèi)核應(yīng)該為相應(yīng)套接字排隊的最大連接數(shù)

函數(shù)原型

/*失敗時返回-1*/
int listen(int sockfd, int backlog)

backlog參數(shù)的設(shè)定其實是表示兩個隊列的總和,這兩個隊列分別是

  1. 未完成連接隊列,在客戶端發(fā)送一個SYN直到三次握手完成,都是這個狀態(tài),SYN_RCVD狀態(tài)。
  2. 已完成連接隊列,這個表示三次握手完成的狀態(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ù)器處理一個客戶端連接的流程

  1. 服務(wù)器阻塞于accept調(diào)用且來自客戶的連接請求到達時的客戶端與服務(wù)器的狀態(tài)
image
  1. 從accept返回后,連接已經(jīng)在內(nèi)核中注冊,并且新的套接口connfd被創(chuàng)建。這是一個已建起連接的套接口,可以進行數(shù)據(jù)的讀寫。
image

3.并發(fā)服務(wù)器在調(diào)用fork之后,listenfd和connfd這兩個描述字在父進程以及子進程之間共享(實際為其中一份為copy)

image
  1. 接下來是由父進程關(guān)閉已連接套接口(connfd),由子進程關(guān)閉監(jiān)聽套接口(listenfd)。然后由子進程負責為客戶端提供服務(wù)
image

最終我們的并發(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ù)器就可以做到快速同時處理多個客戶端連接。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,586評論 19 139
  • 參考:http://www.2cto.com/net/201611/569006.html TCP HTTP UD...
    F麥子閱讀 3,069評論 0 14
  • 1、TCP狀態(tài)linux查看tcp的狀態(tài)命令:1)、netstat -nat 查看TCP各個狀態(tài)的數(shù)量2)、lso...
    北辰青閱讀 9,723評論 0 11
  • 最近在看《UNIX網(wǎng)絡(luò)編程 卷1》和《FREEBSD操作系統(tǒng)設(shè)計與實現(xiàn)》這兩本書,我重點關(guān)注了TCP協(xié)議相關(guān)的內(nèi)容...
    腩啵兔子閱讀 1,282評論 0 7
  • 總結(jié)一些常用的銜接詞,雖然不能涵蓋所,但如果把這十類詞的關(guān)系搞清楚,把列舉的具體銜接詞背誦并會用的話,就能把一篇文...
    張簡亦閱讀 8,900評論 0 1

友情鏈接更多精彩內(nèi)容