2.基于多線程的TCP服務器實現

目錄

1. 簡單分析之前的代碼

2. 多線程服務器設計

2.1 C++11線程的基本使用

2.2 服務器主體邏輯

3. 錯誤處理的封裝

4. 完整的代碼實現

客戶端代碼(client.cpp)

服務器代碼(server.cpp)

5. 運行方式


在我們預想中,服務器端應該能夠同時與多個客戶端建立連接并進行網絡通信。然而,在之前的代碼中,服務器實現只支持單一連接,因為在處理連接時,主線程會被accept()read()write()等方法阻塞,導致無法響應新的連接請求。為了解決這一問題,本文將介紹如何實現一個多線程的TCP服務器,讓我們來一步步分析并構建代碼。

1. 簡單分析之前的代碼

在之前的單線程實現中,偽代碼大致如下:

int lfd = socket();
int ret = bind();
ret = listen();int cfd = accept();while(1) {read();write();
}

在此程序中,一旦與客戶端建立連接,程序會進入while(1)循環,進行數據的接收和發送。這種設計導致了以下幾個問題:

  • accept()會阻塞當前進程,直到有新客戶端連接。
  • read()會阻塞當前進程,直到有數據可以讀取。
  • write()在寫緩沖接滿時也可能阻塞。

由于這種設計,主要阻塞在read()accept()中,導致服務器無法處理多個客戶端的連接。

2. 多線程服務器設計

在多線程服務器中,我們將主要分為兩個角色:監聽和通信。主線程負責監聽客戶端的連接請求,而子線程則負責與不同的客戶端進行通信。

2.1 C++11線程的基本使用

C++11提供了強大的線程支持。以下是一個簡單的線程使用示例:

void func(int num, std::string str) {for (int i = 0; i < 10; ++i) {std::cout << "子線程: i = " << i << ", num: " << num << ", str: " << str << std::endl;}
}std::thread t(func, 520, "I love you"); // 創建子線程
// 創建子線程對象 t,執行 func() 函數。線程啟動后自動運行,參數 520 和 "I love you" 傳遞給 func()。  
// std::thread 的構造函數支持變參,無需擔心參數個數。通常,任務函數 func() 返回 void,因為子線程不處理返回值。  

以上代碼會在一個新線程中執行func(),并傳遞具體參數。

2.2 服務器主體邏輯

偽代碼的主體邏輯如下所示:

void func(int fd) {    while(1) {read();write();}close(fd);
}int main() {int lfd = socket(); // 創建監聽套接字int ret = bind(); // 綁定地址和端口ret = listen(); // 開始監聽while(1) {int cfd = accept(); // 接受客戶端連接// 創建新線程來處理通信std::thread t(func, cfd);t.detach(); // 分離線程,使其獨立運行}close(lfd); // 關閉監聽套接字
}

在此代碼中,每當接受到一個新的客戶端連接,就會創建一個新的子線程來負責與該客戶端的通信。

3. 錯誤處理的封裝

為了簡化錯誤處理,我們可以將錯誤判斷和處理封裝到一個函數中,下面是錯誤處理函數的實現:

void perror_if(bool condition, const char* errorMessage) {if (condition) {perror(errorMessage);exit(1);}
}// 使用示例
int lfd = socket(AF_INET, SOCK_STREAM, 0);
perror_if(lfd == -1, "socket");

這樣的封裝可以使代碼更加簡潔且易于維護。

4. 完整的代碼實現

客戶端代碼(client.cpp)

