Linux編程: 10、線程池與初識網絡編程

今天我計劃通過一個小型項目,系統講解線程池與網絡編程的核心原理及實踐。項目將圍繞 “利用線程池實現高并發網絡通信” 這一核心需求展開,具體設計如下:

  • 為保證線程安全,線程池采用單例模式設計,確保全局唯一實例避免資源競爭;
  • 技術棧采用 C/C++ 混合編程:網絡通信、線程管理、同步鎖等底層操作直接調用 Linux 系統接口(貼合 Linux 編程的 C 語言風格),其他業務邏輯及封裝層則使用 C++ 實現,兼顧底層效率與代碼的可維護性。

一、什么是線程池

線程池是一種線程管理機制,簡單來說,它是一組預先創建好的線程的集合,這些線程在程序啟動時或需要時被初始化并待命,當有任務到來時,線程池會分配一個空閑線程來處理任務,任務完成后線程不會被銷毀,而是回到線程池等待下一個任務。

它的核心作用是避免頻繁創建和銷毀線程帶來的性能開銷。因為線程的創建、切換、銷毀都需要操作系統內核進行資源調度,在高并發場景下(比如大量網絡請求、頻繁的任務處理),如果每次處理任務都新建線程,會導致系統資源(CPU、內存)被大量消耗,甚至引發系統不穩定。

線程池的主要組成部分通常包括:

  • 線程隊列:存儲所有等待或正在運行的線程
  • 任務隊列:存放待處理的任務,當沒有空閑線程時,新任務會暫時進入隊列等待
  • 管理機制:負責線程的創建、回收、任務分配以及線程池大小的動態調整(可選)

在實際應用中,線程池可以高效處理大量并發連接:當新的網絡請求到達時,無需為每個請求單獨創建線程,而是直接交給線程池中的空閑線程處理,既保證了響應速度,又避免了資源浪費。

?

1. 線程池基礎配置

圖中 “線程池” 里標注了線程數量:3,意思是線程池預先創建并維護著 3 個空閑線程,隨時準備處理任務。

2. 任務提交與分配

右側的任務 1、任務 2、任務 3,代表 3 個待處理的任務。因為線程池有 3 個空閑線程,所以這 3 個任務會直接分配給線程池里的空閑線程,同時開始執行。

3. 任務排隊等待

任務 4到來時,線程池里的 3 個線程已經被前面的任務占用了(都在忙)。此時,任務 4 會進入阻塞狀態,被放進線程池的 “任務隊列” 里,等待有線程處理完任務后,再重新變成空閑線程來執行它


二、項目開始前的配置文件

我這里用的編輯器是vscode,調試器是lldb,lsp是clangd,操作系統是centos10,也用了cmake,當然,你們也可以用其它的編譯環境

1、launch.json

{"version": "0.2.0","configurations": [{"type": "lldb","request": "launch","name": "Debug","program": "${workspaceFolder}/bin/server",  "args": [],"cwd": "${workspaceFolder}","internalConsoleOptions": "neverOpen","console": "integratedTerminal"}]
}

2、.clang-format

BasedOnStyle: LLVM
AccessModifierOffset: -2       # 訪問修飾符(public/private)縮進減少 2 格
AlignAfterOpenBracket: Align   # 開括號后的內容對齊
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AllowShortFunctionsOnASingleLine: Empty  # 空函數允許單行
BraceWrapping:AfterClass: trueAfterControlStatement: falseAfterEnum: trueAfterFunction: trueAfterNamespace: falseAfterStruct: trueAfterUnion: trueBeforeCatch: trueBeforeElse: trueIndentBraces: falseSplitEmptyFunction: trueSplitEmptyRecord: trueSplitEmptyNamespace: true
BreakBeforeBraces: Custom
BreakConstructorInitializers: BeforeComma
ColumnLimit: 80
ConstructorInitializerAllOnOneLineOrOnePerLine: true
Cpp11BracedListStyle: true
DerivePointerAlignment: false
IndentCaseLabels: true
IndentWidth: 4
PointerAlignment: Left
SpaceBeforeAssignmentOperators: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInParentheses: false
Standard: Cpp11
TabWidth: 4
UseTab: Never

