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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;带你吃…