从0实现基于Linux socket聊天室-多线程服务器一个很隐晦的错误-2
根据 《0 基于socket和pthread实现多线程服务器模型》所述,server创建子线程的时候用的是以下代码:
pconnsocke = (int *) malloc(sizeof(int));
*pconnsocke = new_fd;
ret = pthread_create(&tid, NULL, rec_func, (void *) pconnsocke);
if (ret < 0)
{
perror("pthread_create err");
return -1;
}
为什么必须要malloc一块内存专门存放这个新的套接字呢?
要讲清楚这个问题的原因需要一些背景知识:
- Linux创建一个新进程时,新进程会创建一个主线程;
- 每个用户进程有自己的地址空间,系统为每个用户进程创建一个task_struct来描述该进程,
实际上task_struct 和地址空间映射表一起用来,表示一个进程; - Linux里同样用task_struct来描述一个线程,线程和进程都参与统一的调度;
- 进程内的不同线程执行是同一程序的不同部分,各个线程并行执行,受操作系统异步调度;
- 由于进程的地址空间是私有的,因此在进程间上下文切换时,系统开销比较大;
- 在同一个进程中创建的线程共享该进程的地址空间。
明白这些基础知识后,下面我来看下,当进程创建一个子线程的时候,传递的参数情况:
直接传递栈中内存地址
我们首先分析下如果创建子线程传递的是局部变量new_fd的地址这种情况。
由上图所示:
创建一个线程,如果我们按照图中传递参数方法,那么new_fd是在栈中的,创建子线程的时候我们把new_fd地址传递给了thread1,线程回调参数arg的地址是new_fd地址。
因为主函数会一直循环不退出,所以new_fd一直存在栈中。用这种方法的确可以把new_fd的值3传递到子线程的局部变量fd,这样子线程就可以使用这个fd与客户端通信。
但是因为我们设计的是并发服务器模型,我们没有办法预测客户端什么时候会连接我们的服务器,假设遇到一个极端情况,在同一时刻,多个客户端同时连接服务器,那么主线程是要同时创建多个子线程的。
多个客户端同时连接服务器
如上图所示,所有新建的的thread回调函数的参数arg存放的都是new_fd的地址。如果客户端连接的时候时间间隔比较大,是没有问题的,但是在一些极端的情况下还是有可能出现由于高并发引起的错误。
我们来捋一下极端的调用时序:
第一步:
如上图所示:
- T1时刻,当客户端1连接服务器的时候,服务器的accept函数会创建新的套接字4;
- T2时刻,创建了子线程thread1,同时子线程回调函数参数arg指向了栈中new_fd对应的内存。
- 假设,正在此时,又有一个客户端要连接服务器,而且thread1页已经用尽了时间片,那么主线程server会被调度到。
第二步:
如上图所示:
- T3时刻,主线程server接受了客户端的连接,accept函数会创建新的套接字5,同时创建子线程thread2,此时OS调度的thread2;
- T4时刻,thread2通过arg得到new_fd了的值5,并存入fd;
- T5时刻,时间片到了,调度thread1,thread1通过arg去读取new_fd,此时栈中new_fd的值已经修5覆盖了;
- 所以出现了2个线程同时使用同一个fd的情况发生。
这种情况的发生,虽然概率很低,但是并不代表不发生,该bug就是一口君在解决实际项目中遇到过的。
传递堆内存地址
如果采用传递堆的地址的方式,我们看下图:
- T1时刻,当客户端1连接服务器的时候,服务器的accept函数会创建新的套接字4,在堆中申请一块内存,用指针pconnsocke指向该内存,同时将4保存到堆中;
- T2时刻,创建了子线程thread1,同时子线程回调函数参数arg指向了堆中pconnsocke指向的内存。
- 假设,正在此时,又有一个客户端要连接服务器,而且thread1页已经用尽了时间片,那么主线程server会被调度到。
- T3时刻,主线程server接受了客户端的连接,accept函数会创建新的套接字5,在堆中申请一块内存,用指针pconnsocke指向该内存,同时将5保存到堆中,然后创建子线程thread2;
- T4时刻,thread2通过arg指向了堆中pconnsocke指向的内存,此处值为5,并存入fd;
- T5时刻,时间片到了,调度thread1,thread1通过arg去读取fd,此时堆中数据位5;
- 就不会出现了2个线程同时使用同一个fd的情况发生。
这个知识点有点隐蔽,希望读者在使用的时候多加小心。
下一章,我们要讲解如何利用我们现有的代码实现登录注册的功能。
获取更多关于Linux的资料,请关注公众号「一口Linux」
从0实现基于Linux socket聊天室-多线程服务器一个很隐晦的错误-2的更多相关文章
- Java 网络编程 -- 基于TCP 实现聊天室 群聊 私聊
分析: 聊天室需要多个客户端和一个服务端. 服务端负责转发消息. 客户端可以发送消息.接收消息. 消息分类: 群聊消息:发送除自己外所有人 私聊消息:只发送@的人 系统消息:根据情况分只发送个人和其他 ...
- 基于WebSocket实现聊天室(Node)
基于WebSocket实现聊天室(Node) WebSocket是基于TCP的长连接通信协议,服务端可以主动向前端传递数据,相比比AJAX轮询服务器,WebSocket采用监听的方式,减轻了服务器压力 ...
- Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G
code&monkey Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...
- ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(十二) 代码重构使用反射工厂解耦(一)缓存切换
前言 上一篇中,我们用了反射工厂来解除BLL和UI层耦合的问题.当然那是最简单的解决方法,再复杂一点的程序可能思路相同,但是在编程细节中需要考虑的就更多了,比如今天我在重构过程中遇到的问题.也是接下来 ...
- ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(四) 之 用户搜索(Elasticsearch),加好友流程(1)。
前面几篇基本已经实现了大部分即时通讯功能:聊天,群聊,发送文件,图片,消息.不过这些业务都是比较粗犷的.下面我们就把业务细化,之前用的是死数据,那我们就从加好友开始吧.加好友,首先你得知道你要加谁.L ...
- ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(七) 之 历史记录查询(时间,关键字,图片,文件),关键字高亮显示。
前言 上一篇讲解了如何自定义右键菜单,都是前端的内容,本篇内容就一个:查询.聊天历史纪录查询,在之前介绍查找好友的那篇博客里已经提到过 Elasticsearch,今天它又要上场了.对于Elastic ...
- Java Socket聊天室编程(一)之利用socket实现聊天之消息推送
这篇文章主要介绍了Java Socket聊天室编程(一)之利用socket实现聊天之消息推送的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下 网上已经有很多利用socket实现聊天的例子了 ...
- python socket 聊天室
socket 发送的时候,使用的是全双工的形式,不是半双工的形式.全双工就是类似于电话,可以一直通信.并且,在发送后,如果又接受数据,那么在这个接受到数据之前,整个过程是不会停止的.会进行堵塞,堵塞就 ...
- TCP/IP以及Socket聊天室带类库源码分享
TCP/IP以及Socket聊天室带类库源码分享 最近遇到个设备,需要去和客户的软件做一个网络通信交互,一般的我们的上位机都是作为客户端来和设备通信的,这次要作为服务端来监听客户端,在这个背景下,我查 ...
- ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室 实战系列
ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(零) 前言 http://www.cnblogs.com/panzi/p/5742089.html ASP.NET S ...
随机推荐
- 【golang】json数据中复杂key的处理
例1 type Transport struct { Time string Id int } func main() { //将struct的切片包装成json格式 var st []Transpo ...
- 图片接口JWT鉴权实现
图片接口JWT鉴权实现 前言 之前做了个返回图片链接的接口,然后没做授权,然后今天键盘到了,也是用JWT来做接口的权限控制. 然后JTW网上已经有很多文章来说怎么用了,这里就不做多的解释了,如果不懂的 ...
- .NET个人博客-使用Back进行消息推送
使用Back推送消息到你的iPhone 前言 我的好友看了我的博客,给我提了个需求,让我搞个网站通知,我开始以为就是评论回复然后发送邮件通知.不过他告诉我网站通知是,当有人评论或者留言后,会通知到我这 ...
- 如何解决jenkins插件下载过慢的问题
1.修改/var/lib/jenkins/updates目录下的default.json文件 通过sed命令将插件的下载地址替换成国内的地址: sed -i 's#http:\/\/updates.j ...
- vs code 设置中文
1.安装 下载地址:官网 打开 安装后打开默认显示英文界面. 2.修改 使用快捷键 ctrl+shift+p, 输入configure display language 下拉框选择 install ...
- wireshark常用过滤指令
前言 wireshark是一款高效且免费的网络封包分析软件,现就自己使用过的过滤表达式进行记录,随时更新. 正文 与.或.非指令 与:and && 示例:tcp and ip.src ...
- SpringBoot定义异步任务类需要获取结果
注意点: 要把异步任务封装到类里面,不能直接写到Controller 增加Future<String>返回结果AsyncResult<String>("task执行完 ...
- react-devtools安装以及使用中的问题
使用react框架开发的小伙伴肯定都想使用想vue-devtools开发工具一样,可以看见组件的状态,和当前组件里的props,data等等. 当然react也有一个开发者工具,现在废话少说,开始安装 ...
- 全新发布!桌面端效率工具RunFlow
RunFlow是一款跨平台的生产力工具,可以启动应用程序和搜索文件等,类似于Windows平台的Wox和PowerToys,同样也类似于Mac平台的Alfred和Raycast.但我们并不与这些工具相 ...
- TypeScript 学习笔记 — 泛型的使用(七)
目录 一.指定函数参数类型 单个泛型 多个泛型 二.函数标注的方式 类型别名 type 接口 interface *案例分析: 三.默认泛型 四.泛型约束 五.泛型接口使用 六.类中的泛型 泛型(Ge ...