3、CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(server)set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 輸出目錄
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)# 頭文件路徑
include_directories(${PROJECT_SOURCE_DIR}/include)file(GLOB_RECURSE SOURCES "${PROJECT_SOURCE_DIR}/src/*.*")# 可執行文件
add_executable(server ${SOURCES} main.cpp)# 鏈接線程庫(必須!)
target_link_libraries(server pthread)

4、項目結構


三、實現單例線程池

1、任務的基類:Task.h

#ifndef TASK_H
#define TASK_Hclass Task
{
public:virtual ~Task() = default;virtual void run() = 0;
};#endif  // !TASK_H

2、線程池的相關接口:ThreadPool.h

#ifndef THREAD_POOL_H
#define THREAD_POOL_H#include "Task.h"
#include <atomic>
#include <cstddef>
#include <cstdio>
#include <pthread.h>
#include <queue>
#include <vector>class ThreadPool
{public:ThreadPool(const ThreadPool&)           = delete;ThreadPool operator=(const ThreadPool&) = delete;~ThreadPool();static ThreadPool& getInstance();// 添加任務到線程池void addTask(Task* task);private:ThreadPool(size_t pool_size = 3);// 工作線程函數static void* worker(void* arg);// 工作循環函數void work();// 開始\停止void start();void stop();private:size_t                 _pool_size;  // 線程池數量std::vector<pthread_t> _threads;    // 線程數組std::queue<Task*>      _tasks;      // 任務隊列pthread_cond_t         _cond;       // 條件變量pthread_mutex_t        _mutex;      // 鎖std::atomic<bool>      _is_stop;    // 是否暫停
};#endif  // !THREAD_POOL_H

3、構造函數與獲取單例對象

ThreadPool::ThreadPool(size_t pool_size): _pool_size(pool_size), _is_stop(true)
{// 初始化互斥量和條件變量pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);// 線程池開始運行start();
}ThreadPool& ThreadPool::getInstance() {static ThreadPool instance;return instance;
}
  1. 構造函數?ThreadPool::ThreadPool(size_t pool_size)

    • 初始化列表中設置了線程池大小_pool_size和停止標志_is_stop(初始為true表示未運行)
    • 調用pthread_mutex_initpthread_cond_init初始化了互斥鎖和條件變量(線程同步的核心工具)
    • 最后調用start()方法啟動線程池(實際創建并啟動工作線程)
  2. 單例獲取函數?ThreadPool::getInstance()

    • 使用 C++11 的static局部變量特性實現單例模式
    • 局部靜態變量instance會在第一次調用時初始化,且保證線程安全
    • 每次調用都返回同一個實例的引用,確保整個程序中只有一個線程池實例

4、工作線程函數與工作循環函數

