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.mzph.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;而…