TCP服务器实现—多进程版,多线程版,线程池版

目录

前言

1.存在的问题

2.多进程版

3.多线程版

4.线程池版

总结


前言

        在上一篇文章中使用TCP协议实现了一个简单的服务器,可以用来服务端和客户端通信,但是之前的服务器存在一个问题,就是当有多个客户端连接服务器的时候,服务器只能和一个客户端通信,其它的客户端是无法通信的,这是为什么呢?有该如何解决呢?将在这篇文章中为大家介绍

1.存在的问题

如图所示:

 为什么会存在这样的问题呢?

如上图所示,之前我们实现的服务器是单进程版的,所以当第一次和客户端建立连接完成之后,就死循环处理读取信息的逻辑了,所以就无法再和新的客户端建立连接了 。找到问题之后,很明显解决方式就是将建立连接和通信分开执行,此时我们就可以使用多进程和多线程来解决了,下面我们就具体来实现以下如何使用多进程和多线程。

2.多进程版

实现思路:与客户端建立连接完成,fork创建子进程,让父进程继续建立连接,让子进程实现后续的数据通信。

这样实现存在的问题:当父进程创建完子进程之后,需要使用waitpid回收子进程的资源,否则子进程就会变为僵尸进程,导致资源泄漏,但是使用waitpid回收子进程资源,程序变为串行化执行了,就无法实现之前的需求,让父进程负责建立连接,子进程负责数据通信了。

解决方式有两种:

1.fork创建子进程,在子进程内部再fork创建子进程,然后让父进程直接退出,此时之前的子进程作为父进程退出,新创建的子进程就变为孤儿进程被操作系统领养,并不会造成资源泄漏,并且让该子进程负责通信

2.因为子进程退出之后操作系统会发送一个SIGCHLD信号,所以可以使用signal函数捕捉SIGCHLD信号,将默认处理动作设置为SIG_IGN,在这个默认动作里会回收子进程的资源,并不会造成资源泄漏,并且让该子进程负责通信

思路1代码:

pid_t id = fork();
if(id == 0)//child
{close(_sock);if(fork() > 0)exit(0);serviceIO(sock);close(sock);exit(0);
}
close(sock);
waitpid(id,nullptr,0);

思路2代码:

signal(SIGCHLD,SIG_IGN);
if(id == 0)//child
{// 子进程会继承父进程的文件描述符表,当子进程不需要时进行关闭,// 防止子进程文件描述符资源泄露close(_sock);serviceIO(sock);close(sock);exit(0);
}

运行截图:

[myl@VM-8-12-centos tcp]$ ./tcpServer 8080
create socket success
bind socket success
listen socket success
accept a new link success
sock: 4
accept a new link success
sock: 4
recvice message: 你好,我是客户端1
recvice message: 你好,我是客户端2

此时就实现了一个客户端可以被多个服务端连接并且实现通信。

3.多线程版

说明:相比于多进程,多线程的创建和销毁对操作系统是更轻量的,消耗的资源也是更少的,所以实现数据通信可以采用多线程的方式,让主线程负责建立,让从线程负责数据通信

代码实现:

class TcpServerData
{
public:TcpServerData(TcpServer* self,int sock):_self(self),_sock(sock) {}
public:TcpServer* _self;int _sock;
};
cout << "我是主线程" << endl;
pthread_t tid;
TcpServerData* tsd = new TcpServerData(this,sock);
pthread_create(&tid,nullptr,start_routine,tsd);//因为是类内成员函数,必须包含this指针,但是start_routine作为参数是没有this指针的
//所以start_routine函数必须加上static,静态成员方法是不能访问类内成员的,所以参数传递this
//调用serviceIO,但是serviceIO函数需要传递参数sock,所以可以封装一个结构体,在结构体中包含成员
//属性sock和this
static void* start_routine(void* args) {
//设置与主线程分离,此时主线程不需要等待从线程退出了,而是继续建立连接
cout << "我是从线程" << endl;
pthread_detach(pthread_self());
TcpServerData* t = static_cast<TcpServerData*>(args);
t->_self->serviceIO(t->_sock);
close(t->_sock);
delete t;
return nullptr;

运行截图:

[myl@VM-8-12-centos tcp]$ ./tcpServer 8080
create socket success
bind socket success
listen socket success
accept a new link success
sock: 4
我是主线程
我是从线程
recvice message: 你好,我是客户端1
accept a new link success
sock: 5
我是主线程
我是从线程
recvice message: 你好,我是客户端2

4.线程池版

说明:线程池版的实现思路是基于多线程,虽然多线程创建和销毁的消耗比多进程的低,但是为了更进一步提升效率,可以预先创建好一批线程,主线程负责建立连接获取任务,然后将任务加入到队列中,让预先创建好的线程从队列中获取任务,然后处理获取到的任务。

代码实现:

void start()
{//线程池初始化:预先创建好一批线程:ThreadPool<Task>::getInstance()->run();for (;;){// 建立连接:struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(_sock, (struct sockaddr *)&peer, &len); if (sock < 0){logMessage(ERROR, "accept error, next");continue;}logMessage(NORMAL, "accept a new link success");std::cout << "sock: " << sock << std::endl;//未来通信全部用sock,面向字节流的,后续全部都是文件操作:ThreadPool<Task>::getInstance()->push(Task(sock,serviceIO));}
}

运行截图:

[myl@VM-8-12-centos tcp]$ ./tcpServer 8080
create socket success
bind socket success
listen socket success
thread-1 start ...
thread-2 start ...
thread-3 start ...
thread-4 start ...
thread-5 start ...
thread-6 start ...
thread-7 start ...
thread-8 start ...
thread-9 start ...
thread-10 start ...
accept a new link success
sock: 4
accept a new link success
sock: 5
recv message: 你好,我是客户端1
recv message: 你好,我是客户端2
注:关于线程池详细的设计与实现可以观看线程池这篇文章,里面有相信的代码实现

总结

        以上就是关于TCP服务器实现多进程版,多线程版,线程池版的详细介绍,可以通过这篇文章发现之前在系统中学习的知识在网络中全部结合起来了,今天的介绍就到这里了,感谢大家的阅读,我们下次再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/95187.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Unity细节】Unity中的层级LayerMask

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

MySQL索引介绍 为什么mysql使用B+树

什么是索引&#xff1f; 索引是一种用于快速查询和检索数据的数据结构&#xff0c;常见的索引结构有&#xff1a;B树&#xff0c;B树和Hash。 索引的作用就相当于目录。打个比方&#xff0c;我们在查字典的时候&#xff0c;如果没有目录&#xff0c;那我们就只能一页一页的去…

K8S核心组件etcd详解(上)

1 介绍 https://etcd.io/docs/v3.5/ etcd是一个高可用的分布式键值存储系统&#xff0c;是CoreOS&#xff08;现在隶属于Red Hat&#xff09;公司开发的一个开源项目。它提供了一个简单的接口来存储和检索键值对数据&#xff0c;并使用Raft协议实现了分布式一致性。etcd广泛应用…

Llama 2免费托管及API提供

Llama 2 是 Meta 最新的文本生成模型&#xff0c;目前其性能优于所有开源替代方案。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 1、强大的Llama 2 它击败了 Falcon-40B&#xff08;之前最好的开源基础模型&#xff09;&#xff0c;与 GPT-3.5 相当&#xff0c;仅低…

redis的三种集群方式

redis有三种集群方式&#xff1a;主从复制&#xff0c;哨兵模式和集群。 1.主从复制 主从复制原理&#xff1a; 从服务器连接主服务器&#xff0c;发送SYNC命令&#xff1b; 主服务器接收到SYNC命名后&#xff0c;开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所…

Fluent-MyBatis

Fluent-MyBatis Fluent-MyBatis 简介 何为 Fluent Mybatis&#xff1f; Fluent Mybatis, 是一款 Mybatis 语法增强框架, 综合了 Mybatis Plus, Dynamic SQL, JPA 等框架特性和优点 Fluent-MyBatis 开源地址 Fluent-MyBatis 原理 Fluent-MyBatis 原理是利用 annotation pro…

2023.8.12号论文阅读

文章目录 TriFormer: A Multi-modal Transformer Framework For Mild Cognitive Impairment Conversion Prediction摘要本文方法实验结果 SwIPE: Efficient and Robust Medical Image Segmentation with Implicit Patch Embeddings摘要本文方法实验结果 TriFormer: A Multi-mod…

Java实现钉钉企业内部应用机器和自定义机器人发送消息

前言 公司让写一个服务监控的功能,当监测到服务停止时,向钉钉群里推送报警信息。之前大概看到钉钉的开放平台的API文档,好像能群发消息的只有机器人。 钉钉开放平台目前提供三种机器人: 企业内部应用机器人 群模板机器人 自定义机器人 本来向用自己比较熟悉的自定义机器人…

【实用黑科技】如何 把b站的缓存视频弄到本地——数据恢复软件WinHex 和 音视频转码程序FFmpeg

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;效率…

numpy与matplotlib 常用日常代码

matplotlab 和 numpy 可能是python 数据处理工作中用的最多的库了&#xff0c; 官网的文档十分详细&#xff0c;但是就是因为数量庞大&#xff0c;很多时候常用的功能和生僻冷门的功能混在一起&#xff0c;找不到重点。按照二八原则&#xff0c;掌握20%的功能就已经能应付绝大多…

[Docker精进篇] Docker镜像构建和实践 (三)

前言&#xff1a; Docker镜像构建的作用是将应用程序及其依赖打包到一个可移植、自包含的镜像中&#xff0c;以便在不同环境中快速、可靠地部署和运行应用程序。 文章目录 Docker镜像构建1️⃣是什么&#xff1f;2️⃣为什么&#xff1f;3️⃣镜像构建一、用现有容器构建新镜像…

最强自动化测试框架Playwright(34)CDPSession

在 Playwright 中&#xff0c;CDPSession 类是用于与浏览器的 Chrome DevTools Protocol (CDP) 会话进行交互的对象。CDP 是与Chromium浏览器通信的底层协议&#xff0c;它提供了许多与浏览器进行交互和控制的功能。 CDPSession 类提供了执行底层 CDP 命令的方法&#xff0c;并…

优测云服务平台|【压力测试功能升级】轻松完成压测任务

一、本次升级主要功能如下&#xff1a; 1.多份报告对比查看测试结果 2.报告新增多种下载格式 Word格式Excel格式 3.新增多种编排复杂场景的控制器 漏斗控制器并行控制器事务控制器仅一次控制器分组控制器集合点 4.新增概览页面&#xff0c;包含多种统计维度 二、报告对比…

30W IP网络有源音箱 校园广播音箱

SV-7042XT是深圳锐科达电子有限公司的一款2.0声道壁挂式网络有源音箱&#xff0c;具有10/100M以太网接口&#xff0c;可将网络音源通过自带的功放和喇叭输出播放&#xff0c;可达到功率30W。同时它可以外接一个30W的无源副音箱&#xff0c;用在面积较大的场所。5寸进口全频低音…

嵌入式Linux开发实操(九):CAN接口开发

前言: CAN网络在汽车中的使用可以说相当广泛。而CAN网络需要的收发器最常用的就是NXP 的TJA1042: CAN网络:

Docker网络与Cgroup硬件资源占用控制

目录 1.dockers的网络模式 1.1 获取容器的进程号 1.2 docker网络模式的特性 1.3 host主机模式 1.4 container模式 1.5 none模式 1.6 bridge 桥接模式 1.6 容器的自定义网络 &#xff08;1&#xff09;未创建自定义网络时&#xff0c;创建指定IP容器的测试 &#xff0…

opsForHash() 与 opsForValue 请问有什么区别?

&#x1f449;&#xff1a;&#x1f517;官方API参考手册 如图&#xff0c;opsForHash()返回HashOperations<K,HK,HV>但是 opsForValue()返回ValueOperations<K,V>… 区别就是opsForHash的返回值泛型中有K,HK,HV,其中K是Redis指定的某个数据库里面某一个关键字(由…

详解拦截器和过滤器

目录 代码演示过滤器Demo拦截器Demo 过滤器自定义拦截器配置拦截器过滤器执行原理多个过滤器的执行顺序 拦截器自定义拦截器注册拦截器1&#xff09;注册拦截器2&#xff09;配置拦截的路径3&#xff09;配置不拦截的路径 多个拦截器的执行顺序 过滤器和拦截器的区别 代码演示 …

如何使用Spark/Flink等分布式计算引擎做网络入侵检测

如何使用Spark/Flink等分布式计算引擎做网络入侵检测 引言16 Distributed Abnormal Behavior Detection Approach Based on Deep Belief Network and Ensemble SVM Using Spark17 Spark configurations to optimize decision tree classification on UNSW-NB1518 A dynamic spa…

elementUI 的上传组件<el-upload>,自定义上传按钮样式

方法一&#xff1a; 原理&#xff1a;调用<el-upload>组件的方法唤起选择文件事件 效果&#xff1a; 页面代码&#xff1a; 1、选择图片按钮 <div class"flex_row_spacebetween btn" click"chooseImg"><span class"el-icon-plus ic…