国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Linux 基于 TCP 協(xié)議的簡單服務(wù)器-客戶端應(yīng)用

這篇具有很好參考價(jià)值的文章主要介紹了Linux 基于 TCP 協(xié)議的簡單服務(wù)器-客戶端應(yīng)用。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

目錄

一、相關(guān)函數(shù)?

1、listen()

2、accept()

3、connect()?

4、兩種IP地址轉(zhuǎn)換方式?

5、TCP和UDP數(shù)據(jù)發(fā)送和接收函數(shù)對比

5、log.hpp自定義記錄日志

二、udp_server.hpp單進(jìn)程版本

三、tcp_server.cc

四、Telnet客戶端(代替tcp_client.cc)

五、多進(jìn)程實(shí)現(xiàn)udp_server.hpp

1、多進(jìn)程版本一

2、tcp_client.cc

3、多進(jìn)程版本二

六、多線程版本

七、線程池版本

tcp_server.hpp

ThreadPool代碼

lockGuard.hpp

log.hpp

thread.hpp

threadPool.hpp

八、實(shí)現(xiàn)回顯、字符轉(zhuǎn)換、在線字典查詢服務(wù)

tcp_server.hpp

?三個(gè)服務(wù)函數(shù)

TcpServer類

tcp_server.cc

tcp_client.cc

九、TCP協(xié)議通訊流程

1、服務(wù)器初始化

2、建立連接的過程(三次握手)

3、數(shù)據(jù)傳輸?shù)倪^程

4、斷開連接的過程(四次揮手)


一、相關(guān)函數(shù)?

1、listen()

int listen(int socket, int backlog);

只有對于TCP服務(wù)端套接字才需要調(diào)用此函數(shù),它使套接字進(jìn)入監(jiān)聽狀態(tài),等待客戶端的連接請求。參數(shù)含義如下:

成功監(jiān)聽后返回0,出錯(cuò)則返回非零錯(cuò)誤碼。

  • socket:要監(jiān)聽的服務(wù)器端套接字描述符。
  • backlog:指定同時(shí)可以排隊(duì)等待處理的最大連接數(shù)。超過這個(gè)數(shù)量的連接請求會(huì)被拒絕。

2、accept()

int accept(int socket, struct sockaddr* address, socklen_t* address_len);

也是只在TCP服務(wù)器端使用,用于接受一個(gè)客戶端的連接請求。參數(shù)含義如下:

成功接受一個(gè)連接請求后,accept()函數(shù)返回一個(gè)新的套接字描述符,這個(gè)描述符用于與該客戶端進(jìn)行通信。同時(shí),address參數(shù)所指向的結(jié)構(gòu)體會(huì)填充上客戶端的地址信息。

  • socket:已經(jīng)監(jiān)聽的服務(wù)器端套接字描述符。
  • address:用于存儲(chǔ)新連接客戶端的地址信息的sockaddr結(jié)構(gòu)體指針。
  • address_len:指向一個(gè)socklen_t變量的指針,用于記錄地址結(jié)構(gòu)體的實(shí)際大小,傳入時(shí)應(yīng)初始化為地址結(jié)構(gòu)體的大小,返回時(shí)會(huì)更新為實(shí)際填充的大小。

3、connect()?

TCP的connect函數(shù)是用于客戶端編程中的一個(gè)重要系統(tǒng)調(diào)用,它是TCP/IP協(xié)議棧的一部分,允許客戶端應(yīng)用程序建立與遠(yuǎn)程服務(wù)器的連接。在C語言或C++編程環(huán)境下,connect函數(shù)的基本原型如下:

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
  • sockfd:這是一個(gè)之前通過socket()函數(shù)創(chuàng)建并返回的套接字描述符,標(biāo)識(shí)著一個(gè)未連接的套接字。

  • serv_addr:這是一個(gè)指向sockaddr結(jié)構(gòu)體的指針,包含了遠(yuǎn)程服務(wù)器的地址信息,對于IPv4而言,通常會(huì)使用sockaddr_in結(jié)構(gòu)體,其中包括服務(wù)器的IP地址和端口號(hào)。

  • addrlen:這是serv_addr指向的地址結(jié)構(gòu)體的長度。

當(dāng)調(diào)用connect函數(shù)時(shí),TCP客戶端會(huì)執(zhí)行以下動(dòng)作:

  1. 發(fā)起連接請求connect函數(shù)會(huì)觸發(fā)TCP三次握手的過程,即客戶端發(fā)送一個(gè)SYN(同步)分節(jié)給服務(wù)器,請求建立連接。

  2. 等待響應(yīng):客戶端會(huì)等待服務(wù)器回應(yīng)SYN+ACK分節(jié),然后發(fā)送ACK(確認(rèn))分節(jié)作為響應(yīng)。

  3. 連接建立:一旦三次握手成功完成,連接就建立了,此時(shí)套接字的狀態(tài)轉(zhuǎn)變?yōu)?code>ESTABLISHED,客戶端可以在該套接字上進(jìn)行讀寫操作。

  4. 錯(cuò)誤處理:如果在一定時(shí)間內(nèi)沒有收到服務(wù)器的響應(yīng),或者由于其他原因無法建立連接(比如網(wǎng)絡(luò)問題、服務(wù)器拒絕連接等),connect函數(shù)會(huì)返回錯(cuò)誤,errno會(huì)被設(shè)置為相應(yīng)的錯(cuò)誤代碼,如ETIMEDOUT(超時(shí))、ECONNREFUSED(連接被拒絕)等。

例如,假設(shè)已經(jīng)有了一個(gè)未連接的套接字sockfd,并且有了服務(wù)器的地址信息serv_addr,可以通過以下方式調(diào)用connect函數(shù):

struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT_NUMBER);
inet_pton(AF_INET, SERVER_IP_ADDRESS, &serv_addr.sin_addr);

if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
    perror("connect error");
    // 錯(cuò)誤處理...
} else {
    // 連接成功,可以開始進(jìn)行數(shù)據(jù)交換
}

在這里,PORT_NUMBER是服務(wù)器監(jiān)聽的端口號(hào),SERVER_IP_ADDRESS是服務(wù)器的IP地址,通過inet_pton函數(shù)將IP地址字符串轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的形式存放在sin_addr中。成功連接后,應(yīng)用程序就可以通過write、read或其他IO函數(shù)與服務(wù)器進(jìn)行雙向數(shù)據(jù)傳輸。

4、兩種IP地址轉(zhuǎn)換方式?

在這段代碼中,TcpServer?類用于創(chuàng)建一個(gè) TCP 服務(wù)器,初始化時(shí)會(huì)綁定到特定的 IP 地址和端口上。

第一種方式:

// 默認(rèn)構(gòu)造函數(shù),若不傳入ip則默認(rèn)綁定所有網(wǎng)絡(luò)接口,“0.0.0.0”等于空字符“”
TcpServer(uint16_t port, std::string ip = "0.0.0.0") 
inet_pton(AF_INET, _ip.c_str(), &local.sin_addr);
  • inet_pton?是一個(gè)從點(diǎn)分十進(jìn)制格式的字符串轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序二進(jìn)制格式的函數(shù),這里它將?_ip?字符串轉(zhuǎn)換為?sockaddr_in?結(jié)構(gòu)體中的?sin_addr?成員。
  • 當(dāng)?_ip?為空字符串或默認(rèn)值 "0.0.0.0" 時(shí),服務(wù)器將會(huì)監(jiān)聽所有可用網(wǎng)絡(luò)接口。

第二種方式:

TcpServer(uint16_t port, std::string ip = "") // 若不傳入ip,則默認(rèn)為空字符串
inet_aton(_ip.c_str(), &local.sin_addr);
local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
  • inet_aton?和?inet_pton?功能類似,也是將點(diǎn)分十進(jìn)制的 IP 地址字符串轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的二進(jìn)制表示形式。
  • 如果?_ip?為空字符串,那么下面的條件語句會(huì)執(zhí)行:
    local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
    • 當(dāng)?_ip?為空時(shí),sin_addr.s_addr?被賦值為?INADDR_ANY,這同樣表示服務(wù)器應(yīng)監(jiān)聽所有可用網(wǎng)絡(luò)接口。

結(jié)論:兩種方式都可以實(shí)現(xiàn)當(dāng)未傳入 IP 地址參數(shù)時(shí),服務(wù)器監(jiān)聽所有網(wǎng)絡(luò)接口的目的。不過,在現(xiàn)代 C++ 編程實(shí)踐中,推薦使用?inet_pton?函數(shù),因?yàn)樗С?IPv6 地址,并且在一些平臺(tái)上兼容性更好。

5、TCP和UDP數(shù)據(jù)發(fā)送和接收函數(shù)對比

TCP

  • 數(shù)據(jù)發(fā)送:
    • write()?或?send()?函數(shù)用于在已建立連接的TCP套接字上發(fā)送數(shù)據(jù)。send()?可以帶有額外的標(biāo)志參數(shù),但對于大多數(shù)情況,write()?即可滿足需求。
  • 數(shù)據(jù)接收:
    • read()?或?recv()?函數(shù)用于在TCP套接字上接收數(shù)據(jù)。recv()?同樣可以攜帶標(biāo)志參數(shù),但通常情況下,read()?已足夠用于接收TCP數(shù)據(jù)流。

UDP

  • 數(shù)據(jù)發(fā)送:
    • 因?yàn)閁DP是無連接的協(xié)議,所以在發(fā)送數(shù)據(jù)時(shí)需要指定目的地址,因此使用?sendto()?函數(shù),它需要包含目標(biāo)IP地址和端口號(hào)的sockaddr結(jié)構(gòu)體作為參數(shù)。
  • 數(shù)據(jù)接收:
    • 對應(yīng)地,在接收UDP數(shù)據(jù)時(shí),不僅要接收數(shù)據(jù),還需要得到發(fā)送數(shù)據(jù)的源地址和端口號(hào),因此使用?recvfrom()?函數(shù),它不僅能返回接收到的數(shù)據(jù),還能填充提供給它的sockaddr結(jié)構(gòu)體。

5、log.hpp自定義記錄日志

#pragma once

#include <cstring>
#include <iostream>
#include <cstdio>
#include <ctime>
#include <cstdarg>

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"};

#define LOGFILE "./threadpool.log"

void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
    if (level == DEBUG)
        return;
#endif
    char stdBuffer[1024];
    time_t timestamp = time(nullptr);
    snprintf(stdBuffer, sizeof(stdBuffer), "[%s] [%d]", gLevelMap[level], timestamp);

    char logBuffer[1024];
    va_list args;
    va_start(args, format);
    vsnprintf(logBuffer, sizeof(logBuffer), format, args);
    va_end(args);

    FILE *fp = fopen("LOGFILE", "a+");
    fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    fclose(fp);
}

二、udp_server.hpp單進(jìn)程版本

TcpServer類實(shí)現(xiàn)了創(chuàng)建TCP服務(wù)器、監(jiān)聽客戶端連接、處理客戶端連接服務(wù)的基本功能。通過調(diào)用initServer()方法初始化服務(wù)器,然后調(diào)用start()方法開始監(jiān)聽和處理客戶端連接。當(dāng)有新客戶端連接時(shí),創(chuàng)建子進(jìn)程(或線程)處理與該客戶端的通信。

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include "log.hpp"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <cassert>

