TCP Socket 編程實戰:實現簡易英譯漢服務

前言:

TCP(傳輸控制協議)是一種面向連接、可靠的流式傳輸協議,與 UDP 的無連接特性不同,它通過三次握手建立連接、四次揮手斷開連接,提供數據確認、重傳機制,保證數據有序且完整傳輸。本文將基于 TCP Socket 編程,實現一個支持多客戶端連接的英譯漢服務,并詳細解析 TCP 核心 API 及不同并發處理方案。

一、TCP 通信基本流程與核心 API

1. 通信流程概覽

  • 服務器端:創建套接字 → 綁定地址端口 → 監聽連接 → 接受連接 → 數據交互 → 關閉連接
  • 客戶端:創建套接字 → 連接服務器 → 數據交互 → 關閉連接

2. 核心 API 詳解(sys/socket.h

socket():創建套接字
int socket(int domain, int type, int protocol);
  • 作用:打開一個網絡通信端口,返回文件描述符(類似文件操作的open())。
  • 參數
    • domain:協議族,IPv4 用AF_INET
    • type:套接字類型,TCP 用SOCK_STREAM(面向流);
    • protocol:協議,默認填 0(自動匹配 type 對應的協議)。
  • 返回值:成功返回非負文件描述符,失敗返回 - 1。
bind():綁定地址與端口(服務器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 作用:將套接字與特定 IP 和端口綁定,使服務器能被客戶端找到。
  • 參數
    • sockfdsocket()返回的套接字描述符;
    • addr:通用地址結構體(需轉換為struct sockaddr_in具體設置 IPv4 信息);
    • addrlen:地址結構體長度。
  • 關鍵設置
    struct sockaddr_in local;
    local.sin_family = AF_INET;         // IPv4
    local.sin_port = htons(9999);       // 端口(主機字節序→網絡字節序)
    local.sin_addr.s_addr = htonl(INADDR_ANY);  // 綁定所有本地IP
    
  • 返回值:成功返回 0,失敗返回 - 1。
listen():監聽連接(服務器)
int listen(int sockfd, int backlog);

  • 作用:將套接字設為監聽狀態,允許接收客戶端連接。
  • 參數
    • backlog:最大等待連接隊列長度(通常設 5~10)。
  • 返回值:成功返回 0,失敗返回 - 1。
accept():接受連接(服務器)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  • 作用:從監聽隊列中取出一個連接,創建新的套接字用于與客戶端通信(原套接字繼續監聽)。
  • 參數
    • addr:傳出參數,存儲客戶端地址信息;
    • addrlen:傳入傳出參數,地址結構體長度。
  • 返回值:成功返回新套接字描述符(用于通信),失敗返回 - 1。
connect():連接服務器(客戶端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 作用:客戶端向服務器發起連接(三次握手在此過程完成)。
  • 參數addr為服務器的地址信息(IP + 端口)。
  • 返回值:成功返回 0,失敗返回 - 1。
數據讀寫:read()/write()
  • TCP 是流式傳輸,可直接用文件讀寫函數:
    ssize_t read(int fd, void *buf, size_t count);  // 從套接字讀數據
    ssize_t write(int fd, const void *buf, size_t count);  // 向套接字寫數據
    

二、英譯漢服務實現(單連接版本)

1. 功能設計

  • 客戶端發送英文單詞,服務器返回對應的中文翻譯;
  • 支持 “quit” 退出連接。

2. 核心代碼實現

輔助類:nocopy(禁止拷貝,避免套接字描述符重復釋放)
// nocopy.hpp
#pragma once
class nocopy {
public:nocopy() = default;nocopy(const nocopy&) = delete;  // 禁止拷貝構造nocopy& operator=(const nocopy&) = delete;  // 禁止賦值~nocopy() = default;
};
服務器端:TcpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "nocopy.hpp"#define CONV(addr_ptr) ((struct sockaddr*)addr_ptr)  // 地址轉換宏
const int PORT = 9999;
const int BUFFER_SIZE = 1024;// 英譯漢字典(簡化版)
std::string translate(const std::string& english) {std::string chinese;if (english == "hello") chinese = "你好";else if (english == "world") chinese = "世界";else if (english == "computer") chinese = "電腦";else if (english == "program") chinese = "程序";else chinese = "未知單詞";return chinese;
}class TcpServer : public nocopy {
private:int _listensock;  // 監聽套接字bool _isrunning;  // 運行狀態public:TcpServer() : _isrunning(false) {}// 初始化服務器:創建套接字→綁定→監聽void Init() {// 1. 創建監聽套接字_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0) {std::cerr << "創建套接字失敗: " << strerror(errno) << std::endl;exit(1);}// 設置端口復用(避免服務器重啟時端口占用)int opt = 1;setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));// 2. 綁定地址和端口struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(PORT);local.sin_addr.s_addr = htonl(INADDR_ANY);if (bind(_listensock, CONV(&local), sizeof(local)) < 0) {std::cerr << "綁定失敗: " << strerror(errno) << std::endl;close(_listensock);exit(1);}// 3. 監聽連接if (listen(_listensock, 5) < 0) {std::cerr << "監聽失敗: " << strerror(errno) << std::endl;close(_listensock);exit(1);}_isrunning = true;std::cout << "服務器啟動成功,監聽端口 " << PORT << std::endl;}// 處理客戶端通信:英譯漢void Service(int client_sock) {char buffer[BUFFER_SIZE];while (true) {// 讀取客戶端發送的英文單詞ssize_t n = read(client_sock, buffer, BUFFER_SIZE - 1);if (n > 0) {buffer[n] = '\0';std::cout << "客戶端發送: " << buffer << std::endl;// 若客戶端發送"quit",斷開連接if (std::string(buffer) == "quit") {std::cout << "客戶端請求斷開連接" << std::endl;break;}// 翻譯并返回結果std::string chinese = translate(buffer);write(client_sock, chinese.c_str(), chinese.size());}else if (n == 0) {  // 客戶端關閉連接std::cout << "客戶端已斷開" << std::endl;break;}else {  // 讀取出錯std::cerr << "讀取失敗: " << strerror(errno) << std::endl;break;}}close(client_sock);  // 關閉通信套接字}// 啟動服務器:循環接受連接void Start() {while (_isrunning) {struct sockaddr_in peer;  // 客戶端地址socklen_t peer_len = sizeof(peer);// 接受連接(阻塞等待)int client_sock = accept(_listensock, CONV(&peer), &peer_len);if (client_sock < 0) {std::cerr << "接受連接失敗: " << strerror(errno) << std::endl;continue;}std::cout << "新客戶端連接: " << inet_ntoa(peer.sin_addr) << ":" << ntohs(peer.sin_port) << std::endl;Service(client_sock);  // 處理該客戶端(單連接版本:一次處理一個)}close(_listensock);  // 關閉監聽套接字}
};
服務器主函數:server.cc
#include "TcpServer.hpp"int main() {TcpServer server;server.Init();server.Start();return 0;
}
客戶端:client.cc
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>const int PORT = 9999;
const int BUFFER_SIZE = 1024;int main(int argc, char* argv[]) {if (argc != 2) {std::cerr << "用法: " << argv[0] << " 服務器IP" << std::endl;return 1;}// 1. 創建客戶端套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {std::cerr << "創建套接字失敗: " << strerror(errno) << std::endl;return 1;}// 2. 連接服務器struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(PORT);if (inet_pton(AF_INET, argv[1], &server.sin_addr) <= 0) {  // IP字符串→二進制std::cerr << "無效的IP地址" << std::endl;close(sockfd);return 1;}if (connect(sockfd, (struct sockaddr*)&server, sizeof(server)) < 0) {std::cerr << "連接服務器失敗: " << strerror(errno) << std::endl;close(sockfd);return 1;}// 3. 交互:發送英文,接收中文翻譯char buffer[BUFFER_SIZE];while (true) {std::cout << "請輸入英文單詞(輸入quit退出): ";std::string input;std::getline(std::cin, input);// 發送數據到服務器write(sockfd, input.c_str(), input.size());if (input == "quit") break;// 接收翻譯結果ssize_t n = read(sockfd, buffer, BUFFER_SIZE - 1);if (n > 0) {buffer[n] = '\0';std::cout << "翻譯結果: " << buffer << std::endl;}}close(sockfd);return 0;
}

三、支持多客戶端:并發處理方案

單連接版本一次只能處理一個客戶端,實際應用中需支持多并發。以下是三種常見方案:

1. 多進程版本

  • 原理:每接受一個客戶端連接,創建子進程處理該客戶端,父進程繼續接受新連接。
  • 關鍵代碼
    void ProcessConnection(int client_sock, const struct sockaddr_in& peer) {pid_t pid = fork();if (pid == 0) {  // 子進程close(_listensock);  // 子進程不需要監聽套接字Service(client_sock);  // 處理客戶端exit(0);  // 處理完退出} else if (pid > 0) {  // 父進程close(client_sock);  // 父進程不需要通信套接字// 回收子進程資源(避免僵尸進程)waitpid(pid, nullptr, WNOHANG);}
    }
    
  • 優缺點:簡單實現,但進程創建開銷大,適合連接數少的場景。

2. 多線程版本

  • 原理:每接受一個客戶端連接,創建線程處理該客戶端,主線程繼續接受新連接。
  • 關鍵代碼
    // 線程數據:通信套接字+客戶端地址
    struct ThreadData {int sockfd;struct sockaddr_in addr;
    };// 線程處理函數
    static void* ThreadHandler(void* arg) {pthread_detach(pthread_self());  // 分離線程,自動回收資源ThreadData* data = (ThreadData*)arg;Service(data->sockfd);  // 處理客戶端close(data->sockfd);delete data;return nullptr;
    }void ProcessConnection(int client_sock, const struct sockaddr_in& peer) {ThreadData* data = new ThreadData{client_sock, peer};pthread_t tid;pthread_create(&tid, nullptr, ThreadHandler, data);  // 創建線程
    }
    
  • 優缺點:線程開銷小于進程,但大量連接時線程創建銷毀仍有開銷。

3. 線程池版本

  • 原理:預先創建一批線程,客戶端連接到來時,將任務(處理邏輯)加入線程池隊列,線程池中的線程異步處理。
  • 關鍵代碼
    // 線程池(簡化版)
    template <typename Task>
    class ThreadPool {
    private:// 線程池實現(隊列+互斥鎖+條件變量)// ...
    public:void Push(const Task& task) {  // 添加任務// 加鎖入隊,喚醒線程}
    };// 服務器中使用線程池
    void ProcessConnection(int client_sock, const struct sockaddr_in& peer) {// 綁定處理函數與參數auto task = std::bind(&TcpServer::Service, this, client_sock);ThreadPool<decltype(task)>::GetInstance()->Push(task);  // 任務入池
    }
    
  • 優缺點:避免頻繁創建銷毀線程,適合高并發場景,是工業級常用方案。

四、編譯與運行

# 編譯服務器(以多線程版本為例)
g++ server.cc -o translator_server -lpthread
# 編譯客戶端
g++ client.cc -o translator_client

運行步驟

  1. 啟動服務器:./translator_server
  2. 啟動客戶端(多終端可啟動多個):./translator_client 127.0.0.1
  3. 客戶端輸入英文單詞(如 “hello”),接收中文翻譯;輸入 “quit” 退出。

五、總結

本文通過實現一個英譯漢服務,詳細講解了 TCP Socket 編程的核心流程與 API,并對比了單連接、多進程、多線程、線程池四種處理方案的優缺點。TCP 的可靠性使其適合需要確保數據完整傳輸的場景(如本文的翻譯服務、文件傳輸等),而并發方案的選擇需根據實際業務的連接量和性能需求決定。

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

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

相關文章

CF566C Logistical Questions Solution

Description 給定一棵 nnn 個點的樹 TTT&#xff0c;點有點權 aia_iai?&#xff0c;邊有邊權 www. 定義 dist?(u,v)\operatorname{dist}(u,v)dist(u,v) 為 u→vu\to vu→v 的簡單路徑上的邊權和. 找到一個節點 uuu&#xff0c;使得 W∑i1ndist?(u,i)32aiW\sum\limits_{i1}^n…

聊天室全棧開發-保姆級教程(Node.js+Websocket+Redis+HTML+CSS)

前言 最近在學習websocket全雙工通信&#xff0c;想要做一個聯機小游戲&#xff0c;做游戲之前先做一個聊天室練練手。 跟著本篇博客&#xff0c;可以從0搭建一個屬于你自己的聊天室。 準備階段 什么人適合學習本篇文章&#xff1f; 答&#xff1a;前端開發者&#xff0c;有一…

后臺管理系統-2-vue3之路由配置和Main組件的初步搭建布局

文章目錄1 路由搭建1.1 路由創建(router/index.js)1.2 路由組件(views/Main.vue)1.3 路由引入并注冊(main.js)1.4 路由渲染(App.vue)2 element-plus的應用2.1 完整引入并注冊(main.js)2.2 示例應用(App.vue)3 ElementPlusIconsVue的應用3.1 圖標引入并注冊(main.js)3.2 示例應用…

使用 Let’s Encrypt 免費申請泛域名 SSL 證書,并實現自動續期

使用 Let’s Encrypt 免費申請泛域名 SSL 證書&#xff0c;并實現自動續期 目錄 使用 Let’s Encrypt 免費申請泛域名 SSL 證書&#xff0c;并實現自動續期 &#x1f6e0;? 環境準備&#x1f4a1; 什么是 Let’s Encrypt&#xff1f;&#x1f9e0; Let’s Encrypt 證書頒發原…

一鍵自動化:Kickstart無人值守安裝指南

Kickstart文件實現自動安裝1. Kickstart文件概述1.1 定義與作用Kickstart文件是Red Hat系Linux發行版&#xff08;如RHEL、CentOS、Fedora&#xff09;用于實現自動化安裝的配置文件&#xff0c;采用純文本格式保存。它通過預設安裝參數的方式&#xff0c;使系統安裝過程無需人…

深度解讀 Browser-Use:讓 AI 驅動瀏覽器自動化成為可能

目錄 一、什么是 Browser-Use&#xff1f; 二、Browser-Use 的核心功能 1. AI 與瀏覽器的鏈接橋梁 2. 無代碼 / 低代碼操作界面 3. 支持多家 LLM 4. 開發體驗簡潔 可快速上手 三、核心價值與適用場景 四、與 Playwright 的結合使用 五、總結與展望 https://github.com…

React.memo、useMemo 和 React.PureComponent的區別

useMemo 和 React.memo 都是 React 提供的性能優化工具&#xff0c;但它們的作用和使用場景有顯著不同。以下是兩者的全面對比&#xff1a; 一、核心區別總結特性useMemoReact.memo類型React Hook高階組件(HOC)作用對象緩存計算結果緩存組件渲染結果優化目標避免重復計算避免不…

Lumerical INTERCONNECT ------ CW Laser 和 OPWM 組成的系統

Lumerical INTERCONNECT ------ CW Laser 和 OPWM 組成的系統 引言 正文 引言 這里我們來簡單介紹一下 CW Laser 與 OSA 組成的簡單系統結構的仿真。 正文 我們構建一個如下圖所示的仿真結構。 我們將 CWL 中的 power 設置為 1 W。 然后直接運行仿真查看結果如下: 雖然 …

想漲薪30%?別只盯著大廠了!轉型AI產品經理的3個通用方法,人人都能學!

在AI產品經理剛成為互聯網公司香餑餑的時候&#xff0c;剛做產品1年的月月就規劃了自己的轉型計劃&#xff0c;然后用3個月時間成功更換賽道&#xff0c;轉戰AI產品經理&#xff0c;漲薪30%。 問及她有什么上岸秘訣&#xff1f;她也復盤總結了3個踩坑經驗和正確路徑&#xff0c…

基于Hadoop的全國農產品批發價格數據分析與可視化與價格預測研究

文章目錄有需要本項目的代碼或文檔以及全部資源&#xff0c;或者部署調試可以私信博主項目介紹每文一語有需要本項目的代碼或文檔以及全部資源&#xff0c;或者部署調試可以私信博主 項目介紹 隨著我國農業數字化進程的加快&#xff0c;農產品批發市場每天都會產生海量的價格…

STM32在使用DMA發送和接收時的模式區別

在STM32的DMA傳輸中&#xff0c;發送使用DMA_Mode_Normal而接收使用DMA_Mode_Circular的設計基于以下關鍵差異&#xff1a;1. ?觸發機制的本質區別??發送方向&#xff08;TX&#xff09;?&#xff1a;由USART的?TXE標志&#xff08;發送寄存器空&#xff09;觸發?&#x…

【秋招筆試】2025.08.15餓了么秋招機考-第三題

?? 點擊直達筆試專欄 ??《大廠筆試突圍》 ?? 春秋招筆試突圍在線OJ ?? 筆試突圍在線刷題 bishipass.com 03. A先生的商貿網絡投資 問題描述 A先生是一位精明的商人,他計劃在 n n n 個城市之間建立商貿網絡。目前有 m m

Socket 套接字的學習--UDP

上次我們大概介紹了一些關于網絡的基礎知識&#xff0c;這次我們利用編程來深入學習一下一&#xff1a;套接字Socket1.1什么是Socketsocket API 是一層抽象的網絡編程接口,適用于各種底層網絡協議,如 IPv4、IPv6,. 然而, 各種網絡協議的地址格式并不相同。1.2套接字的分類套接字…

AI - MCP 協議(一)

AI應用開發的高級特性——MCP模型上下文協議&#xff0c;打通AI與外部服務的邊界。 ************************************************************************************************************** 一、需求分析 當你的AI具備了RAG的能力&#xff0c;具備了調用工具的…

在es中安裝kibana

一 安裝 1.1 驗證訪問https的連通性 # 測試 80 端口&#xff08;HTTP&#xff09; curl -I -m 5 http://目標IP:端口號 說明&#xff1a; -I&#xff1a;僅獲取 HTTP 頭部&#xff08;Head 請求&#xff09;&#xff0c;不下載正文&#xff0c;減少數據傳輸。 -m 5&#x…

嵌入式開發學習———Linux環境下網絡編程學習(二)

UDP服務器客戶端搭建UDP服務器代碼#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h>#define PORT 8080 #define BUFFER_SIZE 1024int main() {int sockfd;char buffer[BUFFER_SIZE…

UVa1465/LA4841 Searchlights

UVa12345 UVa1465/LA4841 Searchlights題目鏈接題意輸入格式輸出格式分析AC 代碼題目鏈接 本題是2010年icpc亞洲區域賽杭州賽區的I題 題意 在一個 n 行 m 列&#xff08;n≤100&#xff0c;m≤10 000&#xff09;的網格中有一些探照燈&#xff0c;每個探照燈有一個最大亮度 k&…

詳解區塊鏈技術及主流區塊鏈框架對比

文章目錄一、區塊鏈技術棧詳解二、主流區塊鏈框架對比1. 公有鏈&#xff08;Public Blockchain&#xff09;2. 聯盟鏈&#xff08;Consortium Blockchain&#xff09;3. 私有鏈&#xff08;Private Blockchain&#xff09;三、技術選型建議1. 按需求選擇框架2. 開發工具與生態四…

大模型 + 垂直場景:搜索 / 推薦 / 營銷 / 客服領域開發有哪些新玩法?

技術文章大綱&#xff1a;大模型 垂直場景的新玩法大模型與搜索領域的結合大模型在搜索領域的應用可以顯著提升搜索結果的準確性和用戶體驗。利用大模型進行語義理解和上下文關聯&#xff0c;能夠實現更精準的意圖識別。結合知識圖譜和動態索引優化&#xff0c;可以增強長尾查…

p5.js 3D盒子的基礎用法

點贊 關注 收藏 學會了 如果你剛接觸 p5.js&#xff0c;想嘗試 3D 繪圖&#xff0c;那么box()函數絕對是你的入門首選。它能快速繪制出 3D 長方體&#xff08;或正方體&#xff09;&#xff0c;配合簡單的交互就能做出酷炫的 3D 效果。本文會從基礎到進階&#xff0c;帶你吃…