当前位置: 首页 > news >正文

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.lryc.cn/news/620260.html

相关文章:

  • Devextreme-vue + Vue2日历下拉框的使用
  • MySQL优化常用的几个方法
  • 《量子雷达》第3章 量子雷达的传输与散射 预习2025.8.13
  • 上下文工程
  • Spring Boot 整合 Thymeleaf 模板引擎:从零开始的完整指南
  • Qwen大模型加载与文本生成关键参数详解
  • lesson37:MySQL核心技术详解:约束、外键、权限管理与三大范式实践指南
  • 第一章 OkHttp 是怎么发出一个请求的?——整体流程概览
  • 浏览器面试题及详细答案 88道(23-33)
  • 智能制造数字孪生最佳交付实践:打造数据融合×场景适配×持续迭代的数字孪生框架
  • 【LeetCode】6. Z 字形变换
  • 公用表表达式和表变量的用法区别?
  • Linux 5.15.189-rt87 实时内核安装 NVIDIA 显卡驱动
  • LeetCode215~ 234题解
  • ACWing 算法基础课-数据结构笔记
  • Leetcode题解:215,数组中的第k个最大元素,如何使用快速算法解决!
  • 把 Linux 装进“小盒子”——边缘计算场景下的 Linux 裁剪、启动与远程运维全景指南
  • C#+Redis,如何有效防止缓存雪崩、穿透和击穿问题
  • 联网车辆功能安全和网络安全的挑战与当前解决方案
  • OpenBMC中的BMCWeb:架构、原理与应用全解析
  • 直播美颜SDK开发实战:高性能人脸美型的架构与实现
  • C++调试革命:时间旅行调试实战指南
  • 图像优化:使用 Next.js 的 Image 组件
  • h5bench(4)
  • linux 内核 - 内存管理概念
  • Linux 服务部署:自签 CA 证书构建 HTTPS 及动态 Web 集成
  • GO学习记录四——读取excel完成数据库建表
  • [AXI5]AXI协议中awsize和awlen在Vector Atomic地址膨胀中的作用
  • Vue3从入门到精通: 3.5 Vue3与TypeScript集成深度解析
  • FPGA的PS基础1