// 定義靜態(tài)函數(shù),用于處理客戶端連接的服務(wù)邏輯
static void service(int sock, const std::string &clientip, const uint16_t &clientport)
{
    char buffer[1024];
    while (true)
    {
        ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << clientip << ":" << clientport << "#" << buffer << std::endl;
        }
        else if (s == 0)
        {
            logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
            break;
        }
        else
        {
            logMessage(ERROR, "read socket error,%d:%s", errno, strerror(errno));
            break;
        }
        write(sock, buffer, strlen(buffer));
    }
}

// 定義TCP服務(wù)器類
class TcpServer
{
private:
    const static int gbacklog = 20; // 服務(wù)器監(jiān)聽隊(duì)列長度(最大掛起連接數(shù))

public:
    // 構(gòu)造函數(shù),接收服務(wù)器監(jiān)聽端口和可選的綁定IP地址
    TcpServer(uint16_t port, std::string ip = "")
        : listensock(-1), _port(port), _ip(ip)
    {}

    // 初始化服務(wù)器:創(chuàng)建套接字、綁定端口、監(jiān)聽連接
    void initServer()
    {
        listensock = socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建TCP套接字
        if (listensock < 0)
        {
            logMessage(FATAL, "%d%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success listensock: %d", listensock);

        struct sockaddr_in local; // 用于存儲(chǔ)服務(wù)器地址信息的結(jié)構(gòu)體
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET; // 設(shè)置協(xié)議族為IPv4
        local.sin_port = htons(_port); // 設(shè)置服務(wù)器監(jiān)聽端口,轉(zhuǎn)換為主機(jī)字節(jié)序
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str()); // 設(shè)置服務(wù)器綁定IP地址

        if (bind(listensock, (struct sockaddr *)&local, sizeof(local)) < 0) // 綁定套接字到指定地址和端口
        {
            logMessage(FATAL, "bind error,%d:%s", errno, strerror(errno));
            exit(3);
        }
        if (listen(listensock, gbacklog) < 0) // 開始監(jiān)聽客戶端連接,設(shè)置監(jiān)聽隊(duì)列長度
        {
            logMessage(FATAL, "listen error,%d:%s", errno, strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "init server success");
    }

    // 啟動(dòng)服務(wù)器:設(shè)置信號(hào)處理、循環(huán)接受客戶端連接并創(chuàng)建子進(jìn)程處理
    void start()
    {
        // 設(shè)置信號(hào)處理,忽略SIGCHLD信號(hào)以自動(dòng)回收子進(jìn)程資源
        signal(SIGCHLD, SIG_IGN);

        while (true)
        {
            struct sockaddr_in src; // 用于存儲(chǔ)客戶端地址信息的結(jié)構(gòu)體
            socklen_t len = sizeof src;
            int servicesock = accept(listensock, (struct sockaddr *)&src, &len); // 接受客戶端連接請求
            if (servicesock < 0)
            {
                logMessage(ERROR, "accept error,%d:%s", errno, strerror(errno));
                continue;
            }
            uint16_t client_port = ntohs(src.sin_port); // 獲取客戶端端口
            std::string client_ip = inet_ntoa(src.sin_addr); // 獲取客戶端IP地址

            logMessage(NORMAL, "link success servicesock: %d | %s : %d |\n", \
                        servicesock, client_ip.c_str(), client_port);
            
            service(servicesock, client_ip, client_port); // 子進(jìn)程處理客戶端連接
            close(servicesock); // 主進(jìn)程中關(guān)閉已接受的客戶端連接套接字            
        }
    }

    // 析構(gòu)函數(shù)
    ~TcpServer()
    {
    }

private:
    uint16_t _port; // 服務(wù)器監(jiān)聽端口
    std::string _ip; // 服務(wù)器綁定IP地址(可選)
    int listensock; // 服務(wù)器監(jiān)聽套接字
};

這段代碼定義了一個(gè)名為TcpServer的類,用于實(shí)現(xiàn)一個(gè)基礎(chǔ)的TCP服務(wù)器。該服務(wù)器具有以下功能:

  1. 構(gòu)造函數(shù)

    • TcpServer(uint16_t port, std::string ip = ""):初始化服務(wù)器對象,接收一個(gè)監(jiān)聽端口號(hào)port和一個(gè)可選的服務(wù)器綁定IP地址ip。默認(rèn)情況下,如果不提供IP地址,服務(wù)器將在所有可用網(wǎng)絡(luò)接口上監(jiān)聽。
  2. initServer()

    • 創(chuàng)建TCP套接字。
    • 使用struct sockaddr_in結(jié)構(gòu)體存儲(chǔ)服務(wù)器地址信息。
    • 綁定服務(wù)器套接字到指定的IP地址和端口號(hào)。
    • 開始監(jiān)聽客戶端的連接請求,并設(shè)置監(jiān)聽隊(duì)列的最大長度為gbacklog(默認(rèn)20)。
  3. service()靜態(tài)函數(shù)

    • 用于處理與單個(gè)客戶端的連接服務(wù)邏輯。
    • 通過read()函數(shù)讀取客戶端發(fā)送過來的數(shù)據(jù),然后回顯到控制臺(tái)。
    • 若讀取到的數(shù)據(jù)長度為0,表示客戶端關(guān)閉連接,服務(wù)器也結(jié)束對該客戶端的服務(wù)。
    • 若讀取過程中發(fā)生錯(cuò)誤,則記錄錯(cuò)誤并結(jié)束服務(wù)。
    • 使用write()函數(shù)將讀取到的數(shù)據(jù)回傳給客戶端。
  4. start()

    • 設(shè)置信號(hào)處理,忽略SIGCHLD信號(hào),這樣操作系統(tǒng)會(huì)在子進(jìn)程結(jié)束后自動(dòng)回收資源。
    • 服務(wù)器進(jìn)入無限循環(huán),不斷地通過accept()函數(shù)接受新的客戶端連接請求。
    • 當(dāng)接收到新的連接請求時(shí),獲取客戶端的IP地址和端口號(hào),并調(diào)用service()函數(shù)處理客戶端連接。
    • 處理完客戶端連接后,關(guān)閉已接受的客戶端連接套接字。
  5. 析構(gòu)函數(shù)

    • 類似于其他類的析構(gòu)函數(shù),~TcpServer()在此處沒有特別的操作,但在實(shí)際開發(fā)中可能需要關(guān)閉監(jiān)聽套接字或執(zhí)行其他清理工作。

三、tcp_server.cc

#include "tcp_server.hpp"
#include <memory>

// 定義展示程序用法的幫助函數(shù)
static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " port\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    // 檢查命令行參數(shù)數(shù)量是否為2(即程序名 + 監(jiān)聽端口號(hào))
    if (argc != 2)
    {
        usage(argv[0]); // 輸出程序使用說明
        exit(1); // 參數(shù)錯(cuò)誤,退出程序
    }

    // 從命令行參數(shù)中獲取監(jiān)聽端口號(hào)并轉(zhuǎn)換為整型數(shù)值
    uint16_t port = atoi(argv[1]);

    // 使用智能指針創(chuàng)建并管理TCP服務(wù)器實(shí)例
    std::unique_ptr<TcpServer> svr(new TcpServer(port));

    // 初始化服務(wù)器,包括創(chuàng)建套接字、綁定端口和開始監(jiān)聽客戶端連接
    svr->initServer();

    // 啟動(dòng)服務(wù)器,開始循環(huán)接受客戶端連接并創(chuàng)建子進(jìn)程處理
    svr->start();

    // 當(dāng)`svr`的作用域結(jié)束時(shí),智能指針會(huì)自動(dòng)釋放TCP服務(wù)器實(shí)例
    // 此時(shí)由于TCP服務(wù)器已經(jīng)進(jìn)入了無限循環(huán)的`start()`方法,程序不會(huì)立即結(jié)束
    // 而是在接收到終止信號(hào)(如Ctrl+C)或系統(tǒng)關(guān)閉時(shí),TCP服務(wù)器才會(huì)停止運(yùn)行

    // 返回0,表示程序正常退出
    return 0;
}

四、Telnet客戶端(代替tcp_client.cc)

在Linux CentOS環(huán)境下,telnet?是一個(gè)命令行工具,用于通過Telnet協(xié)議與遠(yuǎn)程主機(jī)上的服務(wù)進(jìn)行交互。Telnet最初設(shè)計(jì)用于遠(yuǎn)程登錄和命令行交互,但在現(xiàn)代環(huán)境中,由于其不提供加密保護(hù),通常被更安全的SSH(Secure Shell)協(xié)議所取代。盡管如此,Telnet在某些特定場景下(如測試、調(diào)試網(wǎng)絡(luò)服務(wù))因其簡單易用仍被臨時(shí)使用。

1. 安裝Telnet客戶端

在CentOS系統(tǒng)中,telnet客戶端可能未預(yù)裝。若要使用telnet,首先需要確保已經(jīng)安裝了該客戶端??梢酝ㄟ^以下命令安裝:

sudo yum install telnet

2. 使用telnet命令

基本語法如下:

telnet [options] host [port]
  • options:可選的命令行選項(xiàng),如?-l username?用于指定登錄用戶名。
  • host:遠(yuǎn)程主機(jī)的IP地址或域名,如?192.168.0.100?或?example.com。
  • port:可選的端口號(hào),用于指定遠(yuǎn)程主機(jī)上服務(wù)監(jiān)聽的端口。如果不指定,默認(rèn)為Telnet服務(wù)的標(biāo)準(zhǔn)端口?23

3. 示例:連接到遠(yuǎn)程主機(jī)

要連接到IP地址為192.168.0.100、端口為23的遠(yuǎn)程主機(jī),執(zhí)行:

telnet 192.168.0.100

或者連接到特定端口(如?8080)上的服務(wù):

telnet 192.168.0.100 8080

4. 交互過程

  • 成功連接后,將看到類似于以下的響應(yīng):

    Trying 192.168.0.100...
    Connected to 192.168.0.100.
    Escape character is '^]'.
  • 如果遠(yuǎn)程主機(jī)要求身份驗(yàn)證,可能需要輸入用戶名和密碼。這些憑據(jù)將以明文形式在網(wǎng)絡(luò)中傳輸。

  • 輸入用戶名和密碼后(如果有),將進(jìn)入遠(yuǎn)程主機(jī)的命令行環(huán)境,可以像在本地終端一樣輸入命令并觀察響應(yīng)。

  • 若要斷開連接,可以輸入命令?logout?或?quit,然后按回車。或者直接使用快捷鍵?Ctrl+](即按住Ctrl鍵同時(shí)按下右方括號(hào)]),接著輸入?q?并回車,快速退出Telnet客戶端。

5. 安全注意事項(xiàng)

由于Telnet不提供任何加密保護(hù),其明文傳輸特性使得用戶名、密碼以及整個(gè)會(huì)話內(nèi)容都容易被嗅探。在實(shí)際環(huán)境中,強(qiáng)烈建議使用更安全的替代方案,如SSH(Secure Shell),它提供了加密的遠(yuǎn)程登錄功能,能有效保護(hù)敏感信息的安全。如果必須使用Telnet,請確保僅在受信任的網(wǎng)絡(luò)環(huán)境中進(jìn)行,并且了解潛在的安全風(fēng)險(xiǎn)。

6. 其他實(shí)用操作

除了基本的遠(yuǎn)程登錄,telnet還可以用于簡單的網(wǎng)絡(luò)診斷,如測試某個(gè)端口是否開放。例如,要檢查遠(yuǎn)程主機(jī)example.com80端口是否開放,可以執(zhí)行:

telnet example.com 80

如果端口開放且服務(wù)響應(yīng),將看到類似以下的輸出(以HTTP服務(wù)為例):

Trying 93.184.216.34...
Connected to example.com.
Escape character is '^]'.