void* ThreadPool::worker(void* arg) {auto* pool = static_cast<ThreadPool*>(arg);pool->work(); return nullptr;
}void ThreadPool::work()
{while(!_is_stop) {// 加鎖pthread_mutex_lock(&_mutex);// 只有運行時且任務隊列為空時才會等待while(!_is_stop && _tasks.empty()) {pthread_cond_wait(&_cond, &_mutex);}// 運行時若停止了,則退出if(_is_stop) {pthread_mutex_unlock(&_mutex);break;}auto* task = _tasks.front();_tasks.pop();pthread_mutex_unlock(&_mutex);if(task) {task->run();delete task;}}
}
  1. worker 函數
    這是線程的入口函數(符合 pthread 庫對線程函數的要求),作用是:

    • 通過static_cast將傳入的void*參數轉換為線程池實例指針
    • 調用線程池的work()方法,讓線程進入實際的任務處理循環

    它相當于一個適配層,將 pthread 庫的 C 風格函數接口與 C++ 的類方法銜接起來。

  2. work 函數
    這是線程的核心工作循環,實現了 "等待 - 取任務 - 執行" 的邏輯:

    • 循環判斷:通過!_is_stop控制線程是否退出
    • 加鎖保護:操作任務隊列前先加互斥鎖_mutex,保證線程安全
    • 等待機制:當任務隊列為空且線程池未停止時,通過pthread_cond_wait讓線程進入等待狀態(釋放鎖并阻塞,直到被喚醒)
    • 退出檢查:被喚醒后先判斷是否需要停止,若需停止則解鎖并退出循環
    • 處理任務:從隊列取第一個任務,解鎖后執行任務的run()方法,完成后釋放任務對象
    • 線程復用:任務執行完后回到循環開頭,繼續等待新任務,實現線程的復用

5、線程池開啟函數

void ThreadPool::start()
{// 如果已經是啟動狀態]if(!_is_stop) {return;}_is_stop = false;_threads.reserve(_pool_size);for(size_t i = 0; i < _pool_size; i++) {pthread_t pth;if(pthread_create(&pth, nullptr,worker, this) != 0){perror("create thread failed");_is_stop = true;return;}_threads.push_back(pth);}
}
  1. 啟動狀態檢查
    首先判斷_is_stop標志,如果線程池已經處于運行狀態(_is_stopfalse),則直接返回,避免重復啟動。

  2. 初始化準備
    _is_stop設為false(標記線程池進入運行狀態),并通過_threads.reserve(_pool_size)預先為存儲線程 ID 的容器分配內存,提升后續插入效率。

  3. 創建工作線程
    循環?_pool_size?次(線程池預設的線程數量),每次調用pthread_create創建一個線程:

    • 線程入口函數為worker(之前實現的線程工作函數)
    • 傳入this指針作為當前線程池實例指針,讓工作線程能訪問線程池的任務隊列等資源
  4. 錯誤處理
    若線程創建失敗(pthread_create返回非 0),則通過perror打印錯誤信息,將_is_stop重置為true(標記線程池停止),并退出函數。

  5. 記錄線程 ID
    成功創建的線程 ID(pth)會被存入_threads容器,便于后續管理(如停止線程池時回收線程)。

6、線程池停止函數

void ThreadPool::stop()
{// 如果已停止if(_is_stop) return;_is_stop = true;pthread_cond_broadcast(&_cond);// 等待所有線程結束for(pthread_t& pth : _threads) {pthread_join(pth, nullptr);}_threads.clear();// 清空任務隊列pthread_mutex_lock(&_mutex);while (!_tasks.empty()){auto* task = _tasks.front();_tasks.pop();if(task) {delete task;}}pthread_mutex_unlock(&_mutex);
}
  1. 停止狀態檢查
    首先判斷_is_stop標志,如果線程池已經處于停止狀態,則直接返回,避免重復停止操作。

  2. 觸發停止機制

    • _is_stop設為true(標記線程池進入停止狀態)
    • 調用pthread_cond_broadcast(&_cond)喚醒所有等待在條件變量上的工作線程(避免線程一直阻塞在等待任務的狀態)
  3. 回收工作線程
    遍歷存儲線程 ID 的_threads容器,通過pthread_join等待每個工作線程執行完畢并回收資源,最后清空容器。這一步確保所有線程都正常退出,避免僵尸線程。

  4. 清理任務隊列

    • 加鎖保護任務隊列操作
    • 循環清空隊列中剩余的未執行任務,逐個釋放任務對象的內存
    • 解鎖完成清理

7、添加任務進任務隊列

void ThreadPool::addTask(Task* task)
{if(_is_stop || !task) return;pthread_mutex_lock(&_mutex);_tasks.push(task);pthread_cond_signal(&_cond);        // 喚醒一個線程pthread_mutex_unlock(&_mutex);
}
  1. 參數與狀態檢查
    先判斷線程池是否已停止(_is_stoptrue)或任務指針為空,若滿足任一條件則直接返回,避免向已停止的線程池添加任務或添加無效任務。

  2. 線程安全的任務入隊

    • 加鎖(pthread_mutex_lock):確保多線程同時添加任務時,對任務隊列_tasks的操作是線程安全的
    • 入隊(_tasks.push(task)):將新任務添加到任務隊列尾部
    • 喚醒線程(pthread_cond_signal):發送信號喚醒一個正在等待的工作線程(之前在work方法中通過pthread_cond_wait等待的線程),通知有新任務可處理
    • 解鎖(pthread_mutex_unlock):釋放鎖,允許其他線程操作任務隊列

8、析構函數

ThreadPool::~ThreadPool()
{stop();pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);
}
  1. 調用 stop () 方法
    首先調用stop(),確保線程池在銷毀前已經停止運行:包括喚醒所有工作線程、回收線程資源、清理未執行的任務等(這些邏輯已在stop()中實現)。這一步是為了避免線程池對象銷毀后,仍有線程在后臺運行或資源未釋放的情況。

  2. 銷毀同步機制

    • 調用pthread_mutex_destroy(&_mutex)銷毀互斥鎖,釋放其占用的系統資源
    • 調用pthread_cond_destroy(&_cond)銷毀條件變量,同樣釋放相關系統資源

