今天我計劃通過一個小型項目,系統講解線程池與網絡編程的核心原理及實踐。項目將圍繞 “利用線程池實現高并發網絡通信” 這一核心需求展開,具體設計如下:
- 為保證線程安全,線程池采用單例模式設計,確保全局唯一實例避免資源競爭;
- 技術棧采用 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;
}
-
構造函數?
ThreadPool::ThreadPool(size_t pool_size)
- 初始化列表中設置了線程池大小
_pool_size
和停止標志_is_stop
(初始為true
表示未運行) - 調用
pthread_mutex_init
和pthread_cond_init
初始化了互斥鎖和條件變量(線程同步的核心工具) - 最后調用
start()
方法啟動線程池(實際創建并啟動工作線程)
- 初始化列表中設置了線程池大小
-
單例獲取函數?
ThreadPool::getInstance()
- 使用 C++11 的
static
局部變量特性實現單例模式 - 局部靜態變量
instance
會在第一次調用時初始化,且保證線程安全 - 每次調用都返回同一個實例的引用,確保整個程序中只有一個線程池實例
- 使用 C++11 的
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;}}
}
-
worker 函數
這是線程的入口函數(符合 pthread 庫對線程函數的要求),作用是:- 通過
static_cast
將傳入的void*
參數轉換為線程池實例指針 - 調用線程池的
work()
方法,讓線程進入實際的任務處理循環
它相當于一個適配層,將 pthread 庫的 C 風格函數接口與 C++ 的類方法銜接起來。
- 通過
-
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);}
}
-
啟動狀態檢查
首先判斷_is_stop
標志,如果線程池已經處于運行狀態(_is_stop
為false
),則直接返回,避免重復啟動。 -
初始化準備
將_is_stop
設為false
(標記線程池進入運行狀態),并通過_threads.reserve(_pool_size)
預先為存儲線程 ID 的容器分配內存,提升后續插入效率。 -
創建工作線程
循環?_pool_size?
次(線程池預設的線程數量),每次調用pthread_create
創建一個線程:- 線程入口函數為
worker
(之前實現的線程工作函數) - 傳入
this
指針作為當前線程池實例指針,讓工作線程能訪問線程池的任務隊列等資源
- 線程入口函數為
-
錯誤處理
若線程創建失敗(pthread_create
返回非 0),則通過perror
打印錯誤信息,將_is_stop
重置為true
(標記線程池停止),并退出函數。 -
記錄線程 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);
}
-
停止狀態檢查
首先判斷_is_stop
標志,如果線程池已經處于停止狀態,則直接返回,避免重復停止操作。 -
觸發停止機制
- 將
_is_stop
設為true
(標記線程池進入停止狀態) - 調用
pthread_cond_broadcast(&_cond)
喚醒所有等待在條件變量上的工作線程(避免線程一直阻塞在等待任務的狀態)
- 將
-
回收工作線程
遍歷存儲線程 ID 的_threads
容器,通過pthread_join
等待每個工作線程執行完畢并回收資源,最后清空容器。這一步確保所有線程都正常退出,避免僵尸線程。 -
清理任務隊列
- 加鎖保護任務隊列操作
- 循環清空隊列中剩余的未執行任務,逐個釋放任務對象的內存
- 解鎖完成清理
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);
}
-
參數與狀態檢查
先判斷線程池是否已停止(_is_stop
為true
)或任務指針為空,若滿足任一條件則直接返回,避免向已停止的線程池添加任務或添加無效任務。 -
線程安全的任務入隊
- 加鎖(
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);
}
-
調用 stop () 方法
首先調用stop()
,確保線程池在銷毀前已經停止運行:包括喚醒所有工作線程、回收線程資源、清理未執行的任務等(這些邏輯已在stop()
中實現)。這一步是為了避免線程池對象銷毀后,仍有線程在后臺運行或資源未釋放的情況。 -
銷毀同步機制
- 調用
pthread_mutex_destroy(&_mutex)
銷毀互斥鎖,釋放其占用的系統資源 - 調用
pthread_cond_destroy(&_cond)
銷毀條件變量,同樣釋放相關系統資源
- 調用
四、服務器和客戶端的通信流程
1、服務器端(像收件郵箱服務器)
- 新建 socket?→ 架起 “郵件接收系統”,準備收郵件
- bind 綁定?→ 確定自己叫?
xxx@qq.com
?,讓別人能找到 - listen 監聽?→ 開通 “同時收多封郵件” 功能,別一來就擠崩
- accept 等待?→ 守著等你點 “發送”,接住你的郵件請求
- read/write 收發?→ 收你發的郵件內容,還能回 “已收到” 提示
- close 關閉?→ 這次發信結束,等下次你再發
2、客戶端(像你用郵箱發信)
- 新建 socket?→ 打開手機郵箱 App,準備發郵件
- connect 連接?→ 填對方郵箱點 “發送”,主動找服務器
- read/write 收發?→ 寫郵件、發出去,還能收到 “發送成功”
- 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;
}
-
創建服務器套接字
通過socket(AF_INET, SOCK_STREAM, 0)
創建 TCP 套接字(SOCK_STREAM
表示流式協議,即 TCP),失敗則返回錯誤。 -
設置套接字選項
調用setsockopt
設置SO_REUSEADDR
選項,允許端口在服務器重啟后快速重用(避免因端口未完全釋放導致的啟動失敗)。 -
綁定地址和端口
- 初始化
server_addr
結構體,指定 IPv4 協議(AF_INET
)、監聽所有網卡(INADDR_ANY
)和端口(_port
,通過htons
轉換為網絡字節序) - 調用
bind
將套接字與地址端口綁定,失敗則關閉套接字并返回。
- 初始化
-
開始監聽連接
listen(_server_sock, 5)
啟動監聽,設置等待連接的隊列長度為 5(最多同時有 5 個客戶端在隊列中等待處理)。 -
循環接受客戶端連接
- 進入
while(!_is_stop)
循環,持續等待客戶端連接 accept
函數阻塞等待新連接,成功后返回客戶端套接字client_sock
和客戶端地址信息- 打印客戶端 IP 地址,標識新連接建立
- 進入
-
用線程池處理連接
將客戶端套接字封裝成NetworkTask
任務,通過線程池的addTask
方法提交給線程池處理,實現高并發(避免為每個連接單獨創建線程)。
3、服務器停止運行
void Server::stop()
{_is_stop = true;close(_server_sock);
}
-
設置停止標志
將_is_stop
設為true
,用于終止start()
方法中接受連接的循環(while(!_is_stop)
),讓服務器退出等待新連接的狀態。 -
關閉服務器套接字
調用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);
}
-
初始化緩沖區
創建BUFSIZ
大小的字符數組buff
(系統默認緩沖區大小,通常為 8192 字節),并初始化為 0,用于臨時存儲從客戶端讀取的數據。 -
進入通信循環
通過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
輸出錯誤詳情并退出循環。
- 讀取數據:調用
-
清理資源
循環結束后,調用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 ..