此時(shí),可以嘗試輸入HTTP請求(如GET / HTTP/1.1,然后回車兩次),觀察服務(wù)是否返回響應(yīng)。如果端口未開放或無服務(wù)響應(yīng),將看到類似“Connection refused”或“Timeout”的錯(cuò)誤消息。這種簡易的測試方法可以幫助初步判斷遠(yuǎn)程主機(jī)的網(wǎng)絡(luò)服務(wù)狀態(tài)。然而,對于專業(yè)的網(wǎng)絡(luò)診斷,建議使用更專業(yè)的工具,如nc(Netcat)或nmap。

五、多進(jìn)程實(shí)現(xiàn)udp_server.hpp

1、多進(jìn)程版本一

這個(gè)TcpServer類利用了fork()函數(shù)實(shí)現(xiàn)了多進(jìn)程方式處理并發(fā)客戶端連接,相比單進(jìn)程版本增加了并發(fā)能力和資源隔離性,但也引入了額外的系統(tǒng)調(diào)用開銷。

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include "log.hpp"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <cassert>

// 定義靜態(tài)函數(shù),用于處理客戶端連接的服務(wù)邏輯
static void service(int sock, const std::string &clientip, const uint16_t &clientport)
{
    char buffer[1024];
    while (true)
    {
        ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << clientip << ":" << clientport << "#" << buffer << std::endl;
        }
        else if (s == 0)
        {
            logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
            break;
        }
        else
        {
            logMessage(ERROR, "read socket error,%d:%s", errno, strerror(errno));
            break;
        }
        write(sock, buffer, strlen(buffer));
    }
}

// 定義TCP服務(wù)器類
class TcpServer
{
private:
    const static int gbacklog = 20; // 服務(wù)器監(jiān)聽隊(duì)列長度(最大掛起連接數(shù))

public:
    // 構(gòu)造函數(shù),接收服務(wù)器監(jiān)聽端口和可選的綁定IP地址
    TcpServer(uint16_t port, std::string ip = "")
        : listensock(-1), _port(port), _ip(ip)
    {}

    // 初始化服務(wù)器:創(chuàng)建套接字、綁定端口、監(jiān)聽連接
    void initServer()
    {
        listensock = socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建TCP套接字
        if (listensock < 0)
        {
            logMessage(FATAL, "%d%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success listensock: %d", listensock);

        struct sockaddr_in local; // 用于存儲(chǔ)服務(wù)器地址信息的結(jié)構(gòu)體
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET; // 設(shè)置協(xié)議族為IPv4
        local.sin_port = htons(_port); // 設(shè)置服務(wù)器監(jiān)聽端口,轉(zhuǎn)換為主機(jī)字節(jié)序
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str()); // 設(shè)置服務(wù)器綁定IP地址

        if (bind(listensock, (struct sockaddr *)&local, sizeof(local)) < 0) // 綁定套接字到指定地址和端口
        {
            logMessage(FATAL, "bind error,%d:%s", errno, strerror(errno));
            exit(3);
        }
        if (listen(listensock, gbacklog) < 0) // 開始監(jiān)聽客戶端連接,設(shè)置監(jiān)聽隊(duì)列長度
        {
            logMessage(FATAL, "listen error,%d:%s", errno, strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "init server success");
    }

    // 啟動(dòng)服務(wù)器:設(shè)置信號(hào)處理、循環(huán)接受客戶端連接并創(chuàng)建子進(jìn)程處理
    void start()
    {
        // 設(shè)置信號(hào)處理,忽略SIGCHLD信號(hào)以自動(dòng)回收子進(jìn)程資源
        signal(SIGCHLD, SIG_IGN);

        while (true)
        {
            struct sockaddr_in src; // 用于存儲(chǔ)客戶端地址信息的結(jié)構(gòu)體
            socklen_t len = sizeof src;
            int servicesock = accept(listensock, (struct sockaddr *)&src, &len); // 接受客戶端連接請求
            if (servicesock < 0)
            {
                logMessage(ERROR, "accept error,%d:%s", errno, strerror(errno));
                continue;
            }
            uint16_t client_port = ntohs(src.sin_port); // 獲取客戶端端口
            std::string client_ip = inet_ntoa(src.sin_addr); // 獲取客戶端IP地址

            logMessage(NORMAL, "link success servicesock: %d | %s : %d |\n", \
                        servicesock, client_ip.c_str(), client_port);
            
            // 多進(jìn)程處理客戶端連接
            pid_t id = fork(); // 創(chuàng)建子進(jìn)程
            assert(id != -1); // 斷言子進(jìn)程創(chuàng)建成功
            if (id == 0) // 子進(jìn)程
            {
                close(listensock); // 子進(jìn)程中關(guān)閉監(jiān)聽套接字
                service(servicesock, client_ip, client_port); // 子進(jìn)程處理客戶端連接
                exit(0); // 子進(jìn)程處理完客戶端連接后退出
            }

            close(servicesock); // 主進(jìn)程中關(guān)閉已接受的客戶端連接套接字
        }
    }

    // 析構(gòu)函數(shù)
    ~TcpServer()
    {
    }

private:
    uint16_t _port; // 服務(wù)器監(jiān)聽端口
    std::string _ip; // 服務(wù)器綁定IP地址(可選)
    int listensock; // 服務(wù)器監(jiān)聽套接字
};

這個(gè)TcpServer類相較于單進(jìn)程版本,主要區(qū)別在于如何處理每個(gè)客戶端連接。在這個(gè)多進(jìn)程版本中,服務(wù)器在接收到客戶端連接請求后,通過fork()系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程來處理每個(gè)客戶端連接。以下是不同之處的詳細(xì)說明:

  1. 啟動(dòng)服務(wù)器(start()方法)的變化

    • start()方法內(nèi),當(dāng)服務(wù)器通過accept()函數(shù)成功接受一個(gè)客戶端連接后,調(diào)用fork()創(chuàng)建一個(gè)子進(jìn)程。

    • 子進(jìn)程中:

      • 關(guān)閉監(jiān)聽套接字(listensock),因?yàn)樗鼉H用于監(jiān)聽新的連接請求,無需在處理現(xiàn)有客戶端連接的子進(jìn)程中保持打開。
      • 調(diào)用service()函數(shù)處理客戶端連接。
      • service()函數(shù)結(jié)束后,子進(jìn)程調(diào)用exit(0)退出,釋放資源。
    • 主進(jìn)程中:同樣關(guān)閉已接受的客戶端連接套接字,但在主進(jìn)程中這樣做是為了讓主進(jìn)程能夠繼續(xù)監(jiān)聽新的客戶端連接,而不是去處理已連接的客戶端通信。

  2. 多進(jìn)程處理客戶端連接的優(yōu)勢

    • 并發(fā)處理:父進(jìn)程可以繼續(xù)接受新的客戶端連接請求,而子進(jìn)程獨(dú)立處理已連接的客戶端,從而實(shí)現(xiàn)并發(fā)處理多個(gè)客戶端連接。
    • 資源隔離:每個(gè)客戶端連接都在各自的子進(jìn)程中處理,使得各個(gè)連接間的資源相互獨(dú)立,避免了共享資源的競爭問題。
  3. 注意點(diǎn):在實(shí)際部署中,頻繁創(chuàng)建和銷毀子進(jìn)程可能會(huì)帶來一定的開銷,尤其是當(dāng)客戶端連接數(shù)量很大時(shí)。在某些情況下,可能選擇多線程而非多進(jìn)程的方式來處理并發(fā)連接,這取決于具體應(yīng)用場景和性能要求。

  4. 在上述提供的TCP服務(wù)器類中,TcpServerstart方法中,在主進(jìn)程每次接受到客戶端連接請求并創(chuàng)建子進(jìn)程后,都會(huì)關(guān)閉已接受的客戶端連接套接字servicesock。這是因?yàn)橹鬟M(jìn)程并不需要處理與已連接客戶端的實(shí)際通信,這部分任務(wù)交由子進(jìn)程完成。

    主進(jìn)程關(guān)閉servicesock的原因:

    • 資源釋放:每個(gè)文件描述符都是系統(tǒng)資源的一部分。在主進(jìn)程關(guān)閉已接受的客戶端連接套接字后,可以釋放系統(tǒng)資源,以便主進(jìn)程可以繼續(xù)接受新的客戶端連接,而不會(huì)因?yàn)槲募枋龇谋M而導(dǎo)致無法創(chuàng)建新的連接。

    • 避免資源競爭:當(dāng)主進(jìn)程不關(guān)閉已連接的客戶端套接字時(shí),子進(jìn)程和主進(jìn)程之間會(huì)產(chǎn)生資源競爭,因?yàn)橥惶捉幼衷诟缸舆M(jìn)程中同時(shí)存在,會(huì)導(dǎo)致難以預(yù)料的行為。

使用telnet客戶端運(yùn)行示例:

Linux 基于 TCP 協(xié)議的簡單服務(wù)器-客戶端應(yīng)用,Linux,linux,運(yùn)維,網(wǎng)絡(luò),服務(wù)器,計(jì)算機(jī)網(wǎng)絡(luò)

2、tcp_client.cc

這段代碼是一個(gè)簡單的TCP客戶端程序,首先從命令行參數(shù)獲取服務(wù)器的IP地址和端口號(hào),然后創(chuàng)建一個(gè)TCP套接字并與服務(wù)器建立連接。接著,程序進(jìn)入一個(gè)無限循環(huán),循環(huán)中接收用戶輸入并通過套接字發(fā)送給服務(wù)器,并從服務(wù)器接收回顯的數(shù)據(jù)。如果在任何環(huán)節(jié)出現(xiàn)錯(cuò)誤(如創(chuàng)建套接字失敗、連接服務(wù)器失敗等),程序?qū)⒋蛴″e(cuò)誤信息并退出。

#include <iostream>
#include <string>
#include <unistd.h> // 提供Unix標(biāo)準(zhǔn)函數(shù),如close、read、write等
#include <sys/socket.h> // 提供創(chuàng)建、操作套接字的函數(shù)原型
#include <arpa/inet.h> // 提供IPv4地址轉(zhuǎn)換函數(shù),如inet_addr、htonl等
#include <netinet/in.h> // 提供Internet地址家族(AF_INET)相關(guān)的結(jié)構(gòu)和常量
#include <sys/types.h> // 提供通用的數(shù)據(jù)類型定義

// 使用示例:./tcp_client 目標(biāo)IP 目標(biāo)端口
void usage(std::string proc) 
{
    std::cout << "Usage: " << proc << " serverIp serverPort" << std::endl;
}