四、服務器和客戶端的通信流程

1、服務器端(像收件郵箱服務器)

  1. 新建 socket?→ 架起 “郵件接收系統”,準備收郵件
  2. bind 綁定?→ 確定自己叫?xxx@qq.com?,讓別人能找到
  3. listen 監聽?→ 開通 “同時收多封郵件” 功能,別一來就擠崩
  4. accept 等待?→ 守著等你點 “發送”,接住你的郵件請求
  5. read/write 收發?→ 收你發的郵件內容,還能回 “已收到” 提示
  6. close 關閉?→ 這次發信結束,等下次你再發

2、客戶端(像你用郵箱發信)

  1. 新建 socket?→ 打開手機郵箱 App,準備發郵件
  2. connect 連接?→ 填對方郵箱點 “發送”,主動找服務器
  3. read/write 收發?→ 寫郵件、發出去,還能收到 “發送成功”
  4. close 關閉?→ 發完關 App,結束這次發信

3、總結

網絡通信操作郵箱發信類比通俗理解
socket打開郵箱 App / 搭建郵箱系統準備通信工具 / 服務
bind確定郵箱域名(如?qq.com?)給服務定地址,讓人找得到
listen開通 “同時收信” 隊列限制并發,避免系統被擠爆
accept郵箱服務器 “接住” 你的郵件服務器受理客戶端的連接請求
connect你點 “發送”,發起發信客戶端主動連服務器
read/write寫郵件、發郵件、收提示雙方互相收發數據內容
close發完信關 App / 退出頁面結束本次通信,釋放資源

五、實現簡易服務器

1、服務器相關接口:Server.h

#ifndef SERVER_H
#define SERVER_H#include <atomic>
class Server
{
public:Server(int port): _port(port), _is_stop(true) {}bool start();void stop();private:int _server_sock;int _port;std::atomic<bool> _is_stop;
};#endif // !SERVER_H

2、服務器開始運行

bool Server::start()
{// 1. 創建服務器套接字// AF_INET: 使用IPv4協議// SOCK_STREAM: 使用TCP協議(面向連接的流式傳輸)// 0: 使用默認協議(TCP)_server_sock = socket(AF_INET, SOCK_STREAM, 0);if(_server_sock  == -1) {  // 套接字創建失敗std::cerr << "create socket failed" << std::endl;return false;}// 2. 設置套接字選項:允許端口重用// 解決服務器重啟時"地址已在使用"的問題(端口未完全釋放)int opt = 1;  // 選項值:1表示啟用該選項// SOL_SOCKET: 通用套接字級別// SO_REUSEADDR: 允許端口重用選項if (setsockopt(_server_sock, SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt)) == -1){std::cerr << "Failed to set socket options" << std::endl;close(_server_sock);  // 失敗時關閉已創建的套接字return false;}// 3. 綁定套接字到指定地址和端口sockaddr_in server_addr;          // 存儲服務器地址信息的結構體server_addr.sin_family = AF_INET; // 使用IPv4協議// 監聽所有可用網絡接口(服務器可能有多個網卡)server_addr.sin_addr.s_addr = INADDR_ANY;// 將端口號從主機字節序轉換為網絡字節序(大端序)server_addr.sin_port = htons(_port);// 綁定操作:將套接字與地址信息關聯if(bind(_server_sock, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) {std::cerr << "Failed to bind socket" << std::endl;close(_server_sock);  // 失敗時釋放資源return false;}// 4. 開始監聽連接請求// 第二個參數5: 最大等待連接隊列長度(超過的連接會被拒絕)if(listen(_server_sock, 5) == -1) {std::cerr << "Failed to listen on socket" << std::endl;close(_server_sock);  // 失敗時釋放資源return false;}// 5. 進入主循環,持續接受客戶端連接_is_stop = false;  // 重置停止標志,開始運行while (!_is_stop) {sockaddr_in client_addr;      // 存儲客戶端地址信息socklen_t client_len = sizeof(client_addr);  // 地址結構體長度// 阻塞等待客戶端連接,成功后返回客戶端專屬套接字int client_sock = accept(_server_sock, (sockaddr*)&client_addr, &client_len);if(client_sock == -1) {  // 接受連接失敗if(!_is_stop) {  // 非主動停止時才打印錯誤std::cerr << "Failed to accept connection" << std::endl;}continue;  // 繼續等待下一個連接}// 打印新連接的客戶端IP地址std::cout << "New connection from " << inet_ntoa(client_addr.sin_addr)<< std::endl;// 6. 將客戶端連接交給線程池處理// 創建網絡任務(封裝客戶端套接字),添加到線程池任務隊列// 線程池會自動分配空閑線程處理該連接,實現并發處理ThreadPool::getInstance().addTask(new NetworkTask(client_sock));}return true;
}
  1. 創建服務器套接字
    通過socket(AF_INET, SOCK_STREAM, 0)創建 TCP 套接字(SOCK_STREAM表示流式協議,即 TCP),失敗則返回錯誤。

  2. 設置套接字選項
    調用setsockopt設置SO_REUSEADDR選項,允許端口在服務器重啟后快速重用(避免因端口未完全釋放導致的啟動失敗)。

  3. 綁定地址和端口

    • 初始化server_addr結構體,指定 IPv4 協議(AF_INET)、監聽所有網卡(INADDR_ANY)和端口(_port,通過htons轉換為網絡字節序)
    • 調用bind將套接字與地址端口綁定,失敗則關閉套接字并返回。
  4. 開始監聽連接
    listen(_server_sock, 5)啟動監聽,設置等待連接的隊列長度為 5(最多同時有 5 個客戶端在隊列中等待處理)。

  5. 循環接受客戶端連接

    • 進入while(!_is_stop)循環,持續等待客戶端連接
    • accept函數阻塞等待新連接,成功后返回客戶端套接字client_sock和客戶端地址信息
    • 打印客戶端 IP 地址,標識新連接建立
  6. 用線程池處理連接
    將客戶端套接字封裝成NetworkTask任務,通過線程池的addTask方法提交給線程池處理,實現高并發(避免為每個連接單獨創建線程)。

