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

基于linux下的高并发服务器开发(第四章)- 多线程实现并发服务器

>>了解文件描述符

文件描述符分为两类,一类是用于监听的,一类是用于通信的,在服务器端既有监听的,又有通信的。而且在服务器端只有一个用于监听的文件描述符,用于通信的文件描述符是有n个。和多少个客户端建立了连接,在服务器端就有多少个用于通信的文件描述符。在客户端只有一类文件描述符,就是用于通信的文件描述符。有了这些文件描述符,我们就可以进行网络io操作了。

网络io其实就是网络数据得到读或写操作。那么这个读或写操作的主体是谁呢?其实是内核里边的一块内存。在进行文件IO操作的时候,操作的是磁盘上的某一块内存。我们通过文件描述符就可以把数据从磁盘里边读出来,或者说把数据写入到磁盘中。我们在进行套接字通信的时候,每一个文件描述符它对应的是内核里边的两块内存,一块内存我们称之为读缓冲区,另一块内存我们称之为写缓冲区。读缓冲区是用来接收数据的,写缓冲区是用来发送数据的。

>>总结:在进行套接字通信的时候,每一个文件描述符都在内核的内存里边对应两块内存,一块内存我们称之为读缓冲区,用来接收数据的;另一块内存我们称之为写缓冲区,用来发送数据的。

>>pthread_create 

基于linux下的高并发服务器开发(第三章)-(3.1-3.2)线程概述和创建_呵呵哒( ̄▽ ̄)"的博客-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/131878249?spm=1001.2014.3001.5501

一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程
称之为子线程。
程序中默认只有一个进程,fork()函数调用,2进行
程序中默认只有一个线程,pthread_create()函数调用,2个线程。#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
void *(*start_routine) (void *), void *arg);- 功能:创建一个子线程- 参数:- thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。- attr : 设置线程的属性,一般使用默认值,NULL- start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码- arg : 给第三个参数使用,传参- 返回值:成功:0失败:返回错误号。这个错误号和之前errno不太一样。获取错误号的信息:  char * strerror(int errnum);

>>pthread_detach

基于linux下的高并发服务器开发(第三章)- 3.5 线程的分离_呵呵哒( ̄▽ ̄)"的博客-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/131880285?spm=1001.2014.3001.5501