int main(int argc, char *argv[]) 
{
    // 檢查命令行參數(shù)個(gè)數(shù)是否正確(IP地址+端口號(hào))
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1); // 參數(shù)錯(cuò)誤,退出程序
    }

    // 從命令行參數(shù)中提取目標(biāo)服務(wù)器的IP地址和端口號(hào)
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]); // 將端口號(hào)字符串轉(zhuǎn)換為整數(shù)

    // 創(chuàng)建一個(gè)基于IPv4的TCP套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    
    // 檢查套接字創(chuàng)建是否成功
    if (sock < 0)
    {
        std::cerr << "socket create error" << std::endl;
        exit(2); // 套接字創(chuàng)建失敗,退出程序
    }

    // 初始化服務(wù)器地址結(jié)構(gòu)體
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server)); // 清零內(nèi)存區(qū)域
    server.sin_family = AF_INET; // 設(shè)置為IPv4協(xié)議
    server.sin_port = htons(serverport); // 將端口號(hào)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序
    server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 將IP地址字符串轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序
    
    // 嘗試連接服務(wù)器
    if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
    {
        std::cerr << "connect error" << std::endl;
        exit(3); // 連接服務(wù)器失敗,退出程序
    }

    // 進(jìn)入通信循環(huán),等待用戶輸入并向服務(wù)器發(fā)送數(shù)據(jù),接收并顯示服務(wù)器響應(yīng)
    while (true)
    {
        std::string line;
        std::cout << "請輸入# ";
        std::getline(std::cin, line); // 從標(biāo)準(zhǔn)輸入讀取一行文本
        send(sock, line.c_str(), line.size(), 0); // 發(fā)送消息至服務(wù)器
        
        // 準(zhǔn)備接收服務(wù)器響應(yīng)的緩沖區(qū)
        char buffer[1024];
        
        // 接收服務(wù)器數(shù)據(jù)
        ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
        
        // 如果接收到數(shù)據(jù)
        if (s > 0)
        {
            buffer[s]=0; // 在有效數(shù)據(jù)后面添加結(jié)束符,方便當(dāng)作字符串處理
            std::cout<<"server 回顯# "<<buffer<<std::endl; // 輸出服務(wù)器響應(yīng)
        }
    }

    // 主程序結(jié)束,返回0表示正常退出
    return 0;
}

3、多進(jìn)程版本二

class TcpServer
{
private:
    const static int gbacklog = 20; // 服務(wù)器監(jiān)聽隊(duì)列長度(最大掛起連接數(shù))

public:
    // 啟動(dòng)服務(wù)器:設(shè)置信號(hào)處理、循環(huán)接受客戶端連接并創(chuàng)建子進(jìn)程處理
    void start()
    {
        while (true)
        {
            struct sockaddr_in src; // 用于存儲(chǔ)客戶端地址信息的結(jié)構(gòu)體
            socklen_t len = sizeof src;
            int servicesock = accept(listensock, (struct sockaddr *)&src, &len); // 接受客戶端連接請求
            if (servicesock < 0)
            {
                logMessage(ERROR, "accept error,%d:%s", errno, strerror(errno));
                continue;
            }
            uint16_t client_port = ntohs(src.sin_port); // 獲取客戶端端口
            std::string client_ip = inet_ntoa(src.sin_addr); // 獲取客戶端IP地址

            logMessage(NORMAL, "link success servicesock: %d | %s : %d |\n", \
                        servicesock, client_ip.c_str(), client_port);
            
            // 多進(jìn)程v2
            pid_t id = fork();
            if (id == 0)
            {
                // 子進(jìn)程
                close(listensock);
                if (fork() > 0 )// 子進(jìn)程本身
                    exit(0); // 子進(jìn)程本身立即退出
                // 孫子進(jìn)程,孤兒進(jìn)程,OS領(lǐng)養(yǎng),OS在孤兒進(jìn)程退出的時(shí)候,由OS自動(dòng)回收孤兒進(jìn)程!
                service(servicesock, client_ip, client_port);
            }
            // 父進(jìn)程
            waitpid(id, nullptr, 0); // 不會(huì)阻塞!
            close(servicesock);
        }
    }
private:
    uint16_t _port; // 服務(wù)器監(jiān)聽端口
    std::string _ip; // 服務(wù)器綁定IP地址(可選)
    int listensock; // 服務(wù)器監(jiān)聽套接字
};

六、多線程版本

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include "log.hpp" // 自定義的日志記錄模塊
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <cassert>

// 靜態(tài)函數(shù),用于處理客戶端連接的服務(wù)邏輯
// 參數(shù):sock - 與客戶端建立連接的套接字
//       clientip - 客戶端IP地址字符串
//       clientport - 客戶端端口號(hào)
static void service(int sock, const std::string &clientip, const uint16_t &clientport)
{
    char buffer[1024]; // 緩沖區(qū),用于讀取和發(fā)送數(shù)據(jù)
    while (true)
    {
        ssize_t s = read(sock, buffer, sizeof(buffer) - 1); // 從客戶端接收數(shù)據(jù)
        if (s > 0)
        {
            buffer[s] = 0; // 添加字符串結(jié)束符
            std::cout << clientip << ":" << clientport << "#" << buffer << std::endl; // 輸出接收到的數(shù)據(jù)和客戶端信息
            write(sock, buffer, strlen(buffer)); // 將接收到的數(shù)據(jù)原樣發(fā)送回客戶端
        }
        else if (s == 0)
        {
            logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport); // 如果讀取到EOF,認(rèn)為客戶端已關(guān)閉連接
            break;
        }
        else
        {
            logMessage(ERROR, "read socket error,%d:%s", errno, strerror(errno)); // 若讀取發(fā)生錯(cuò)誤,記錄錯(cuò)誤信息并斷開連接
            break;
        }
    }
}

// 定義ThreadData類,用于傳遞給線程處理函數(shù)的參數(shù)
class ThreadData
{
public:
    int _sock; // 客戶端連接套接字
    std::string _ip; // 客戶端IP地址
    uint16_t _port; // 客戶端端口號(hào)
};

// TCP服務(wù)器類
class TcpServer
{
private:
    const static int gbacklog = 20; // 服務(wù)器監(jiān)聽隊(duì)列大小,表示能同時(shí)待處理的連接請求個(gè)數(shù)

    // 線程處理函數(shù),負(fù)責(zé)處理客戶端連接
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self()); // 確保線程結(jié)束后能夠被內(nèi)核回收資源
        ThreadData *td = static_cast<ThreadData *>(args);
        service(td->_sock, td->_ip, td->_port); // 調(diào)用service函數(shù)處理客戶端連接
        delete td; // 刪除ThreadData對象

        return nullptr;
    }

public:
    // 構(gòu)造函數(shù),接收服務(wù)器監(jiān)聽端口和可選的綁定IP地址
    TcpServer(uint16_t port, std::string ip = "")
        : listensock(-1), _port(port), _ip(ip)
    {}

    // 初始化服務(wù)器:創(chuàng)建套接字、綁定端口、監(jiān)聽連接
    void initServer()
    {
        listensock = socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建TCP套接字
        if (listensock < 0)
        {
            logMessage(FATAL, "%d%s", errno, strerror(errno)); // 記錄并輸出錯(cuò)誤信息
            exit(2); // 出錯(cuò)則退出程序
        }
        logMessage(NORMAL, "create socket success listensock: %d", listensock);

        struct sockaddr_in local; // 本地服務(wù)器地址結(jié)構(gòu)體
        memset(&local, 0, sizeof local); // 清零結(jié)構(gòu)體內(nèi)容
        local.sin_family = AF_INET; // 設(shè)置為IPv4協(xié)議
        local.sin_port = htons(_port); // 設(shè)置服務(wù)器監(jiān)聽端口(主機(jī)字節(jié)序)
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str()); // 設(shè)置服務(wù)器綁定IP地址(若為空則監(jiān)聽所有地址)

        if (bind(listensock, (struct sockaddr *)&local, sizeof(local)) < 0) // 綁定套接字到指定地址和端口
        {
            logMessage(FATAL, "bind error,%d:%s", errno, strerror(errno));
            exit(3);
        }
        if (listen(listensock, gbacklog) < 0) // 開始監(jiān)聽客戶端連接,設(shè)置監(jiān)聽隊(duì)列長度
        {
            logMessage(FATAL, "listen error,%d:%s", errno, strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "init server success"); // 初始化服務(wù)器成功
    }

    // 啟動(dòng)服務(wù)器:設(shè)置信號(hào)處理、循環(huán)接受客戶端連接并創(chuàng)建子線程處理
    void start()
    {
        // 設(shè)置信號(hào)處理,忽略SIGCHLD信號(hào)以自動(dòng)回收子進(jìn)程(這里是子線程)資源
        signal(SIGCHLD, SIG_IGN);

        while (true)
        {
            struct sockaddr_in src; // 客戶端地址結(jié)構(gòu)體
            socklen_t len = sizeof src; // 結(jié)構(gòu)體大小

            int servicesock = accept(listensock, (struct sockaddr *)&src, &len); // 接受客戶端連接請求
            if (servicesock < 0)
            {
                logMessage(ERROR, "accept error,%d:%s", errno, strerror(errno));
                continue;
            }
            uint16_t client_port = ntohs(src.sin_port); // 獲取客戶端端口(網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)為主機(jī)字節(jié)序)
            std::string client_ip = inet_ntoa(src.sin_addr); // 獲取客戶端IP地址

            logMessage(NORMAL, "link success servicesock: %d | %s : %d |\n", \
                        servicesock, client_ip.c_str(), client_port);

            // 多線程處理客戶端連接
            ThreadData *td = new ThreadData();
            td->_sock = servicesock;
            td->_ip = client_ip;
            td->_port = client_port;
            pthread_t tid;
            pthread_create(&tid, nullptr, threadRoutine, td); // 創(chuàng)建新線程處理客戶端連接

            // 注意:不應(yīng)該在這里關(guān)閉servicesock,否則新創(chuàng)建的線程將無法繼續(xù)通過該套接字與客戶端通信
        }
    }

    // 析構(gòu)函數(shù)
    ~TcpServer()
    {
        // 可在此處關(guān)閉監(jiān)聽套接字(如果需要的話),但通常在程序退出前由操作系統(tǒng)自動(dòng)關(guān)閉所有打開的文件描述符
    }

private:
    uint16_t _port; // 服務(wù)器監(jiān)聽端口
    std::string _ip; // 服務(wù)器綁定IP地址(可選)
    int listensock; // 服務(wù)器監(jiān)聽套接字
};

七、線程池版本

?tcp_server.hpp

代碼實(shí)現(xiàn)了一個(gè)可以并發(fā)處理多個(gè)客戶端連接的TCP服務(wù)器,通過線程池調(diào)度不同的客戶端連接任務(wù),每個(gè)任務(wù)都在獨(dú)立的線程中執(zhí)行service函數(shù)以處理客戶端的數(shù)據(jù)傳輸。服務(wù)器啟動(dòng)后,將在指定的端口上監(jiān)聽客戶端連接,并在接收到連接請求時(shí)創(chuàng)建新的線程處理連接,從而實(shí)現(xiàn)高效、并發(fā)的通信服務(wù)。?

#pragma once

#include <iostream>
#include <string>
#include <unordered_map>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <pthread.h>
#include <ctype.h>
#include "ThreadPool/log.hpp"
#include "ThreadPool/threadPool.hpp"
#include "ThreadPool/Task.hpp"

// 服務(wù)處理函數(shù),負(fù)責(zé)處理客戶端連接并回顯數(shù)據(jù)
static void service(int sock, const std::string &clientip,
                    const uint16_t &clientport, const std::string &thread_name)
{
    char buffer[1024];
    while (true)
    {
        // 讀取客戶端發(fā)送的數(shù)據(jù)
        ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            // 結(jié)束字符串
            buffer[s] = 0;
            // 打印客戶端信息及接收到的數(shù)據(jù)
            std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;
        }
        else if (s == 0) // 對端關(guān)閉連接
        {
            logMessage(NORMAL, "%s:%d shutdown, closing connection...", clientip.c_str(), clientport);
            break;
        }
        else
        {
            logMessage(ERROR, "Read socket error, %d:%s", errno, strerror(errno));
            break;
        }

        // 將接收到的數(shù)據(jù)回寫給客戶端
        write(sock, buffer, strlen(buffer));
    }
    // 關(guān)閉已斷開的客戶端連接
    close(sock);
}