#include <stdlib.h>      // 提供exit函數
#include <stdio.h>       // 提供printf和perror函數
#include <unistd.h>      // 提供close函數
#include <arpa/inet.h>   // 提供socket、connect等函數
#include <string.h>      // 提供memset和strlen函數// 錯誤處理函數
void perror_if(bool condition, const char* errorMessage) {if (condition) {perror(errorMessage); // 輸出錯誤信息exit(1);              // 退出程序}
}int main() {// 1. 創建監聽的套接字int fd = socket(AF_INET, SOCK_STREAM, 0);perror_if(fd == -1, "socket"); // 檢查socket創建是否成功// 2. 綁定IP地址和端口struct sockaddr_in saddr;memset(&saddr, 0, sizeof(saddr)); // 清空結構體saddr.sin_family = AF_INET; // IPv4saddr.sin_port = htons(10000); // 設置端口,使用網絡字節序inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr); // 將IP地址轉換為網絡字節序// 連接到服務器int ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));perror_if(ret == -1, "connect"); // 檢查連接是否成功// 3. 與服務器進行通信int n = 0; // 消息計數while (1) {// 發送數據char buf[512] = {0}; // 初始化緩沖區sprintf(buf, "hi, I am client...%d\n", n++); // 格式化消息write(fd, buf, strlen(buf)); // 發送數據到服務器// 接收數據memset(buf, 0, sizeof(buf)); // 清空緩沖區int len = read(fd, buf, sizeof(buf)); // 從服務器讀取數據if (len > 0) {printf("server say: %s\n", buf); // 打印服務器返回的消息} else if (len == 0) {printf("server disconnect...\n"); // 服務器斷開連接break; // 退出循環} else {perror("read"); // 讀取數據出錯break; // 退出循環}sleep(1); // 每隔1秒發送一條數據}close(fd); // 關閉套接字return 0; // 程序結束
}

服務器代碼(server.cpp)

#include <stdlib.h>      // 提供exit函數
#include <stdio.h>       // 提供printf和perror函數
#include <unistd.h>      // 提供close函數
#include <arpa/inet.h>   // 提供socket、bind、listen、accept等函數
#include <string.h>      // 提供memset函數
#include <thread>        // 提供std::thread類以支持多線程// 錯誤處理函數
void perror_if(bool condition, const char* errorMessage) {if (condition) {perror(errorMessage); // 輸出錯誤信息exit(1);              // 退出程序}
}// 子線程函數,負責與客戶端的通信
void working(int clientfd) {char buf[512]; // 用于存儲接收到的數據while (1) {memset(buf, 0, sizeof(buf)); // 清空緩沖區int len = read(clientfd, buf, sizeof(buf)); // 從客戶端讀取數據if (len > 0) {printf("client says: %s\n", buf); // 打印客戶端發送的消息write(clientfd, buf, len); // 將接收到的數據回寫給客戶端(回顯)}else if (len == 0) {printf("client is disconnect..\n"); // 客戶端斷開連接break; // 退出循環}else {// 在多線程環境中,不再使用perror,而使用printfprintf("read error..\n"); // 讀取數據出錯break; // 退出循環}}close(clientfd); // 關閉與客戶端的連接
}int main() {// 1. 創建監聽的套接字int fd = socket(AF_INET, SOCK_STREAM, 0);perror_if(fd == -1, "socket"); // 檢查socket創建是否成功// 2. 綁定IP地址和端口struct sockaddr_in saddr;memset(&saddr, 0, sizeof(saddr)); // 清空結構體saddr.sin_family = AF_INET; // IPv4saddr.sin_port = htons(10000); // 設置端口,使用網絡字節序saddr.sin_addr.s_addr = INADDR_ANY; // 綁定到所有可用的接口// 綁定監聽套接字int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));perror_if(ret == -1, "bind"); // 檢查綁定是否成功// 3. 設置監聽ret = listen(fd, 64); // 開始監聽連接請求perror_if(ret == -1, "listen"); // 檢查監聽是否成功while (1) {// 4. 等待并建立連接struct sockaddr_in cliaddr; // 保存客戶端IP地址信息socklen_t len = sizeof(cliaddr);// 接受連接int cfd = accept(fd, (struct sockaddr*)&cliaddr, &len);if (cfd == -1) {perror("accept"); // 處理錯誤continue; // 繼續等待新的連接}char ip[64] = { 0 }; // 用于保存客戶端IP地址printf("new client fd:%d ip:%s, port:%d\n", cfd,inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip)), // 獲取客戶端IP地址ntohs(cliaddr.sin_port)); // 獲取客戶端端口// 創建新的線程來處理客戶端的通信std::thread t(working, cfd);t.detach(); // 分離線程,使其獨立運行}close(fd); // 關閉監聽套接字return 0; // 程序結束
}

5. 運行方式

  1. 編譯代碼: 使用 g++ 編譯器將代碼編譯為可執行文件:

    g++ server.cpp -o server -std=c++11 -pthread
    
  2. 運行服務器: 在終端中運行服務器程序:

    ./server
    
  3. 運行客戶端: 需要在不同的終端中運行多個客戶端程序:

    ./client
    

    可以打開多個終端來模擬多個客戶端。

  4. 觀察輸出: 在服務器終端,您將看到每個客戶端的連接消息以及客戶端發送的消息,服務器將響應這些消息。

  5. 結束運行: 要結束服務器和客戶端,可以在各自的終端使用 Ctrl+C 來終止程序。

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

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

相關文章

Python Web 框架 Django、Flask 和 FastAPI 對比

在探索 Python Web 框架時&#xff0c;Django、Flask 和 FastAPI 無疑是最常被提及的名字。根據我們最新的 Python 開發者調查&#xff0c;這三大框架繼續穩坐后端 Web 開發的熱門寶座。它們均為開源項目&#xff0c;并且與 Python 的最新版本無縫兼容。然而&#xff0c;面對不…

SQL Server數據庫表刪除分區

在 SQL Server 中刪除分區并將表恢復到非分區狀態&#xff0c;需按以下步驟操作&#xff1a; 一、合并所有分區 1. 檢查現有分區結構 首先確認表的分區方案和分區函數&#xff1a; -- 查看分區方案 SELECT * FROM sys.partition_schemes;-- 查看分區函數 SELECT * FROM sys…

信息安全和病毒防護——安全協議關于SSL和TLS協議的補充說明

文章目錄 SSL與TLS的關系SSL與TLS的核心區別SSL/TLS的典型應用安全建議總結SSL與TLS的關系 SSL(Secure Sockets Layer,安全套接層)和TLS(Transport Layer Security,傳輸層安全)是同一技術體系的演進版本,而非完全獨立的協議。其發展歷程如下: SSL 1.0(1994):未公開…

[原創](Modern C++)現代C++的關鍵性概念: 多維數組的下標引用.

[作者] 常用網名: 豬頭三 出生日期: 1981.XX.XX 企鵝交流: 643439947 個人網站: 80x86匯編小站 編程生涯: 2001年~至今[共24年] 職業生涯: 22年 開發語言: C/C、80x86ASM、Object Pascal、Objective-C、C#、R、Python、PHP、Perl、 開發工具: Visual Studio、Delphi、XCode、C …

從零構建大語言模型全棧開發指南:第二部分:模型架構設計與實現-2.2.3實戰案例:在筆記本電腦上運行輕量級LLM

?? 點擊關注不迷路 ?? 點擊關注不迷路 ?? 點擊關注不迷路 文章大綱 實戰案例:在筆記本電腦上運行輕量級LLM2.2.3 模型架構設計與實現1. 環境與工具準備1.1 硬件要求1.2 軟件棧選擇2. 輕量級模型架構設計2.1 模型參數配置2.2 關鍵技術優化3. 實戰流程3.1 數據準備流程3.2…

工業軟件的破局與重構:從技術依賴到自主創新的未來路徑

工業軟件作為現代工業的“神經與大腦”&#xff0c;不僅是制造業數字化轉型的核心工具&#xff0c;更是國家工業競爭力的戰略制高點。近年來&#xff0c;中國工業軟件市場在政策驅動與技術迭代中迅猛發展&#xff0c;但核心技術受制于人的困境仍待突破。如何實現從“跟跑”到“…

歌曲緩存相關功能

1. 核心組件 MusicCacheManager (音樂緩存管理器) 單例模式&#xff1a;確保全局只有一個實例&#xff0c;方便管理。 private static var instance: MusicCacheManager?static func shared() -> MusicCacheManager {if instance nil {instance MusicCacheManager()}ret…

解決 Ubuntu/Debian 中 `apt-get` 報錯 “無法獲得鎖 /var/lib/dpkg/lock“

問題描述 在 Ubuntu/Debian 系統中運行 sudo apt-get install 或 sudo apt update 時&#xff0c;遇到以下錯誤&#xff1a; E: 無法獲得鎖 /var/lib/dpkg/lock - open (11: 資源暫時不可用) E: 無法鎖定管理目錄(/var/lib/dpkg/)&#xff0c;是否有其他進程正占用它&#…

阿里開源的免費數據集成工具——DataX

企業里真實的數據流轉是什么樣子的呢&#xff1f; 左側描述了一個企業真實的樣子&#xff0c;我們總是需要把數據從一個地方搬到另一個地方&#xff0c;最后就是搬來搬去搬成了一張張解不開的網。 右側則表達了使用DataX為中心實現數據的同步。 什么是DataX DataX是一個異構…

26考研——圖_圖的遍歷(6)

408答疑 文章目錄 三、圖的遍歷圖的遍歷概述圖的遍歷算法的重要性圖的遍歷與樹的遍歷的區別圖的遍歷過程中的注意事項避免重復訪問遍歷算法的分類遍歷結果的不唯一性 廣度優先搜索廣度優先搜索&#xff08;BFS&#xff09;概述BFS 的特點廣度優先遍歷的過程示例圖遍歷過程 BFS …

前端解決方案:實現網頁截圖并導出PDF功能

前端解決方案&#xff1a;實現網頁截圖并導出PDF功能 在前端開發中&#xff0c;我們經常會遇到需要將網頁內容導出為PDF的需求。本文將以一個準考證預覽和導出的例子&#xff0c;帶你一步步實現這個功能。我們會處理包括跨域圖片、Canvas繪圖、PDF生成等多個技術要點。 一、基…

【MySQL】表操作

表操作 一、創建表 1、語句2、語句介紹3、注意事項4、介紹5、示例 二、查看表結構 1、語句2、介紹3、返回的信息4、示例 三、添加字段 1、語句2、語句介紹3、示例 四、修改 1、語句2、語句介紹3、示例 五、刪除 1、語句2、示例 六、修改表名 1、語句2、語句介紹3、示例 七、刪…

[新聞.AI]國產大模型新突破:阿里開源 Qwen2.5-VL-32B 與 DeepSeek 升級 V3 模型

&#xff08;本文借助 Deepseek-R1 協助生成&#xff09; 在2025年3月24日至25日的短短24小時內&#xff0c;中國AI領域迎來兩大重磅開源更新&#xff1a;阿里通義千問團隊發布多模態大模型Qwen2.5-VL-32B-Instruct&#xff0c;而DeepSeek則推出編程能力大幅提升的DeepSeek-V3…

深入剖析C# List<T>的底層實現與性能奧秘

一、動態數組的本質&#xff1a;List的架構設計 在C#的集合類型體系中&#xff0c;List作為最常用的線性數據結構&#xff0c;其核心實現基于動態數組機制。與傳統數組不同&#xff0c;List通過智能的容量管理策略&#xff0c;在保持數組高速隨機訪問優勢的同時&#xff0c;突…

【單元測試】

一、框架 不同的編程語言有不同的測試框架&#xff0c;以下是一些常見的測試框架&#xff1a; 1&#xff09;Java&#xff1a;JUnit、TestNG2&#xff09;Python&#xff1a;unittest、pytest3&#xff09;JavaScript&#xff1a;Jest、Mocha4&#xff09;C#&#xff1a;NUni…

機器學習——XGBoost

XGBoost(極度梯度提升樹&#xff0c;eXtreme Gradient Boosting)是基于GBDT的優化模型&#xff0c;其最大特性在于對GBDT的損失函數展開到二階導數&#xff0c;使得其梯度提升樹模型更接近其真實損失 其XGBoost分類樹擬合和預測方法的基本思路為&#xff1a; 遍歷所有的樹&…

響應“一機兩用”政策 ,實現政務外網安全

在數字化辦公的浪潮下&#xff0c;企業與政務機構面臨著既要保障數據安全&#xff0c;又要高效訪問互聯網的雙重需求。“一機兩用”成為解決這一難題的關鍵。 政策驅動&#xff0c;需求迫切 隨著《網絡安全法》《數據安全法》等法律法規的相繼出臺&#xff0c;網絡安全防護的要…

【后端】【Django】Django DRF API 單元測試完整方案(基于 `TestCase`)

Django DRF API 單元測試完整方案&#xff08;基于 TestCase&#xff09; 一、方案概述 使用 django.test.TestCase 和 rest_framework.test.APIClient 進行 API 單元測試&#xff0c;確保 API 正確性、權限控制、數據返回格式、業務邏輯 等。 二、基本步驟 使用 setUp() 初始…

文生圖語義識別插件使用(controlnet)

1. 插件下載(github) https://github.com/Mikubill/sd-webui-controlnet https://github.com/lllyasviel/ControlNet2. 模型下載(hugging face) https://github.com/Mikubill/sd-webui-controlnet/wiki/Model-download https://huggingface.co/bdsqlsz/qinglong_controlnet-l…

學者觀察 | web3.0產業發展與技術融合——北京大學研究員肖臻

導語 肖臻老師認為在未來很長一段時間內&#xff0c;Web 3.0將和現在的Web 2.0共存。Web 3.0和人工智能&#xff08;AI&#xff09;的融合發展前景非常廣闊&#xff0c;Web 3.0致力于打造去中心化的互聯網生態系統&#xff0c;賦予用戶更大的數據所有權和控制權&#xff0c;而…