3、服務器停止運行

void Server::stop()
{_is_stop = true;close(_server_sock);
}
  1. 設置停止標志
    _is_stop設為true,用于終止start()方法中接受連接的循環(while(!_is_stop)),讓服務器退出等待新連接的狀態。

  2. 關閉服務器套接字
    調用close(_server_sock)關閉服務器監聽套接字,這會導致阻塞在accept()函數上的服務器線程被喚醒并退出,使服務器無法再接受新連接。

4、相關的接口函數

#include <sys/socket.h>int socket(int domain, int type, int protocol);功能:創建一個套接字參數1AF_INET  網絡套接字(不同主機通過通絡進行通信)AF_UNIX  文件系統套接字(本機內多進程之間通信)參數2
指定套接字的特性:當參數1是AF_INET是,參數可以選擇:SOCK_STREAM 數據流服務,是面向連接的,更可靠的,使用TCP協議SOCK_DGRAM 數據報服務,使用UDP協議參數3
0: 表示使用默認的協議參數2為SOCK_STREAM的默認協議就是TCP參數3為SOCK_DGRAM的默認協議就是UDP返回值:成功,返回套接字對應的文件描述符;失敗,返回-1
#include <sys/socket.h>int bind(int socket,const struct sockaddr *address,socklen_t address_len);功能:把套接字和地址綁定
參數1:服務器套接字
參數2:服務器的地址
參數3:參數2的長度
返回值:成功,返回0;失敗,返回-1
#include <sys/socket.h>int listen(int socket, int backlog);功能:創建套接字隊列服務器正在處理一個客戶端的請求時,后續的客戶請求就被放入隊列等待處理。如果隊列中等待處理的請求數超過參數 2,連接請求就會被拒絕。返回值:成功,返回 0;失敗,返回-1
#include <sys/socket.h>int accept(int socket,struct sockaddr *restrict address,socklen_t *restrict address_len);功能:等待客戶端的請求,直到有客戶端接入。
參數1:服務器套接字
參數2:被接入服務器的客戶端的地址
參數3:客戶端地址的長度(注意,是一個指針)
返回值:成功,返回一個對應的客戶端套接字失敗,返回-1
#include <sys/socket.h>int connect(int socket,const struct sockaddr *address,socklen_t address_len);功能:客戶端向指定的服務器發起連接請求。
參數1:套接字
參數2:服務器地址
參數3:地址的長度
返回值:成功,返回 0;失敗,返回-1
#include <sys/socket.h>int setsockopt(int socket, int level, int option_name,const void *option_value, socklen_t option_len);功能:設置套接字的屬性選項
參數1:要設置的套接字
參數2:選項級別(如SOL_SOCKET表示通用套接字選項)
參數3:具體選項(如SO_REUSEADDR表示允許端口重用)
參數4:選項值的指針
參數5:選項值的長度
返回值:成功返回0;失敗返回-1