// TCP服務(wù)器類
class TcpServer
{
private:
    static const int gbacklog = 20; // 用于listen的連接請求隊(duì)列長度

public:
    // 構(gòu)造函數(shù),初始化服務(wù)器監(jiān)聽端口與IP地址
    TcpServer(uint16_t port, std::string ip = "0.0.0.0")
        : _listensock(-1), _port(port), _ip(ip),
          _threadpool_ptr(ThreadPool<Task>::getThreadPool())
    {
    }

    // 初始化服務(wù)器:創(chuàng)建socket,綁定地址,并開始監(jiān)聽
    void initServer()
    {
        // 1. 創(chuàng)建socket
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            logMessage(FATAL, "Create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }

        // 2. 綁定socket到指定IP地址和端口
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &local.sin_addr);
        if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "Bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }

        // 3. 開始監(jiān)聽連接請求
        if (listen(_listensock, gbacklog) < 0)
        {
            logMessage(FATAL, "Listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "Init server success");
    }

    // 啟動(dòng)服務(wù)器主循環(huán),接受新連接并將它們分配給線程池
    void start()
    {
        // 忽略子進(jìn)程結(jié)束時(shí)產(chǎn)生的SIGCHLD信號(hào)(避免產(chǎn)生僵尸進(jìn)程)
        // signal(SIGCHLD, SIG_IGN);

        // 啟動(dòng)線程池
        _threadpool_ptr->run();

        // 主循環(huán)等待并處理新連接
        while (true)
        {
            // 4. 接受新的客戶端連接請求
            struct sockaddr_in src;
            socklen_t len = sizeof(src);
            int servicesock = accept(_listensock, (struct sockaddr *)&src, &len);
            if (servicesock < 0)
            {
                logMessage(ERROR, "Accept error, %d:%s", errno, strerror(errno));
                continue;
            }

            // 獲取客戶端信息
            uint16_t client_port = ntohs(src.sin_port);
            std::string client_ip = inet_ntoa(src.sin_addr);
            logMessage(NORMAL, "Link success, servicesock: %d | %s : %d |\n", servicesock, client_ip.c_str(), client_port);

            // 創(chuàng)建任務(wù)對象并將客戶端連接放入線程池執(zhí)行服務(wù)
            Task t(servicesock, client_ip, client_port, service);
            _threadpool_ptr->pushTask(t);
        }
    }

    // 析構(gòu)函數(shù)
    ~TcpServer() {}

private:
    uint16_t _port;             // 監(jiān)聽端口號(hào)
    std::string _ip;            // 監(jiān)聽IP地址
    int _listensock;             // 服務(wù)器監(jiān)聽套接字
    std::unique_ptr<ThreadPool<Task>> _threadpool_ptr; // 線程池指針
};
  1. 服務(wù)處理函數(shù) service()

    • service函數(shù)接收四個(gè)參數(shù):客戶端套接字描述符(int sock)、客戶端IP地址(const std::string &clientip)、客戶端端口號(hào)(const uint16_t &clientport)和線程名稱(const std::string &thread_name)。
    • 函數(shù)在一個(gè)無限循環(huán)中讀取客戶端發(fā)送的數(shù)據(jù),并將接收到的數(shù)據(jù)回顯給客戶端。
    • 當(dāng)讀取到0字節(jié)(s == 0)時(shí),表示客戶端已關(guān)閉連接,服務(wù)器也關(guān)閉連接并退出循環(huán)。
    • 若讀取過程中發(fā)生錯(cuò)誤,記錄錯(cuò)誤信息并退出循環(huán)。
    • 通過write函數(shù)將接收到的數(shù)據(jù)回寫給客戶端,確保數(shù)據(jù)雙向傳輸。
  2. TCP服務(wù)器類 TcpServer

    • 類內(nèi)定義了服務(wù)器監(jiān)聽套接字描述符_listensock、監(jiān)聽的端口號(hào)_port、監(jiān)聽的IP地址_ip和一個(gè)線程池_threadpool_ptr。
    • 構(gòu)造函數(shù)初始化服務(wù)器對象,接收端口號(hào)和可選的IP地址。
    • initServer方法負(fù)責(zé)初始化服務(wù)器,包括創(chuàng)建套接字、綁定地址和開始監(jiān)聽連接請求。
    • start方法啟動(dòng)服務(wù)器主循環(huán),首先啟動(dòng)線程池,然后不斷地等待并處理新的客戶端連接請求。當(dāng)有新的連接請求時(shí),通過accept函數(shù)接收連接,并創(chuàng)建一個(gè)Task對象,將客戶端連接信息和service函數(shù)封裝進(jìn)去,然后將任務(wù)推送到線程池中執(zhí)行。
    • 析構(gòu)函數(shù)確保在服務(wù)器實(shí)例銷毀時(shí)關(guān)閉監(jiān)聽套接字。

運(yùn)行結(jié)果:

Linux 基于 TCP 協(xié)議的簡單服務(wù)器-客戶端應(yīng)用,Linux,linux,運(yùn)維,網(wǎng)絡(luò),服務(wù)器,計(jì)算機(jī)網(wǎng)絡(luò)

ThreadPool代碼

lockGuard.hpp

#pragma once

#include <pthread.h>

class Mutex
{
public:
    Mutex(pthread_mutex_t *mtx) : pmtx_(mtx){};
    void lock() { pthread_mutex_lock(pmtx_); }
    void unlock() { pthread_mutex_unlock(pmtx_); }
    ~Mutex() {}

private:
    pthread_mutex_t *pmtx_;
};

class lockGuard
{
public:
    lockGuard(pthread_mutex_t *mtx) : mtx_(mtx)
    {
        mtx_.lock();
    }
    ~lockGuard()
    {
        mtx_.unlock();
    }

private:
    Mutex mtx_;
};

log.hpp

#pragma once

#include <cstring>
#include <iostream>
#include <cstdio>
#include <ctime>
#include <cstdarg>

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"};

// #define LOGFILE "./threadpool.log"

void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
    if (level == DEBUG)
        return;
#endif
    char stdBuffer[1024];
    time_t timestamp = time(nullptr);
    snprintf(stdBuffer, sizeof(stdBuffer), "[%s] [%d]", gLevelMap[level], timestamp);

    char logBuffer[1024];
    va_list args;
    va_start(args, format);
    vsnprintf(logBuffer, sizeof(logBuffer), format, args);
    va_end(args);

    // FILE *fp = fopen(LOGFILE, "a");
    printf("%s%s\n", stdBuffer, logBuffer);
    // fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    // fclose(fp);
}

thread.hpp

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstdio>

typedef void *(*fun_t)(void *);

class ThreadData
{
public:
    void *args_;
    std::string name_;
};

class Thread
{
public:
    Thread(int num, fun_t callback, void *args)
        : func_(callback)
    {
        char nameBuffer[64];
        snprintf(nameBuffer, sizeof(nameBuffer), "Thread-%d", num);
        name_ = nameBuffer;
        tdata_.args_ = args;
        tdata_.name_ = name_;
    }
    void start()
    {
        pthread_create(&tid_, nullptr, func_, (void *)&tdata_);
    }
    void join()
    {
        pthread_join(tid_, nullptr);
    }
    std::string name()
    {
        return name_;
    }
    ~Thread()
    {
    }

private:
    std::string name_;
    fun_t func_;
    ThreadData tdata_;
    pthread_t tid_;
};

threadPool.hpp

#pragma once
 
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unistd.h>
#include "thread.hpp"
#include "lockGuard.hpp"
#include "log.hpp"
 
// 定義默認(rèn)線程數(shù)量
const int g_thread_num = 5;
 
// 類模板ThreadPool,代表一個(gè)線程池,可以處理不同類型的任務(wù)(T)
template <class T>
class ThreadPool
{
public:
    // 獲取線程池內(nèi)部使用的互斥鎖
    pthread_mutex_t *getMutex()
    {
        return &lock;
    }
 
    // 判斷任務(wù)隊(duì)列是否為空
    bool isEmpty()
    {
        return task_queue_.empty();
    }
 
    // 線程等待條件變量
    void waitCond()
    {
        pthread_cond_wait(&cond, &lock);
    }
 
    // 從任務(wù)隊(duì)列中取出并移除一個(gè)任務(wù)
    T getTask()
    {
        T t = task_queue_.front();
        task_queue_.pop();
        return t;
    }
 
private:
    // ThreadPool構(gòu)造函數(shù),初始化線程池,創(chuàng)建指定數(shù)量的工作線程
    ThreadPool(int thread_num = g_thread_num) : num_(thread_num)
    {
        pthread_mutex_init(&lock, nullptr);
        pthread_cond_init(&cond, nullptr);
        for (int i = 1; i <= num_; i++)
        {
            threads_.push_back(new Thread(i, &ThreadPool::routine, this));
        }
    }
 
    // 刪除拷貝構(gòu)造函數(shù)和賦值操作符,避免線程池實(shí)例的拷貝
    ThreadPool(const ThreadPool<T> &other) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &other) = delete;
 
public:
    // 獲取線程池的單例實(shí)例
    static ThreadPool<T> *getThreadPool(int num = g_thread_num)
    {
        // 使用雙重檢查鎖定模式確保線程安全地初始化單例
        if (nullptr == thread_ptr) 
        {
            // 加鎖
            lockGuard lockguard(&mutex);
 
            // 如果在加鎖后仍然沒有初始化,則創(chuàng)建一個(gè)新的線程池實(shí)例
            if (nullptr == thread_ptr)
            {
                thread_ptr = new ThreadPool<T>(num);
            }
 
            // 不需要顯式解鎖,因?yàn)閘ockGuard會(huì)在作用域結(jié)束時(shí)自動(dòng)解鎖
        }
        return thread_ptr;
    }
 
    // 啟動(dòng)線程池中的所有工作線程
    void run()
    {
        for (auto &iter : threads_)
        {
            iter->start();
            // 記錄線程啟動(dòng)成功的日志消息
            logMessage(NORMAL, "%s %s", iter->name().c_str(), "啟動(dòng)成功");
        }
    }
 
    // 靜態(tài)方法,作為工作線程的執(zhí)行入口
    static void *routine(void *args)
    {
        // 解封裝傳入的參數(shù)
        ThreadData *td = (ThreadData *)args;
        ThreadPool<T> *tp = (ThreadPool<T> *)td->args_;
 
        // 工作線程循環(huán)執(zhí)行,直到收到終止信號(hào)
        while (true)
        {
            T task;
            
            // 上鎖,同步訪問任務(wù)隊(duì)列
            {
                lockGuard lockguard(tp->getMutex());
 
                // 等待非空任務(wù)到來
                while (tp->isEmpty())
                    tp->waitCond();
 
                // 從任務(wù)隊(duì)列中取出一個(gè)任務(wù)
                task = tp->getTask();
            }
 
            // 執(zhí)行任務(wù)
            task(td->name_);
 
            // 這里假設(shè)任務(wù)完成后會(huì)自動(dòng)重置循環(huán)條件,否則需要顯式判斷是否退出循環(huán)
        }
    }
 
    // 將新任務(wù)推送到線程池的任務(wù)隊(duì)列中
    void pushTask(const T &task)
    {
        // 加鎖,同步訪問任務(wù)隊(duì)列
        lockGuard lockguard(&lock);
 
        // 將任務(wù)放入隊(duì)列,并通知條件變量,有一個(gè)新的任務(wù)可被處理
        task_queue_.push(task);
        pthread_cond_signal(&cond);
    }
 
