1. 程式人生 > >基於TCP協議實現Linux下客戶端與伺服器之間的通訊,實現多執行緒、多程序伺服器

基於TCP協議實現Linux下客戶端與伺服器之間的通訊,實現多執行緒、多程序伺服器

TCP是TCP/IP協議族中一個比較重要的協議,這是一種可靠、建立連結、面向位元組流的傳輸,工作在傳輸層。和TCP相對的不可靠、無連結、面向資料報的協議UDP,瞭解UDP客戶端與伺服器之間通訊請戳UDP協議實現的伺服器與客戶端通訊

TCP協議建立連線

首先我們通過一個大概的圖來了解。
建立
建立連線首先必須是伺服器啟動,這沒什麼好說的,伺服器為被動方,客戶端為主動方,當客戶端發起請求建立連線,伺服器被動接受,經過上圖三次握手建立連線,注意這三次連線都是在作業系統內部實現的。
那麼我們就來介紹建立連線的相關API

socket獲取通訊的檔案描述符。

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

埠號的繫結

#include <sys/socket.h>
int bind(int sock, const struct sockaddr* address, socklen_t address_len);

作為伺服器首先要進行監聽

#include <sys/socket.h>
int listen(int socket, int backog);

引數介紹:
socket: 為socket函式返回的檔案描述符
backlog: 建立連線過程中等待建立的請求個數
返回值:
成功返回0,失敗返回-1;

這裡我們再介紹這個backlog引數的含義:
這就相當與我們去銀行取錢,到了發現人比較多,這個時候就需要坐在凳子上等,那麼這裡的凳子就是backlog的含義,就是現在最大的等待處理的個數。

接受請求:

#include <sys.socket.h>
int accept(int socket,  struct sockaddr* address, socklen_t* address_len);

引數:
socket:檔案描述符
address:輸出型引數,用來接受對方的IP 埠號,是一個結構體。
address_len:是一個輸入輸出型引數,輸入進去為當前address的大小,輸出為實際的大小。
返回值:
返回值成功為一個檔案描述符,失敗為-1;

這裡要解釋一下,socket不是已經創建出一個檔案描述符,怎麼還有?
accept的檔案描述符,使用來直接進行資料的傳送與收取,處理請求。
前面socket建立的檔案描述符,在listen函式中用,是為了來接受請求.

舉個例子:
socket建立的檔案描述符,就像一個飯店,拉客人(listen函式)就是在外面進行監聽,是否有客人要來飯店,當listen接受到客人,就會帶到飯店,然後交給服務員(accept)來進行對客人吃飯請求的處理。這時候accpet就會是這個客人的請求的處理。

客戶端連線函式:

#include <sys/socket.h>
int connect(int socketfd, const struct sockaddr* addr, socklen_t addrlen);

引數:
socketfd: 檔案描述符;
addr :為要建立連線伺服器的IP、埠號、使用網路協議IPV4;
addrlen:addr結構體的大小;
引數:
建立成功返回0,失敗返回-1;

相關API介紹完我們開始實現一個簡單的客戶端與伺服器

我們實現最簡單的沒有業務處理邏輯的通訊,就把接受到的訊息,傳送給客戶端

伺服器

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>

void Response(int new_sock, struct sockaddr_in * client)
{
    while (1)
    {
        char buf[512] = {0};
        // 接受請求
        ssize_t rd = read(new_sock, buf, sizeof(buf)-1);
        if (rd < 0)
        {
            perror("read error\n");
            return;
        }
        if (rd == 0)
        {
            printf("read done!\n");
            return;
        }
        buf[rd] = '\0';
        // 處理請求
        printf("client say [%s:%d] # %s\n",inet_ntoa(client->sin_addr), ntohs(client->sin_port), buf);

        // response 響應
        write(new_sock, buf, strlen(buf));
    }
}

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        printf("usage:./server_tcp [ip] [port]");
        return 1;
    }

    int sock = socket(AF_INET, SOCK_STREAM, 0); // 檔案描述符
    if (sock < 0)
    {
        perror("sock error\n");
        return 2;
    }

    // 繫結埠號
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));
    int b = bind(sock, (struct sockaddr*)&server, sizeof(server));
    if (b < 0)
    {
        perror("bind error\n");
        return 3;
    }

    // 監聽
    int l = listen(sock, 3); // 最大的連線數量
    if (l < 0)
    {
        perror("listen error\n");
        return 4;
    }

    printf("bind and listen .... \n");

    // 接受請求處理
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int new_sock = accept(sock, (struct sockaddr*)&client, &len);
    Response(new_sock, &client); // 處理函式
    return 0;
}

客戶端:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        perror("usage:./client_tcp [ip] [port]");
        return 1;
    }

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("scoket error\n");
        return 2;
    }

    // 建立連線
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));
    socklen_t len = sizeof(server);
    int co = connect(sock, (struct sockaddr*)&server, len);
    if (co < 0)
    {
        perror("connect error\n");
        return 3;
    }

    while (1)
    {
        char buf[512] = {0};
        ssize_t rd = read(0, buf, sizeof(buf)-1);
        if (rd < 0)
        {
            perror("read error\n");
            return 4;
        }
        if (rd == 0)
        {
            printf("read done!\n");
            return 5;
        }
        buf[rd-1] = '\0';

        write(sock, buf, strlen(buf)); // 會阻塞的寫與讀

        ssize_t sockrd = read(sock, buf, sizeof(buf)-1);
        if (sockrd < 0)
        {
            perror("read error\n");
            return 4;
        }

        if (sockrd == 0)
        {
            printf("read done!\n");
            return 5;
        }

        buf[rd] = '\0';
        printf("server say # %s\n", buf);
    }
    return 0;
}

結果:
客戶端
客戶
伺服器
server

TCP協議的的斷開連線

建立連線需要三次互動,斷開連線需要四次互動。
我們用圖分析一下
斷開
當資料傳送完客戶端呼叫close 這時候就會發送 FIN 伺服器核心立刻恢復ACK 伺服器再呼叫close 傳送FIN 客戶端回覆ACK 進入等待。一段時間後退出。

基於TCP實現多執行緒、多程序伺服器

關於建立程序和建立執行緒
執行緒的建立
程序的建立