/*#include <pthread.h>int pthread_detach(pthread_t thread);- 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统。1.不能多次分离,会产生不可预料的行为。2.不能去连接一个已经分离的线程,会报错。- 参数:需要分离的线程的ID- 返回值:成功:0失败:返回错误号
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void * callback(void * arg) {printf("chid thread id : %ld\n", pthread_self());return NULL;
}int main() {// 创建一个子线程pthread_t tid;int ret = pthread_create(&tid, NULL, callback, NULL);if(ret != 0) {char * errstr = strerror(ret);printf("error1 : %s\n", errstr);}// 输出主线程和子线程的idprintf("tid : %ld, main thread id : %ld\n", tid, pthread_self());// 设置子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放ret = pthread_detach(tid);if(ret != 0) {char * errstr = strerror(ret);printf("error2 : %s\n", errstr);}// 设置分离后,对分离的子线程进行连接 pthread_join()// ret = pthread_join(tid, NULL);// if(ret != 0) {//     char * errstr = strerror(ret);//     printf("error3 : %s\n", errstr);// }pthread_exit(NULL);return 0;
}

网友评论lecranek:老师这集关于`pthread_create()`这里讲得蛮好的,是逐步引入了为什么`sockInfos`需要这样定义,而不是敲好代码直接带过。设置成全局变量是避免在栈上变量被销毁

多线程实现并发服务器

以下思路和文字总结来自爱编程的大丙:服务器并发 | 爱编程的大丙 (subingwen.cn)

大丙老师的课程也讲得非常棒(๑•̀ㅂ•́)و✧

多线程中的线程有两大类:主线程(父线程)和子线程,他们分别要在服务器端处理监听和通信流程。根据多进程的处理思路,就可以这样设计了:

  • 主线程:
    • 负责监听,处理客户端的连接请求,循环调用accept()函数
    • 创建子线程:建立一个新的连接,就创建一个新的子线程,让这个子线程和对应的客户端通信
    • 回收子线程资源:由于回收需要调用阻塞函数,这样就会影响accept()直接做线程分离即可。
  • 子线程:负责通信,基于主线程建立新连接之后得到的文件描述符,和对应的客户端完成数据的接收和发送。
    • 发送数据:send() / write()
    • 接收数据:recv() / read()

在多线程版的服务器端程序中,多个线程共用同一个地址空间,有些数据是共享的,有些数据的独占的,下面来分析一些其中的一些细节:

  • 同一地址空间中的多个线程的栈空间是独占的
  • 多个线程共享全局数据区,堆区,以及内核区的文件描述符等资源,因此需要注意数据覆盖问题,并且在多个线程访问共享资源的时候,还需要进行线程同步。

思路:首先accept是有一个线程的,另外只要这个accept成功的和一个客户端建立了连接,那么我们就需要创建一个对应的线程,用这个线程和客户端进行网络通信。每建立一个连接,通信的线程就需要创建出来一个。这样的话,能够保证通信的线程和客户端是一个一一对应的关系,也就是说用于通信的线程一共是有n个,用于建立连接的线程只有一个。在线程里边一共分为两类,一类是主线程,一类是子线程,只要是建立了新连接,主线程创建一个子线程,让子线程和对应建立连接的那个客户端去通信就行了。 

这个图的思路和分析:我们需要在主线程里面不停的进行accept操作,如果说有新的客户端连接就建立连接。如果说没有新的客户端连接,主线程就阻塞在accept这个函数上。在主线程里边每创建一个新连接,就需要调用pthread_create创建一个子线程让这个子线程和对应的那个客户端进行网络通信。

考虑细节:多线程之间有哪些资源是共享的?哪些资源是不共享的?

全局和堆区是共享的,他们可以共同访问全局数据区里面的某一块内存或者说堆区里边的某一块内存。如果说有三个线程,那么这个栈区会被分成三份,每个线程都有一块属于自己的独立的栈空间,因此对于多个线程来说,他们并不是共享的。

 1.sockInfo结构

struct sockInfo {int fd;// 通信的文件描述符struct sockaddr_in addr;pthread_t tid;// 线程号
};
struct sockInfo sockInfos[128];

需要注意父子线程共用同一个地址空间中的文件描述符,因此每当在主线程中建立一个新的连接,都需要将得到文件描述符值保存起来,不能在同一变量上进行覆盖,这样做丢失了之前的文件描述符值也就不知道怎么和客户端通信了。

在上面示例代码中是将成功建立连接之后得到的用于通信的文件描述符值保存到了一个全局数组中,每个子线程需要和不同的客户端通信,需要的文件描述符值也就不一样,只要保证存储每个有效文件描述符值的变量对应不同的内存地址,在使用的时候就不会发生数据覆盖的现象,造成通信数据的混乱了。

把结构体数组里边的每一个元素中的文件描述符设置为-1,这样的话,可以通过这个服务器来判断当前的数组元素是不是被占用的。如果这个数组元素被占用了,它的文件描述符的值应该是一个有效值。如果是-1,是无效值。也就意味着这个元素是空闲的,是可用的

 2.初始化数据

// 初始化数据
int max = sizeof(sockInfos) / sizeof(sockInfos[0]);
for(int i = 0;i < max; i++) {bzero(&sockInfos[i],sizeof(sockInfos[i]));sockInfos[i].fd = -1;sockInfos[i].tid = -1;
} 

3.创建子线程

struct sockInfo* pinfo;
for(int i = 0;i < max;i++) {// 从这个数组中找到一个可用的sockInfo元素if(sockInfos[i].fd == -1) {pinfo = &sockInfos[i];break;}if(i == max - 1) {sleep(1);//i--;i=-1;}
}pinfo->fd = cfd;
memcpy(&pinfo->addr,&clientaddr,len);// 创建子线程
pthread_create(&pinfo->tid,NULL,working,pinfo);

4.线程分离

pthread_detach(pinfo->tid);

server_thread.c

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>struct sockInfo {int fd;// 通信的文件描述符struct sockaddr_in addr;pthread_t tid;// 线程号
};struct sockInfo sockInfos[128];void* working(void* arg){// 子线程和客户端通信    cfd    客户端的信息    线程号// 获取客户端的信息struct sockInfo * pinfo = (struct sockInfo*)arg;char clientIP[16];inet_ntop(AF_INET,&pinfo->addr.sin_addr.s_addr,clientIP,sizeof(clientIP));unsigned short clientPort = ntohs(9999);printf("client ip is : %s, port is %d\n",clientIP,clientPort);// 接收客户端发来的数据char recvBuf[1024];while(1) {int len = read(pinfo->fd,&recvBuf,sizeof(recvBuf));if(len == -1) {perror("read");exit(-1);}else if(len > 0) {printf("recv client : %s\n",recvBuf);}else if(len == 0) {printf("client closed...\n");break;}write(pinfo->fd,recvBuf,strlen(recvBuf) + 1);}close(pinfo->fd);pinfo->fd=-1;return NULL;    
}int main() {// 创建socketint lfd = socket(AF_INET,SOCK_STREAM,0);if(lfd == -1) {perror("socket");exit(-1);}struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(9999);saddr.sin_addr.s_addr = INADDR_ANY;// 绑定int ret = bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));if(ret == -1) {perror("bind");exit(-1);}// 监听ret = listen(lfd,128);if(ret == -1) {perror("listen");exit(-1);}// 初始化数据int max = sizeof(sockInfos) / sizeof(sockInfos[0]);for(int i = 0;i < max; i++) {bzero(&sockInfos[i],sizeof(sockInfos[i]));sockInfos[i].fd = -1;sockInfos[i].tid = -1;}   // 循环等待客户端连接,一旦一个客户端连接进来,就创建一个子线程进行通信while (1){struct sockaddr_in clientaddr;int len = sizeof(clientaddr);// 接受连接int cfd = accept(lfd,(struct sockaddr*)&clientaddr,&len);struct sockInfo* pinfo;for(int i = 0;i < max;i++) {// 从这个数组中找到一个可用的sockInfo元素if(sockInfos[i].fd == -1) {pinfo = &sockInfos[i];break;}if(i == max - 1) {sleep(1);//i--;i=-1;}}pinfo->fd = cfd;memcpy(&pinfo->addr,&clientaddr,len);// 创建子线程pthread_create(&pinfo->tid,NULL,working,pinfo);pthread_detach(pinfo->tid);}close(lfd);return 0;
}

client.c

// TCP 通信的客户端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>int main() {// 1.创建套接字int fd = socket(AF_INET,SOCK_STREAM,0);if(fd == -1) {perror("socket");exit(-1);}// 2.连接服务器struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;inet_pton(AF_INET,"192.168.88.129",&serveraddr.sin_addr.s_addr);serveraddr.sin_port = htons(9999);int ret = connect(fd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));if(ret == -1) {perror("connect");exit(-1);}// 3.通信char recvBuf[1024] = {0};int i = 0;while(1) {sprintf(recvBuf,"data : %d\n",i++);// 给服务端发送数据write(fd,recvBuf,strlen(recvBuf)+1);int len = read(fd,recvBuf,sizeof(recvBuf));if(len == -1) {perror("read");exit(-1);}else if(len > 0) {printf("recv server : %s\n",recvBuf);}else if(len == 0) {//表示服务器断开连接printf("server closed....\n");break;}sleep(1);}return 0;
}

相关socket的api可以看这一篇,有介绍到: 

基于linux下的高并发服务器开发(第四章)- 多进程实现并发服务器(回射服务器)_呵呵哒( ̄▽ ̄)"的博客-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/132021287?spm=1001.2014.3001.5501

相关文章:

基于linux下的高并发服务器开发(第四章)- 多线程实现并发服务器

>>了解文件描述符 文件描述符分为两类&#xff0c;一类是用于监听的&#xff0c;一类是用于通信的&#xff0c;在服务器端既有监听的&#xff0c;又有通信的。而且在服务器端只有一个用于监听的文件描述符&#xff0c;用于通信的文件描述符是有n个。和多少个客户端建立了…...

YUV 色彩空间中U 和 V 分量的范围

在YUV色彩空间中&#xff0c;U分量和V分量的范围通常是-0.5到0.5。 具体来说&#xff0c;对于标准的YUV色彩空间&#xff08;例如YUV420&#xff09;&#xff0c;取样是按照4:2:0的比例进行的。这意味着在水平和垂直方向上&#xff0c;U和V分量的取样比Y分量少一半。因此&…...

【云原生】K8S二进制搭建一

目录 一、环境部署1.1操作系统初始化 二、部署etcd集群2.1 准备签发证书环境在 master01 节点上操作在 node01与02 节点上操作 三、部署docker引擎四、部署 Master 组件4.1在 master01 节点上操 五、部署Worker Node组件 一、环境部署 集群IP组件k8s集群master01192.168.243.1…...

自动化应用杂志自动化应用杂志社自动化应用编辑部2023年第11期目录

数据处理与人工智能 大数据视域下无轨设备全生命周期健康管理技术的研究 赖凡; 1-3 三维激光扫描结合无人机倾斜摄影在街区改造测绘中的技术应用 张睿; 4-6 井上变电站巡检机器人的设计与应用 刘芳; 7-9 《自动化应用》投稿邮箱&#xff1a;cnqikantg126.com 基于机…...

Tensorflow2-初识

TensorFlow2是一个深度学习框架&#xff0c;可以理解为一个工具&#xff0c;有谷歌的全力支持&#xff0c;具有易用、灵活、可扩展、性能优越、良好的社区资源等优点。 1、环境的搭建 1.1 Anaconda3的安装 https://www.anaconda.com/ Python全家桶&#xff0c;包括Python环境和…...

idea-常用插件汇总

idea-常用插件汇总 码云插件 这个插件是码云提供的ps-码云是国内的一款类似github的代码托管工具。 Lombok Lombok是一个通用Java类库&#xff0c;能自动插入编辑器并构建工具&#xff0c;简化Java开发。通过添加注解的方式&#xff0c;不需要为类编写getter或setter等方法…...

【Kubernetes】

目录 一、Kubernetes 概述1、K8S 是什么&#xff1f;2、为什么要用 K8S?3、Kubernetes 集群架构与组件 二、核心组件1、Master 组件2、Node 组件3、K8S创建Pod的工作流程&#xff1f;&#xff08;重点&#xff09;4、K8S资源对象&#xff08;重点&#xff09;5、Kubernetes 核…...

使用逗号方式、JOIN方式和USING方式进行多表连接查询时哪个方式更好

在Oracle中&#xff0c;使用逗号方式、JOIN方式和USING方式进行多表连接查询时&#xff0c;性能上没有明显的差异。这是因为Oracle优化器会自动将这些语法转换为内部执行计划&#xff0c;以获得最佳的查询性能。 逗号方式&#xff1a;逗号方式是最简单的连接语法&#xff0c;它…...

MacOS上用docker运行mongo及mongo-express

MongoDB简介 MongoDB 是一个基于分布式文件存储的数据库。由 C 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 MongoDB 是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&#xff0c;最像关系数据库的。 前提 要求…...

海康视频插件VideoWebPlugin在vue中的实现

一,将js文件放在public文件下 二,在index中全局引入 三.在视频页面写方法,创建实例,初始化,我写的是1*4屏的 <template><!--视频窗口展示--><div idplayWnd classNameplayWnd refplayWnd styleleft: 0; bottom: 0;height: 902px;width: 60vw></div>&…...

swagger相关问题

swagger相关问题 swagger版本为&#xff1a; <dependency><groupId>com.github.xiaoymin</groupId><artifactId>swagger-bootstrap-ui</artifactId><version>1.9.6</version> </dependency> <dependency><groupId&…...

Scala关键字lazy的见解

Scala中使用关键字lazy来定义惰性变量&#xff0c;实现延迟加载(懒加载)。 惰性变量只能是不可变变量&#xff0c;并且只有在调用惰性变量时&#xff0c;才会去实例化这个变量。 在Java中&#xff0c;要实现延迟加载(懒加载)&#xff0c;需要自己手动实现。一般的做法是这样的…...

sql分类 DDL、DML、DCL

DDL &#xff08;Data Definition Language 数据定义语言) 这些语句定了不同的数据库、表、视图、索引等数据库对象&#xff0c;还可以用来创建、删除、修改数据库和数据表的结构 如: CREATE \ DROP \ ALTER \ RENAME \ TRUNCATE 等 DML&#xff08;Data Manipulation Langua…...

C++ 性能优化

要系统地提升C项目的性能&#xff0c;可以采取以下步骤&#xff1a; 分析和度量&#xff1a;首先&#xff0c;你需要通过性能分析工具来确定项目中的性能瓶颈。使用工具如gprof、perf等&#xff0c;来识别代码中消耗时间和资源最多的部分。 选择合适的数据结构和算法&#xff…...

435. 无重叠区间

435. 无重叠区间 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 示例 1: 输入: intervals [[1,2],[2,3],[3,4],[1,3]] 输出: 1 解释: 移除 [1,3] 后&#xff0c;剩下的区间…...

winform使用SetParent 嵌入excel,打开的excel跟随dpi 25%*125%缩放了两次,目前微软官方没有好的解决方案,为什么

双重缩放问题在将 Excel 嵌入到 WinForm 中时确实可能会出现&#xff0c;这是因为两个不同的应用程序&#xff08;WinForm 和 Excel&#xff09;之间的 DPI 缩放逻辑不一致&#xff0c;导致双重缩放的结果。 在 Windows 操作系统中&#xff0c;DPI 缩放是一种全局的设置&#…...

MySQL 数据库、表的基本操作

目录 数据库 关系数据库SQL 关系数据库常用词汇 常用命令语句 数据库操作 查看数据库 创建数据库 修改数据库编码 删除数据库 数据表操作 查看数据表 创建数据表 表中数据操作 增 删 改 查 数据库 数据库是在数据管理和程序开发过程中&#xff0c;一种非常重要…...

html5播放器视频切换和连续播放的实例

当前播放器实例可以使用changeVid接口切换正在播放的视频。当有多个视频&#xff0c;在上一个视频播放完毕时&#xff0c;自动播放下一个视频时也可采用该处理方式。 const option {vid: 88083abbf5bcf1356e05d39666be527a_8,//autoplay: true,//playsafe: , //PC端播放加密视…...

什么是无服务器架构技术

什么是无服务器架构技术 无服务器架构&#xff08;Serverless Architecture&#xff09;是jin年来逐渐兴起的一种软件架构方案&#xff0c;它采用了一种全新的方式来处理应用程序的部署、运行和扩展。与传统的服务器架构相比&#xff0c;无服务器架构具有很多优势&#xff0c;包…...

大数据开发的学习路线是什么样的

大数据技术的体系庞大且复杂&#xff0c;每年都会涌现出大量新的技术&#xff0c;目前大数据行业所涉及到的核心技术主要就是&#xff1a;数据采集、数据存储、数据清洗、数据查询分析和数据可视化。 学习大数据需要掌握什么语言基础&#xff1f; 1、Java基础 大数据框架90%以…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...