    // 線程池析構(gòu)函數(shù),清理所有線程資源
    ~ThreadPool()
    {
        // 確保所有工作線程完成其任務(wù)后再銷毀
        for (auto &iter : threads_)
        {
            iter->join();
            delete iter;
        }
 
        // 銷毀互斥鎖和條件變量
        pthread_mutex_destroy(&lock);
        pthread_cond_destroy(&cond);
    }
 
private:
    // 存儲(chǔ)工作線程實(shí)例的容器
    std::vector<Thread *> threads_;
    
    // 工作線程的數(shù)量
    int num_;
 
    // 任務(wù)隊(duì)列,用于存放待執(zhí)行的任務(wù)
    std::queue<T> task_queue_;
 
    // 單例實(shí)例指針
    static ThreadPool<T> *thread_ptr;
 
    // 用于保護(hù)線程池單例初始化的全局互斥鎖
    static pthread_mutex_t mutex;
 
    // 用于控制線程同步的互斥鎖
    pthread_mutex_t lock;
 
    // 條件變量,用于實(shí)現(xiàn)線程間的通信,如通知工作線程有新任務(wù)到來
    pthread_cond_t cond;
};
 
// 初始化靜態(tài)成員變量
template <typename T>
ThreadPool<T> *ThreadPool<T>::thread_ptr = nullptr;
 
template <typename T>
pthread_mutex_t ThreadPool<T>::mutex = PTHREAD_MUTEX_INITIALIZER;

八、實(shí)現(xiàn)回顯、字符轉(zhuǎn)換、在線字典查詢服務(wù)

tcp_server.hpp

?三個(gè)服務(wù)函數(shù)

// tcp_server.hpp
#pragma once

// 引入必要的頭文件,包括C++標(biāo)準(zhǔn)庫和POSIX網(wǎng)絡(luò)編程相關(guān)的頭文件
#include <iostream>
#include <string>
#include <unordered_map>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <pthread.h>
#include <ctype.h>

// 引入自定義的日志模塊和線程池模塊
#include "ThreadPool/log.hpp"
#include "ThreadPool/threadPool.hpp"
#include "ThreadPool/Task.hpp"

// 定義三個(gè)靜態(tài)服務(wù)函數(shù),分別實(shí)現(xiàn)不同的客戶端請求處理邏輯

// service函數(shù):實(shí)現(xiàn)回顯服務(wù),從客戶端接收數(shù)據(jù)并在控制臺(tái)打印,同時(shí)將接收到的數(shù)據(jù)原樣返回給客戶端
static void service(int sock, const std::string &clientip,
                    const uint16_t &clientport, const std::string &thread_name)
{
    char buffer[1024];
    while (true)
    {
        // 從客戶端讀取數(shù)據(jù)
        ssize_t bytesReceived = read(sock, buffer, sizeof(buffer) - 1);
        
        if (bytesReceived > 0)
        {
            // 結(jié)束字符串,便于打印
            buffer[bytesReceived] = 0;
            
            // 打印客戶端信息和接收到的消息
            std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;

            // 將接收到的消息原樣寫回給客戶端
            write(sock, buffer, bytesReceived);
        }
        else if (bytesReceived == 0) // 對端關(guān)閉連接
        {
            logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
            break;
        }
        else // 讀取數(shù)據(jù)出錯(cuò)
        {
            logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
            break;
        }
    }

    // 關(guān)閉與客戶端的連接
    close(sock);
}

// change函數(shù):實(shí)現(xiàn)字符轉(zhuǎn)換服務(wù),將客戶端發(fā)送的小寫字母轉(zhuǎn)換為大寫后返回
static void change(int sock, const std::string &clientip,
                   const uint16_t &clientport, const std::string &thread_name)
{
    char buffer[1024];
    ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    
    if (s > 0)
    {
        buffer[s] = 0;
        std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;

        // 轉(zhuǎn)換輸入字符串中小寫字母為大寫
        std::string convertedMessage;
        for (char *c = buffer; *c; ++c)
            convertedMessage.push_back(islower(*c) ? toupper(*c) : *c);

        // 將轉(zhuǎn)換后的消息寫回給客戶端
        write(sock, convertedMessage.c_str(), convertedMessage.size());
    }
    else if (s == 0)
    {
        logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
    }
    else
    {
        logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    }

    // 關(guān)閉與客戶端的連接
    close(sock);
}

// dictOnline函數(shù):實(shí)現(xiàn)在線字典查詢服務(wù),根據(jù)客戶端發(fā)送的單詞查詢預(yù)定義字典并返回結(jié)果
static void dictOnline(int sock, const std::string &clientip,
                       const uint16_t &clientport, const std::string &thread_name)
{
    char buffer[1024];
    static std::unordered_map<std::string, std::string> dictionary = {
        {"producer", "生產(chǎn)者"},
        {"consumer", "消費(fèi)者"},
        {"udp", "用戶數(shù)據(jù)報(bào)協(xié)議"},
        {"tcp", "傳輸控制協(xié)議"},
        {"http", "超文本傳輸協(xié)議"}
    };

    ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    if (s > 0)
    {
        buffer[s] = 0;
        std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;

        // 查找字典中是否存在該單詞及其對應(yīng)含義
        std::string response;
        auto it = dictionary.find(buffer);
        if (it == dictionary.end())
            response = "我不知道...";
        else
            response = it->second;

        // 將查詢結(jié)果寫回給客戶端
        write(sock, response.c_str(), response.size());
    }
    else if (s == 0)
    {
        logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
    }
    else
    {
        logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    }

    // 關(guān)閉與客戶端的連接
    close(sock);
}
  1. service函數(shù)

    • service函數(shù)負(fù)責(zé)實(shí)現(xiàn)最基礎(chǔ)的回顯服務(wù),即服務(wù)器接收到客戶端發(fā)送的數(shù)據(jù)后,原樣返回給客戶端。
    • 首先,函數(shù)通過read系統(tǒng)調(diào)用從給定的套接字sock中讀取客戶端發(fā)送的數(shù)據(jù),存儲(chǔ)在緩沖區(qū)buffer中。
    • 當(dāng)讀取到有效數(shù)據(jù)時(shí)(read返回值大于0),將在控制臺(tái)上打印客戶端的IP地址、端口號(hào)以及發(fā)送的消息,并將接收到的消息原樣通過write系統(tǒng)調(diào)用返回給客戶端。
    • read返回值為0,表示客戶端已經(jīng)關(guān)閉連接,服務(wù)端也會(huì)相應(yīng)地關(guān)閉連接。
    • 若出現(xiàn)讀取錯(cuò)誤(read返回負(fù)數(shù)),函數(shù)將記錄錯(cuò)誤日志,并關(guān)閉連接。
  2. change函數(shù)

    • change函數(shù)實(shí)現(xiàn)了一個(gè)簡單的字符轉(zhuǎn)換服務(wù),將客戶端發(fā)送的所有小寫字母轉(zhuǎn)換成大寫字母后再發(fā)送回去。
    • 讀取客戶端數(shù)據(jù)的過程與service函數(shù)相同,但在讀取之后,函數(shù)遍歷接收到的字符,利用islowertoupper函數(shù)將小寫字母轉(zhuǎn)換為大寫字母,然后構(gòu)建一個(gè)新的字符串convertedMessage
    • 最后,將轉(zhuǎn)換后的大寫字符串發(fā)送回給客戶端。
  3. dictOnline函數(shù)

    • dictOnline函數(shù)實(shí)現(xiàn)了在線字典查詢服務(wù),允許客戶端發(fā)送一個(gè)單詞請求,服務(wù)器在其內(nèi)部維護(hù)的一個(gè)預(yù)定義字典(這里是通過std::unordered_map實(shí)現(xiàn))中查找該單詞的含義。
    • 類似地,先通過read讀取客戶端發(fā)送的單詞。
    • 查找字典中是否存在該單詞,若存在,則將對應(yīng)的含義發(fā)送回給客戶端;若不存在,則返回一個(gè)默認(rèn)提示信息。
    • 注意這里字典是靜態(tài)局部變量,因此在整個(gè)函數(shù)生命周期內(nèi)只初始化一次,提高了效率。

TcpServer類

class TcpServer
{
private:
    // 設(shè)置服務(wù)器可掛起的最大連接數(shù)
    const static int gbacklog = 20;

public:
    // 構(gòu)造函數(shù),初始化服務(wù)器監(jiān)聽端口和IP地址,默認(rèn)監(jiān)聽所有網(wǎng)絡(luò)接口(0.0.0.0)
    TcpServer(uint16_t port, std::string ip = "0.0.0.0")
        : _listensock(-1), _port(port),
          _ip(ip), _threadpool_ptr(ThreadPool<Task>::getThreadPool())
    {
    }

    // 初始化服務(wù)器,包括創(chuàng)建socket、綁定端口/IP、設(shè)置監(jiān)聽
    void initServer()
    {
        // 1. 創(chuàng)建socket,AF_INET代表IPv4,SOCK_STREAM代表TCP協(xié)議
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            logMessage(FATAL, "Failed to create socket, error: %d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "Created socket successfully, listensock: %d", _listensock);

        // 2. 綁定socket到指定IP地址和端口
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &local.sin_addr);
        if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "Failed to bind socket, error: %d:%s", errno, strerror(errno));
            exit(3);
        }

        // 3. 設(shè)置監(jiān)聽,允許最多gbacklog個(gè)連接排隊(duì)
        if (listen(_listensock, gbacklog) < 0)
        {
            logMessage(FATAL, "Failed to listen on socket, error: %d:%s", errno, strerror(errno));
            exit(4);
        }

        logMessage(NORMAL, "Initialized server successfully");
    }

    // 啟動(dòng)服務(wù)器并開始接受客戶端連接
    void start()
    {
        // 啟動(dòng)線程池
        _threadpool_ptr->run();

        while (true)
        {
            // 4. 等待并接受來自客戶端的連接請求
            struct sockaddr_in src;
            socklen_t len = sizeof(src);
            int servicesock = accept(_listensock, (struct sockaddr *)&src, &len);
            if (servicesock < 0)
            {
                logMessage(ERROR, "Failed to accept connection, error: %d:%s", errno, strerror(errno));
                continue;
            }

            // 獲取已連接客戶端的IP地址和端口
            uint16_t client_port = ntohs(src.sin_port);
            std::string client_ip = inet_ntoa(src.sin_addr);
            logMessage(NORMAL, "Accepted connection, servicesock: %d | %s : %d |\n", servicesock, client_ip.c_str(), client_port);

            // 根據(jù)需求選擇不同的服務(wù)函數(shù),并將其封裝為Task對象,推送到線程池中處理
            Task t(servicesock, client_ip, client_port, dictOnline);
            _threadpool_ptr->pushTask(t);
        }
    }

    // 析構(gòu)函數(shù),確保資源正確釋放
    ~TcpServer() {}

private:
    // 服務(wù)器監(jiān)聽的端口號(hào)
    uint16_t _port;

    // 服務(wù)器監(jiān)聽的IP地址
    std::string _ip;

    // 服務(wù)器監(jiān)聽用的套接字描述符
    int _listensock;

    // 線程池實(shí)例,用于并發(fā)處理客戶端連接請求
    std::unique_ptr<ThreadPool<Task>> _threadpool_ptr;
};

tcp_server.cc

#include "tcp_server.hpp"
#include <memory>

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}

// ./tcp_server port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<TcpServer> svr(new TcpServer(port));
    svr->initServer();
    svr->start();
    return 0;
}

tcp_client.cc

tcp_client.cc?是一個(gè)簡單的TCP客戶端程序,它通過命令行參數(shù)獲取服務(wù)器的IP地址和端口號(hào),然后嘗試與服務(wù)器建立連接,并進(jìn)行交互。