六、實現網絡工作類

1、網絡工作類的接口:NetworkTask.h

#ifndef NETWORK_TASK_H
#define NETWORK_TASK_H
#include "Task.h"class NetworkTask: public Task
{
public:NetworkTask(int sock) :_client_sock(sock) {}void run() override;private:int _client_sock;
};#endif // !NETWORK_TASK_H

2、實現run函數

#include "NetworkTask.h"
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <cstring>void NetworkTask::run() {char buff[BUFSIZ] = {0};while (1) {size_t read_bytes = read(_client_sock, buff, BUFSIZ - 1);if (read_bytes > 0) {std::cout << "接收: " << buff << std::endl;std::string response ="接收到" + std::to_string(strlen(buff)) + "個字符";int ret = write(_client_sock, response.data(), response.size());if (ret == -1) {perror("Error writing to socket");break;}memset(buff, 0, BUFSIZ);  // 清空緩沖區,準備下次讀取}else if (read_bytes == 0) {// 客戶端正常關閉連接std::cout << "客戶端主動關閉連接" << std::endl;break;}else {// 讀取錯誤perror("Error reading from socket");break;}}// 循環結束后關閉close(_client_sock);
}
  1. 初始化緩沖區
    創建BUFSIZ大小的字符數組buff(系統默認緩沖區大小,通常為 8192 字節),并初始化為 0,用于臨時存儲從客戶端讀取的數據。

  2. 進入通信循環
    通過while(1)開啟無限循環,持續處理與客戶端的交互:

    • 讀取數據:調用read(_client_sock, buff, BUFSIZ-1)從客戶端套接字讀取數據,最多讀取BUFSIZ-1字節(預留 1 字節給字符串結束符)。
    • 處理有效數據:若read_bytes>0(成功讀取到數據):
      • 打印接收的內容到服務器控制臺。
      • 構造響應字符串(格式為 “接收到 X 個字符”),通過write函數發送給客戶端。
      • 若寫入失敗(ret==-1),打印錯誤并退出循環。
      • memset清空緩沖區,為下一次讀取做準備。
    • 客戶端斷開連接:若read_bytes==0(客戶端主動關閉連接),打印提示信息并退出循環。
    • 讀取錯誤:若read_bytes<0(讀取失敗,如網絡異常),用perror輸出錯誤詳情并退出循環。
  3. 清理資源
    循環結束后,調用close(_client_sock)關閉客戶端套接字,釋放該連接占用的資源。


?七、main函數與測試文件

1、主函數入口: main.cpp

#include "Server.h"
#include <cstdlib>
#include <iostream>int main()
{try{Server s(8000);std::cout << "Starting server..." << std::endl;if (!s.start()) {std::cerr << "Failed to start server" << std::endl;exit(1);}std::cout << "Server is running. Press Enter to stop..." << std::endl;std::cin.get();// 停止服務器和線程池s.stop();std::cout << "Server stopped successfully" << std::endl;}catch (const std::exception& e){std::cerr << "Error: " << e.what() << std::endl;return 1;}return 0;
}

2、測試用的客戶端: test.c

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>int main()
{// 創建客戶端套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);// 設置服務器地址struct sockaddr_in address;address.sin_family      = AF_INET;address.sin_addr.s_addr = inet_addr("192.168.1.10");address.sin_port        = htons(8000);int ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address));if (ret == -1) {perror("connect failed.");exit(1);}// 接收用戶輸入char buff[BUFSIZ];printf("Please input: ");fgets(buff, sizeof(buff), stdin);buff[strlen(buff) - 1] = 0;// 向服務器發送數據write(sockfd, buff, strlen(buff) + 1);// 讀取服務器發回的數據read(sockfd, buff, sizeof(buff));printf("Received: %s\n", buff);// 關閉套接字close(sockfd);return 0;
}

3、運行結果:

// cmake編譯后可調試的命令
cmake -DCMAKE_BUILD_TYPE=Debug ..

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/93887.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/93887.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/93887.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

藏云閣 Logo 庫(開源項目SVG/PNG高清Logo)

在日常技術方案設計、架構圖繪制或PPT制作中&#xff0c;常常會遇到一些問題&#xff0c;比如&#xff1a; 找不到統一風格的開源項目組件圖標&#xff0c;PPT中的logo五花八門下載的圖標分辨率不足&#xff0c;放大后模糊失真不同來源的圖標顏色風格沖突&#xff0c;破壞整體…

從0開始學習R語言--Day64--決策樹回歸

對于沒有特征或者說需要尋找另類關系的數據集&#xff0c;我們通常會用聚合或KNN近鄰的方法來分類&#xff0c;但這樣的分類或許在結果上是好的&#xff0c;但是解釋性并不好&#xff0c;有時候我們甚至能看到好的結果反直覺&#xff1b;而決策樹回歸做出的結果&#xff0c;由于…

B+樹高效實現與優化技巧

B樹的定義 一顆M階B樹T,滿足以下條件 每個結點至多擁有M課子樹 根結點至少擁有兩顆子樹 除了根結點以外,其余每個分支結點至少擁有M/2課子樹 所有的葉結點都在同一層上 有k棵子樹的分支結點則存在k-1個關鍵字,關鍵字按照遞增順序進行排序 關鍵字數量滿足 ceil( M/2 ) - 1 &…

Android 基礎入門學習目錄(持續更新)

四大組件 Activity&#xff1a; Service&#xff1a; BroadcastReceiver&#xff1a; ContentProvider&#xff1a; UI 與交互開發 常見的UI布局和UI控件 樣式與主題 Fragment Intent 數據存儲 自定義View和自定義Group 自定義View 自定義ViewGroup 事件分發 Key…

Linux移動大量文件命令

背景 使用 mv 命令報“/bin/mv: 參數列表過長”&#xff0c;也是第一遇到&#xff0c;查了一下&#xff0c;最后用rsync命令解決了。還好每臺服務器&#xff0c;都必裝rsync了&#xff0c;記錄如下。 命令 nohup rsync -av --remove-source-files --progress /public/tmp/video…

SQL中的HAVING用法

HAVING 是 SQL 中專門對 “分組之后的聚合結果” 再做篩選的子句。 它一般跟在 GROUP BY 后面&#xff0c;不能單獨使用&#xff0c;作用類似于分組版的 WHERE。? 1. 語法位置 SELECT 列1, 聚合函數(列2) AS 別名 FROM 表 GROUP BY 列1 HAVING 聚合條件; -- 這里寫對聚合…

【Halcon 】Halcon 實戰:如何為 XLD 模板添加極性信息以提升匹配精度?

Halcon 實戰&#xff1a;如何為 XLD 模板添加極性信息以提升匹配精度&#xff1f; 在使用 Halcon 進行模板匹配時&#xff0c;我們通常有兩種方式創建模板&#xff1a; 基于圖像灰度&#xff08;CreateScaledShapeModel&#xff09;基于輪廓 XLD&#xff08;CreateScaledShapeM…

grafana/lock-stack 日志 Pipeline 配置

前言 本文使用的是 grafana/loki-stack chart 抓取的 k8s 日志。其他 chart 配置都差不多。 日志問題 docker 容器運行時 pod 內原始日志 [cpu-4] Hello, 第 9788 次報時&#xff0c;時間&#xff1a;2025-08-01T06:35:420000 {"HOSTNAME":"cpu-4",&qu…

appium2.0+之PointerActions詳解

以下內容在 夜神模擬器 上進行。 一、應用場景 一些針對手勢的操作&#xff0c;比如滑動、長按、拖動等。可以將這些基本手勢組合成一個相對復雜的手勢。 二、使用步驟創建觸摸輸入設備&#xff08;模擬手指操作&#xff09; touch_input PointerInput(interaction.POINTER_TO…

Java HTTPS 請求失敗排查與證書導入全過程

文章目錄Java HTTPS 請求失敗排查與證書導入全過程問題背景問題初步分析排查過程查看目標地址證書導入證書驗證證書是否導入成功重啟應用進一步驗證&#xff1a;是否真的是證書問題&#xff1f;1. 瀏覽器訪問2. 抓包工具驗證&#xff08;如 Charles、Wireshark&#xff09;補充…

android APT技術

1&#xff0c;背景 對于注解的使用&#xff0c;想必大家都不陌生&#xff0c;它出現在我們的源碼中&#xff0c;以及大部分框架中&#xff0c;比如ButterKnife、Arouter、Retrofit&#xff0c;但它們是有區別的&#xff0c;其中前2個是編譯時注解&#xff0c;最后一個是運行時注…

MySQL 和 PostgreSQL綜合比對分析匯總

面對大數據項目或其它類型項目中&#xff0c;面對關系型數據庫選擇一直是很總要的一點&#xff0c;本文針對MySQL 和 PostgreSQL進行綜合比對分析匯總&#xff0c;內容僅供參考。MySQL 和 PostgreSQL 是兩款主流的開源關系型數據庫&#xff08;RDBMS&#xff09;&#xff0c;但…

Linux---make和makefile

一、基本概念1.是什么make是一條命令&#xff0c;makefile是一個文件2.對應在vs中按一下f5就能運行代碼&#xff0c;在Linux中make就相當于f5&#xff0c;使用makefile來封裝從而實現我&#xff0c; 想要的功能3.使用①創建makefile文件②編輯makefile解釋&#xff1a;test.exe…

【DAB收音機】DAB收音機協議及其他資料匯總

目錄[ETSI DAB標準協議文檔](https://www.etsi.org/standards)Other DAB資料DAB收音機相關的專利DAB收音機相關的期刊及學位論文DAB開源項目代碼倉庫qt-dab工具welle.io工具dablin工具【eti廣播工具】?? 項目對比與選型建議Other 收音機資料Other資料ETSI DAB標準協議文檔 官…

RabbitMQ的特點和消息可靠性保障

掌握RabbitMQ的核心知識&#xff0c;需從其特點和消息可靠性保障&#xff08;尤其是消息丟失解決方案&#xff09;兩方面入手&#xff0c;以下是詳細說明&#xff1a; 一、RabbitMQ的核心特點 RabbitMQ是基于AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;協議…

項目升級啦

公司要新做一個醫療行業的業務&#xff0c;經過業務端和產品端的評估該業務與公司已有的產品線關聯不大&#xff0c;用戶后續也不想在老系統那臺老爺車上繼續使用&#xff0c;話說老系統到現在差不多10年了&#xff0c;中間經歷過的前后端開發者形形色色&#xff0c;維護者換了…

Android中頁面生命周期變化

一、Activity切換的生命周期變化&#xff08;A啟動B&#xff09;1. 標準流程&#xff08;B完全覆蓋A&#xff09;完整生命周期路徑&#xff1a;Activity A&#xff1a;onPause()&#xff1a;失去焦點&#xff0c;仍部分可見onStop()&#xff1a;完全不可見&#xff08;當B完全覆…

自動駕駛控制算法——PID算法

自動駕駛控制算法——PID算法 文章目錄自動駕駛控制算法——PID算法一、PID 是什么&#xff1f;二、PID 原理2.1 **比例環節&#xff08;P&#xff09;**2.2 **積分環節&#xff08;I&#xff09;**2.3 **微分環節&#xff08;D&#xff09;**2.4 特點總結2.5 案例分析 —— 小…

Spring Boot 異步執行方式全解析:@Async、CompletableFuture 與 TaskExecutor 對比

在 Spring Boot 開發中&#xff0c;異步執行是提升系統性能的重要手段&#xff0c;尤其適用于處理耗時操作&#xff08;如日志記錄、郵件發送、數據同步等&#xff09;。本文將深入對比 Spring Boot 中三種主流的異步實現方式 ——Async注解、手動CompletableFuture和直接使用T…

高效微調2:Prompt-Tuning原理與實戰

高效微調2:Prompt-Tuning原理與實戰 Prompt-Tuning原理介紹 代碼 Prompt-Tuning原理介紹 Prompt-Tuning Prompt-Tuning的思想:凍結主模型全部參數,在訓練數據前加入一小段Prompt,只訓練Prompt的表示層,即一個Embedding模塊。其中,Prompt.又存在兩種形式,一種是hard promp…