// tcp_client.cc
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>

// 定義一個(gè)幫助函數(shù),用于輸出程序的使用說明
static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
              << std::endl;
}

// 主函數(shù),接收命令行參數(shù):服務(wù)器IP地址和端口號(hào)
int main(int argc, char *argv[])
{
    // 檢查命令行參數(shù)數(shù)量是否正確(應(yīng)為3個(gè),包括程序名本身)
    if (argc != 3)
    {
        // 如果參數(shù)數(shù)量不正確,則輸出使用說明并退出程序
        usage(argv[0]);
        exit(1);
    }

    // 獲取命令行參數(shù)中的服務(wù)器IP地址和端口號(hào)
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    // 客戶端狀態(tài)標(biāo)志,標(biāo)識(shí)當(dāng)前客戶端是否已連接至服務(wù)器
    bool alive = false;

    // 客戶端套接字描述符
    int sock = 0;

    // 用于暫存用戶輸入的行數(shù)據(jù)
    std::string line;

    // 主循環(huán),持續(xù)監(jiān)聽用戶輸入并與其進(jìn)行交互
    while (true)
    {
        // 如果當(dāng)前沒有與服務(wù)器建立連接,則嘗試創(chuàng)建并建立連接
        if (!alive)
        {
            // 創(chuàng)建一個(gè)AF_INET協(xié)議族下的SOCK_STREAM類型套接字(即TCP套接字)
            sock = socket(AF_INET, SOCK_STREAM, 0);
            if (sock < 0)
            {
                std::cerr << "socket error" << std::endl;
                exit(2);
            }

            // 客戶端無需bind到本地地址,操作系統(tǒng)會(huì)自動(dòng)為其分配一個(gè)可用的源端口
            // 準(zhǔn)備服務(wù)器的地址結(jié)構(gòu)體
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));

            // 設(shè)置地址族為IPv4,端口號(hào)轉(zhuǎn)換為主機(jī)字節(jié)序
            server.sin_family = AF_INET;
            server.sin_port = htons(serverport);

            // 將服務(wù)器IP地址轉(zhuǎn)換為二進(jìn)制格式
            server.sin_addr.s_addr = inet_addr(serverip.c_str());

            // 嘗試連接到服務(wù)器
            if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
            {
                std::cerr << "connect error" << std::endl;
                exit(3); 
            }

            // 輸出連接成功的提示
            std::cout << "connect success" << std::endl;

            // 設(shè)置alive標(biāo)志為真,表明已連接至服務(wù)器
            alive = true;
        }

        // 提示用戶輸入,并讀取一行
        std::cout << "請輸入# ";
        std::getline(std::cin, line);

        // 如果用戶輸入"quit",則跳出循環(huán),結(jié)束客戶端程序
        if (line == "quit")
            break;

        // 將用戶輸入的數(shù)據(jù)發(fā)送給服務(wù)器
        ssize_t s = send(sock, line.c_str(), line.size(), 0);
        if (s > 0)
        {
            // 接收服務(wù)器的回應(yīng)數(shù)據(jù)
            char buffer[1024];
            ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);

            // 如果接收到數(shù)據(jù)長度大于0,則輸出服務(wù)器的回顯內(nèi)容
            if (s > 0)
            {
                buffer[s] = 0;
                std::cout << "server 回顯# " << buffer << std::endl;
            }
            // 若接收的數(shù)據(jù)長度為0,通常意味著服務(wù)器關(guān)閉了連接,此時(shí)客戶端也需要關(guān)閉連接并重置alive標(biāo)志
            else if (s == 0)
            {
                alive = false;
                close(sock);
            }
        }
        // 發(fā)送數(shù)據(jù)失敗時(shí),同樣關(guān)閉連接并重置alive標(biāo)志
        else
        {
            alive = false;
            close(sock);
        }
    }

    // 關(guān)閉套接字并退出程序
    return 0;
}
  1. main函數(shù)入口

    • 檢查命令行參數(shù)的數(shù)量是否為3,如果不是則輸出幫助信息并退出程序。
    • 解析服務(wù)器的IP地址和端口號(hào)。
    • 進(jìn)入一個(gè)無限循環(huán),持續(xù)嘗試或保持與服務(wù)器的連接。
  2. 建立TCP連接

    • 如果當(dāng)前沒有活躍的連接(alive為false),則創(chuàng)建一個(gè)TCP套接字(socket),使用AF_INET表示IPv4協(xié)議,SOCK_STREAM表示使用TCP協(xié)議。
    • 不需要客戶端顯示地bind本地端口,操作系統(tǒng)會(huì)自動(dòng)分配一個(gè)可用端口。
    • 填充sockaddr_in結(jié)構(gòu)體,設(shè)置服務(wù)器的IP地址和端口號(hào)。
    • 使用connect函數(shù)嘗試連接服務(wù)器,如果連接成功,則設(shè)置alive為true,并輸出“connect success”。
  3. 用戶交互

    • 在循環(huán)中,提示用戶輸入消息,并通過getline讀取一行文本。
    • 如果用戶輸入的是"quit",跳出循環(huán),結(jié)束程序。
    • 發(fā)送用戶輸入的消息到服務(wù)器,使用send函數(shù)。
    • 接收服務(wù)器的回復(fù),使用recv函數(shù),并將接收到的數(shù)據(jù)打印出來作為服務(wù)器的回顯。
  4. 錯(cuò)誤處理與重連機(jī)制:若sendrecv過程中發(fā)生錯(cuò)誤,將alive設(shè)置為false,關(guān)閉套接字(close(sock)),進(jìn)入下一輪循環(huán)重新嘗試連接服務(wù)器。

通過這個(gè)TCP客戶端程序,用戶可以向指定服務(wù)器發(fā)送消息并接收服務(wù)器的回復(fù),直到用戶選擇退出程序。在每次交互中,客戶端都會(huì)檢查網(wǎng)絡(luò)連接的狀態(tài),確保在連接斷開時(shí)能夠嘗試重新連接。

九、TCP協(xié)議通訊流程

Linux 基于 TCP 協(xié)議的簡單服務(wù)器-客戶端應(yīng)用,Linux,linux,運(yùn)維,網(wǎng)絡(luò),服務(wù)器,計(jì)算機(jī)網(wǎng)絡(luò)

1、服務(wù)器初始化

  • 調(diào)用socket, 創(chuàng)建文件描述符;
  • 調(diào)用bind, 將當(dāng)前的文件描述符和ip/port綁定在一起; 如果這個(gè)端口已經(jīng)被其他進(jìn)程占用了, 就會(huì)bind失敗;
  • 調(diào)用listen, 聲明當(dāng)前這個(gè)文件描述符作為一個(gè)服務(wù)器的文件描述符, 為后面的accept做好準(zhǔn)備;
  • 調(diào)用accecpt, 并阻塞, 等待客戶端連接過來;

2、建立連接的過程(三次握手)

  • 創(chuàng)建套接字: 客戶端同樣通過socket()系統(tǒng)調(diào)用創(chuàng)建一個(gè)新的套接字,生成用于與服務(wù)器通信的文件描述符。

  • 發(fā)起連接請求: 調(diào)用connect()函數(shù),向服務(wù)器的IP地址和端口發(fā)起連接請求。此操作會(huì)引發(fā)TCP的“三次握手”過程:

  • 第一次握手: 客戶端發(fā)送一個(gè)帶有SYN(同步序列編號(hào))標(biāo)志的TCP報(bào)文段,該報(bào)文段包含一個(gè)隨機(jī)生成的初始序列號(hào)(ISN)。此時(shí),客戶端進(jìn)入SYN_SENT狀態(tài),等待服務(wù)器的確認(rèn)。

  • 第二次握手: 服務(wù)器接收到客戶端的SYN報(bào)文段后,回應(yīng)一個(gè)SYN-ACK(同步確認(rèn))報(bào)文段。該報(bào)文段不僅確認(rèn)了客戶端的SYN(設(shè)置ACK標(biāo)志),還包含了服務(wù)器自己的初始序列號(hào)。服務(wù)器進(jìn)入SYN_RCVD狀態(tài)。

  • 第三次握手: 客戶端收到服務(wù)器的SYN-ACK報(bào)文段后,發(fā)送一個(gè)ACK(確認(rèn))報(bào)文段,確認(rèn)服務(wù)器的SYN(設(shè)置ACK標(biāo)志并使用服務(wù)器的ISN+1作為確認(rèn)序列號(hào))。至此,客戶端和服務(wù)器雙方均確認(rèn)了對方的初始序列號(hào),連接建立成功,客戶端進(jìn)入ESTABLISHED狀態(tài),服務(wù)器也從SYN_RCVD狀態(tài)切換到ESTABLISHED狀態(tài)。文章來源地址http://www.zghlxwxcb.cn/news/detail-859476.html

經(jīng)過上述 三次握手過程,客戶端與服務(wù)器之間的TCP連接正式建立,雙方可以開始進(jìn)行雙向的數(shù)據(jù)傳輸。在整個(gè)過程中, connect()函數(shù)在客戶端一側(cè)會(huì)阻塞,直到三次握手完成或發(fā)生錯(cuò)誤。而在服務(wù)器一側(cè),通常通過 accept()函數(shù)阻塞等待客戶端的連接請求,并在接收到有效連接請求后返回一個(gè)新的已連接套接字供后續(xù)通信使用。

3、數(shù)據(jù)傳輸?shù)倪^程

  • 在數(shù)據(jù)傳輸過程中,TCP(Transmission Control Protocol)協(xié)議作為互聯(lián)網(wǎng)層與應(yīng)用層之間的關(guān)鍵橋梁,為網(wǎng)絡(luò)通信提供了可靠、有序且面向連接的全雙工服務(wù)。全雙工模式意味著,在同一條TCP連接上,通信雙方能夠在同一時(shí)刻獨(dú)立地進(jìn)行數(shù)據(jù)的發(fā)送與接收,猶如兩條并行的高速公路,使得信息能夠在雙向通道中同時(shí)流動(dòng),顯著提升了通信效率。
  • 當(dāng)服務(wù)器通過accept()系統(tǒng)調(diào)用成功接納一個(gè)客戶端的連接請求后,一個(gè)新的TCP連接便正式建立起來。此時(shí),服務(wù)器進(jìn)入待命狀態(tài),立即調(diào)用read()函數(shù)嘗試從該連接的socket中讀取數(shù)據(jù)。這個(gè)過程就如同守候在一個(gè)信息管道入口,若此時(shí)客戶端尚未發(fā)送任何數(shù)據(jù),服務(wù)器端的read()函數(shù)會(huì)暫時(shí)陷入阻塞狀態(tài),耐心等待數(shù)據(jù)流的到來。
  • 與此同時(shí),客戶端在連接建立后,開始執(zhí)行其業(yè)務(wù)邏輯,調(diào)用write()函數(shù)向服務(wù)器發(fā)送請求。這些請求數(shù)據(jù)沿著已建立的TCP連接,如同信使般穿越網(wǎng)絡(luò),準(zhǔn)確無誤地送達(dá)服務(wù)器端。服務(wù)器的read()函數(shù)感知到數(shù)據(jù)到達(dá),立即解除阻塞狀態(tài),從socket中取出客戶端的請求進(jìn)行處理。
  • 在服務(wù)器專心處理客戶端請求的同時(shí),客戶端并不閑著,它調(diào)用read()函數(shù)進(jìn)入阻塞狀態(tài),靜候服務(wù)器對請求的響應(yīng)。這種同步阻塞模式確保了客戶端能夠及時(shí)接收到服務(wù)器端的反饋,保持通信的連貫性。
  • 服務(wù)器完成請求處理后,通過write()函數(shù)將處理結(jié)果打包成數(shù)據(jù)包,沿原路返回給客戶端。如同投遞員將信件放入郵筒,這些結(jié)果數(shù)據(jù)被安全、高效地傳遞至客戶端的socket。發(fā)送完畢后,服務(wù)器再次調(diào)用read()函數(shù),重新進(jìn)入阻塞等待狀態(tài),準(zhǔn)備接收客戶端可能發(fā)出的下一條請求。

4、斷開連接的過程(四次揮手)

  • 當(dāng)客戶端完成所有業(yè)務(wù)交互,不再有新的請求需要發(fā)送時(shí),它會(huì)選擇主動(dòng)發(fā)起連接的關(guān)閉流程,即所謂的“斷開連接”??蛻舳送ㄟ^調(diào)用close()函數(shù),向服務(wù)器發(fā)送一個(gè)特殊的TCP控制報(bào)文——FIN(Finish,結(jié)束)標(biāo)志位被置為1的報(bào)文段。這標(biāo)志著客戶端已經(jīng)沒有數(shù)據(jù)要發(fā)送,期望結(jié)束該連接。這是斷開連接過程中的第一次“揮手”。
  • 服務(wù)器端在接收到客戶端發(fā)送的FIN報(bào)文后,立即做出響應(yīng)。它會(huì)向客戶端發(fā)送一個(gè)ACK(Acknowledgment,確認(rèn))報(bào)文段,確認(rèn)序號(hào)為收到的FIN報(bào)文的序號(hào)加1,表明已正確接收并理解了客戶端的斷開意圖。與此同時(shí),服務(wù)器端的read()函數(shù)會(huì)返回值0,這是一個(gè)重要信號(hào),提示應(yīng)用程序客戶端已關(guān)閉寫入,即不會(huì)再有新的數(shù)據(jù)到來。這是斷開連接過程中的第二次“揮手”。
  • 服務(wù)器端應(yīng)用程序在得知客戶端關(guān)閉連接后,通常會(huì)進(jìn)行必要的清理工作,如釋放資源、更新狀態(tài)等,然后調(diào)用自身的close()函數(shù),向客戶端發(fā)送一個(gè)FIN報(bào)文段,明確告知其服務(wù)器也已完成數(shù)據(jù)發(fā)送,希望關(guān)閉連接。這是斷開連接過程中的第三次“揮手”。
  • 最后,客戶端收到服務(wù)器發(fā)送的FIN報(bào)文后,同樣以一個(gè)ACK報(bào)文段作為回應(yīng),確認(rèn)序號(hào)為收到的FIN報(bào)文的序號(hào)加1,表示已知悉服務(wù)器關(guān)閉連接的信息。至此,雙方均確認(rèn)對方已無數(shù)據(jù)待發(fā)送,且都同意關(guān)閉連接,斷開連接的四次“揮手”過程宣告完成。這就是網(wǎng)絡(luò)通信中著名的“四次揮手”(Four-way Handshake)機(jī)制,它確保了TCP連接能夠有序、可靠地關(guān)閉,避免了數(shù)據(jù)丟失或混亂,保障了網(wǎng)絡(luò)環(huán)境的穩(wěn)定性和效率。

到了這里,關(guān)于Linux 基于 TCP 協(xié)議的簡單服務(wù)器-客戶端應(yīng)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • socket的使用 | TCP/IP協(xié)議下服務(wù)器與客戶端之間傳送數(shù)據(jù)

    socket的使用 | TCP/IP協(xié)議下服務(wù)器與客戶端之間傳送數(shù)據(jù)

    謹(jǐn)以此篇,記錄TCP編程,方便日后查閱筆記 注意:用BufferedWriter write完后,一定要flush;否則字符不會(huì)進(jìn)入流中。去看源碼可知:真正將字符寫入的不是write(),而是flush()。 服務(wù)器端代碼: 客戶端代碼: 運(yùn)行后結(jié)果: 服務(wù)器端: 客戶端: 參考資料: https://www.bilibili.com/vid

    2024年02月09日
    瀏覽(39)
  • 三菱FX5U modbus tcp協(xié)議 plc做服務(wù)器和客戶端案例程序

    三菱FX5U modbus tcp協(xié)議 plc做服務(wù)器和客戶端案例程序

    三菱FX5U ?modbus tcp協(xié)議 ?plc做服務(wù)器和客戶端案例程序,提供調(diào)試工具,程序注解,通訊協(xié)議功能的配置。 標(biāo)題:三菱FX5U PLC在Modbus TCP協(xié)議中充當(dāng)服務(wù)器和客戶端的案例程序及通信配置詳解 摘要:本文主要介紹了如何在三菱FX5U PLC上實(shí)現(xiàn)Modbus TCP協(xié)議的服務(wù)器和客戶端功能,并

    2024年04月17日
    瀏覽(107)
  • day-03 基于TCP的服務(wù)器端/客戶端

    day-03 基于TCP的服務(wù)器端/客戶端

    TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是兩種常見的傳輸層協(xié)議,用于在計(jì)算機(jī)網(wǎng)絡(luò)中提供可靠的數(shù)據(jù)傳輸。 1.TCP: 連接導(dǎo)向 :TCP是一種面向連接的協(xié)議,通信雙方在數(shù)據(jù)傳輸前需要先建立可靠的連接。 可靠性 :TCP提供可靠的數(shù)據(jù)傳輸,通過使用序列號(hào)、確

    2024年02月10日
    瀏覽(28)
  • TCP IP網(wǎng)絡(luò)編程(四) 基于TCP的服務(wù)器端、客戶端

    TCP IP網(wǎng)絡(luò)編程(四) 基于TCP的服務(wù)器端、客戶端

    TCP/IP協(xié)議棧 ? TCP/IP協(xié)議棧 TCP/IP協(xié)議棧共分為4層,可以理解為數(shù)據(jù)收發(fā)分成了4個(gè)層次化過程。 ? TCP協(xié)議棧 ? UDP協(xié)議棧 鏈路層 鏈路層是物理連接領(lǐng)域標(biāo)準(zhǔn)化的結(jié)果,也是最基本的領(lǐng)域,專門定義LAN、WAN、MAN等網(wǎng)絡(luò)標(biāo)準(zhǔn)。兩臺(tái)主機(jī)通過網(wǎng)絡(luò)進(jìn)行數(shù)據(jù)交換,這需要像下圖所示

    2024年01月16日
    瀏覽(18)
  • TCP IP網(wǎng)絡(luò)編程(五) 基于TCP的服務(wù)器端、客戶端 (補(bǔ)充)

    TCP IP網(wǎng)絡(luò)編程(五) 基于TCP的服務(wù)器端、客戶端 (補(bǔ)充)

    回聲客戶端出現(xiàn)的問題 在上一節(jié)基于TCP的服務(wù)器端、回聲客戶端中,存在問題: 如果數(shù)據(jù)太大,操作系統(tǒng)就有可能把數(shù)據(jù)分成多個(gè)數(shù)據(jù)包發(fā)送到客戶端,客戶端有可能在尚未收到全部數(shù)據(jù)包時(shí)就調(diào)用read函數(shù) 問題出在客戶端,而不是服務(wù)器端,先來對比一下客戶端與服務(wù)器端

    2024年02月09日
    瀏覽(37)
  • 《TCP/IP網(wǎng)絡(luò)編程》閱讀筆記--基于TCP的服務(wù)器端/客戶端

    《TCP/IP網(wǎng)絡(luò)編程》閱讀筆記--基于TCP的服務(wù)器端/客戶端

    目錄 1--TCP/IP協(xié)議棧 2--TCP服務(wù)器端默認(rèn)函數(shù)調(diào)用順序 3--TCP客戶端的默認(rèn)函數(shù)調(diào)用順序 4--Linux實(shí)現(xiàn)迭代回聲服務(wù)器端/客戶端 5--Windows實(shí)現(xiàn)迭代回聲服務(wù)器端/客戶端 6--TCP原理 7--Windows實(shí)現(xiàn)計(jì)算器服務(wù)器端/客戶端 ????????TCP/IP協(xié)議棧共分 4 層,可以理解為數(shù)據(jù)收發(fā)分成了 4 個(gè)層

    2024年02月10日
    瀏覽(29)
  • Linux下TCP網(wǎng)絡(luò)服務(wù)器與客戶端通信程序入門

    Linux下TCP網(wǎng)絡(luò)服務(wù)器與客戶端通信程序入門

    實(shí)現(xiàn)客戶端連接服務(wù)器,通過終端窗口發(fā)送信息給服務(wù)器端,服務(wù)器接收到信息后對信息數(shù)據(jù)進(jìn)行回傳,客戶端讀取回傳信息并返回。 服務(wù)器當(dāng)前IP地址要知道 建立socket 綁定本地IP地址并設(shè)置端口號(hào) 知道服務(wù)器的IP地址和端口號(hào) 然后進(jìn)行連接

    2024年02月14日
    瀏覽(33)
  • 一臺(tái)linux服務(wù)器最多能支持多少個(gè)TCP連接?(要區(qū)分客戶端還是服務(wù)端)

    參考大佬文章: 一臺(tái)機(jī)器最多能撐多少個(gè)TCP連接? 今天掰扯清楚! 這個(gè)問題要分場景,先說下結(jié)論: 客戶端: 最多支持TCP連接數(shù) = IP數(shù) * 端口數(shù) = IP數(shù) * 65535 ,其中的IP數(shù)是由于linux下可以配置多IP 服務(wù)端:取決于linux服務(wù)器的內(nèi)存大小, 內(nèi)存數(shù) / 靜默TCP連接所占大小 3.3k ,

    2024年02月09日
    瀏覽(28)
  • [TCP協(xié)議]基于TCP協(xié)議的字典服務(wù)器

    [TCP協(xié)議]基于TCP協(xié)議的字典服務(wù)器

    目錄 1.TCP協(xié)議簡介: 2.TCP協(xié)議在Java中封裝的類以及方法 3.字典服務(wù)器 3.1服務(wù)器代碼: 3.2客戶端代碼: TCP協(xié)議是一種有連接,面向字節(jié)流,全雙工,可靠的網(wǎng)絡(luò)通信協(xié)議 .它相對于UDP協(xié)議來說有以下幾點(diǎn)好處: 1.它是可靠傳輸,相比于UDP協(xié)議,傳輸?shù)臄?shù)據(jù)更加可靠.當(dāng)然這里的可靠是相對的

    2024年02月22日
    瀏覽(23)
  • 計(jì)算機(jī)網(wǎng)絡(luò)套接字編程實(shí)驗(yàn)-TCP多進(jìn)程并發(fā)服務(wù)器程序與單進(jìn)程客戶端程序(簡單回聲)

    1.實(shí)驗(yàn)系列 ·Linux NAP-Linux網(wǎng)絡(luò)應(yīng)用編程系列 2.實(shí)驗(yàn)?zāi)康?·理解多進(jìn)程(Multiprocess)相關(guān)基本概念,理解父子進(jìn)程之間的關(guān)系與差異,熟練掌握基于fork()的多進(jìn)程編程模式; ·理解僵尸進(jìn)程產(chǎn)生原理,能基于|sigaction()或signal(),使用waitpid()規(guī)避僵尸進(jìn)程產(chǎn)生; ·

    2024年02月12日
    瀏覽(36)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包