Linux网络编程04

更高效的零拷贝
在这里插入图片描述

发送方过程零拷贝
sendfile
发送文件方的零拷贝,虽然之前我们就可以使用mmap来实现零拷贝但是存在一个方法sendfile也可以直接实现数据从内核区发送到网络发送区socket

直接把内核里面你的文件数据不经过用户态,直接发送给另外一个文件对象
有一个限制,这里的in_fd是要可以mmap的,磁盘文件可以mmap,网络设备不能mmap ,因此sendfile只能将磁盘文件取出来发送给网络,不能将网络的文件发送给磁盘(只能发送文件不能接受文件,因此可以改造服务端)

在这里插入图片描述

//将fd中的数据发送到netFd中偏移量为NULL空指针表示从0开始发送的大小为文件大小
send(netFd,fd,NULL,fileSize);

在这里插入图片描述

接收文件的零拷贝(仅供了解)

之前我们讲述过使用mmap方法让内核态和用户态映射同一块物理区域,可以实现零拷贝,但是我们还可以使用管道,来实现更快速的从socket发送数据到内核文件对象的零拷贝

在这里插入图片描述

flags的取值为SPLICE_F_MORE,表示其将数据进行移动,因为移动数据比拷贝数据简单
使用之前必须要先定义一个宏GNU_SOURCE

int pipfds[2];
pipe(pipefds);//创建管道
int total = 0;
while(1) {//读取网络数据int ret = splice(sockFd,NULL,pipefds[1],NULL,4096,SPLICE_F_MORE);total += ret;splice(pipefd[0],NULL,fd,NULL,ret,SPLICE_F_MORE);//利用管道将网络数据传给内核文件对象
}

在这里插入图片描述

进程池的退出(重点)

给父进程发送10号信号(SIGUSR1),kill 10 父进程,然后父进程先不要退出,父进程给子进程发送10号信号,退出子进程,此时父进程在退出

在注册信号的时候,我们要先创建出子进程(fork),在注册信号(signal),这样才能让每个子进程都可以收到10号信号(SIGUSR1)的执行。如果先signalfork,子进程就和父进程拥有了相同的注册信号
在这里插入图片描述
执行命令kill -10 主进程pid就可以关闭进程池

用异步拉起同步(重要)
先有一个全局管道,用主进程的epoll监听管道的读端,注册SIGUSR1,在其递送时,我们往管道中写入数据;当信号产生时,信号会递送,会开始写管道,然后读端就会就绪,epoll_wait就会就绪。可以减少全局变量的使用量,我们只需要佳能管道作为全局变量即可
0是管道的读端,1是管道的写端
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

上面那这个退出方式会导致加入子进程没有将任务做完就会被立刻退出;需求是如果子进程有任务就不要立刻退出,等待子进程将热任务做完再退出
方案1:用sigprocmask屏蔽信号,任务结束完再结束屏蔽
方案2:在父子进程间不使用信号,我们可以用文本信息代替信号,因为父子进程之间库存在管道,我们可以用主进程往管道里写入文本信息(close)告诉子进程退出,加入子进程在执行任务,是不会执行recv读取管道的,当子进程任务执行完毕之后就会调用recv读取信息,以此来实现子进程任务结束之后在关闭

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

线程池

进程池(Nginx,chrome多进程)
优点:一个子进程崩溃,不影响其他子进程
缺点:一般子进程数量在一倍CPU到二倍CPU,进程间通信太困难,消耗资源比较多

线程池架构
一个主线程建立客户端连接,主线程和子线程之间存在任务队列,主线程作为任务生产者,子线程作为任务消费者,因为任务队列是共享资源,因此需要用互斥锁保护,而且需要先是生产才能消费,所以是同步的,用条件变量来实现同步
在这里插入图片描述

线程池的实现

//存储单个线程的数据结构
typedef struct task_s{int netFd;//传递文件描述符struct task_s *pNext;//指向链表中下一个线程
} task_t;
//任务队列
typedef struct taskQueue_s {task_t *pFront;//队首指针task_t *pRear;//队尾指针int size;//队列现在的长度pthread_mutex_t mutex;//互斥锁pthread_cond_t connd;//条件变量
} taskQueue_t;
//管理线程池的数据结构
typedef struct threadPool_s {pthread_t *tid;//子线程的数组int threadNum;//子线程的数量taskQueue_t taskQueue;
} threadPool_t;

在这里插入图片描述

初始化线程池
在这里插入图片描述

创建子线程

#include "threadPool.h"
int makeWorker(threadPool_t *threadPoool){for(int i = 0;i < threadPool->threadNum;i++) {//创建子线程并且让子线程执行事件handleEventpthread_create(&threadPool->tid[i],NULL,handleEvent,(void *)threadPool);}
}
void *handleEvent(void *arg){threadPool_t *threadPool = (threadPool_t *)arg;int netFd;while(1) {printf("I am free!\n");pthread_mutex_lock(&threadPool->taskQueue.mutex);//给任务队列加锁while(threadPool->taskQueue.size == 0) {//如果任务队列为空,那么线程处于等待,调用pthread_cond_wait会先把锁给解开,然后在使线程陷入等待pthread_cond_wait(&threadPool->taskQueue.cond,&threadPool->taskQueue.mutex);}//子线程苏醒netFd = threadPool->taskQueue.pFront->netFd;//拿到了对首收文件的文件描述符taskDeQueue(&threadPool->taskQueue);//从任务队列中删除任务pthread_mutex_unlock(&threadPool->taskQueue.mutex);printf("I am working! pid = %lu\n",pthread_self());transFile(netFd);//下载文件printf("done\n");close(netFd);//关闭文件描述符}
}

主进程

int main(int argc,char *argv[]) {int workerNum = atoi(argv[3]);threadPool_t threadPool;//为线程池的任务队列,子线程的tid申请内存threadPoolInit(&threadPool,workerNum);//初始化内存makeWorker(&threadPool);//创建若干个子线程int sockFd;tcpInit(&sockFd,argv[1],argv[2]);//主线程要初始化TCP连接int epfd = epoll_create(1);epollAdd(sockFd,edfd);//用epoll把sockFd监听起来struct epoll_event readyArr[2];while(1) {int readyNum = epoll_wait(epfd,readyArr,2,-1);printf("epol_wait return \n");for(int i = 0;i < readgNum;i++) {if(readyArr[i].data.fd == sockFd) {//说明客户端有新的连接到来int netFd = accept(sockFd,NULL,NULL);//多个线程可以共享任务队列//先加锁,为修改就绪线程队列长度pthread_mutex_lock(&threadPool.taskQueue.mutex);taskEnqueue(&threadPool.taskQueue,netFd);//任务进队printf("New task!\n");pthread_cond_signal(&threadPool.taskQueue.cond);//通知处于就绪队列的线程pthread_mutex_unlock(&threadPool.taskQueue.mutex);//主线程解锁}}}
}

在这里插入图片描述
在这里插入图片描述

多线程和信号不能直接混合使用,因为信号产生就会发送给目标进程,一个进程中可以存在多个线程,信号的递送不知道是由哪个线程来实现递送,因此不能直接混合使用

线程池的退出

我们创建两个进程,让子进程实现上面所说的线程池操作,而主进程执行连接,接收客户端信号的操作,然后再主进程与子进程之间创建一根管道,父进程注册SIGUSR1信号,父进程递送信号决定写管道,子进程使用epoll监听子进程的读管道,子进程中主线程收到管道终止信号后,向子线程发送终止信号pthread_concal终止子进程,并且再终止自己

//main.c
int exitPipe[2];//创建管道用于父子进程通信
void sigFunc(int signum){//主进程发送终止管道消息的方法printf("signum = %d\n",signum);write(exitPipe[1],"1",1);printf("Parent process is going to die!\n");	
}
int main() {//...pipe(exitPipe);if(fork() != 0) {//父进程执行的代码close(exitPipe[0]);signal(SIGUSR1,sigFunc);wait(NULL);exit(0);}//子进程执行的代码close(exitPipe[1]);//子进程关闭管道写端//...epollAdd(exitPipe[0],epfd);//监听管道读端口while(1) {for(int i = 0;i < readyNum;i++) {if(readyArr[i].data.fd == sockFd){}else if(readyArr[i].data.fd == exitPipe[0]) {//就绪的是管道printf("child peocess,threadPool is going to die\n");for(int j = 0;j < workerNum;j++) {pthread_cancel(threadPool.pid[i]);//给子线程发送终止信号}for(int j = 0;j < workerNum;j++) {pthread_join(threadPool.pid[j],NULL);//等待回收子线程资源}pthread_exit(NULL);//主线程退出}}}
}

以上的退出方式存在问题,不能实现退出,因为我们再给主进程发送终止信号,主进程通过管道告诉子进程要终止,子进程的主线程收到终止信号,会执行终止,但是由于子线程都是处在睡眠状态,我们使用pthread_cancle唤醒子线程,子线程首先执行pthread_mutex_lock,进行上锁,然后子线程就会终止,但是此时锁还没解开,因此会导致下一个线程被pthread_cancle唤醒之后无法上锁,导致无法正常关闭

避免死锁

采用资源清理实现正常终止线程池
再lock之后pushpthread_cleanup_push
再原来unlock的地方poppthread_clean_pop

//worker.c
void cleanFunc(void *arg){//上锁threadPool_t *pthreadPool = (threadPool_t *)arg;pthread_mutex_unlock(&pthreadPool->taskQueue.mutex);
}//上锁位置
pthread_mutex_push(cleanFuunc,(void *)pthreadPool);//解锁位置
pthread_cleanup_pop(1);

要实现优雅的退出,不能使用pthread_cancle

使用最简单的方式,设置一个flag,表示退出的标志位,主线程再管道exitPipe就绪时,将flag改为终止标志。子线程再接收任务前检查以下flag,如果为终止标志则终止
首先我们需要在结构体taskQueue_s中加入标志位exitflag,初始值位0(表示不退出)

//main.c
while(1) {for(int i = 0;i < workerNum;i++) {if(readyArr[i].data.fd == sockFd){}else if(readyArr[i].data.fd == exitPipe[0]) {threadPool.exitFlag = 1;//改标志位pthread_cond_boradcast(&threadPool->taskQueue.cond);//将处于睡眠的进程唤醒}}
}
//worker.cwhile(1) {//...while(pthreadPool->tyaskQueue.size == 0 && pthreadPool->exitFlag == 0) {pthread_cond_wait(...);}//子线程被唤醒if(pthread->exitFlag != 0) {pthread_exit(NULL);}
}

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

如何对ppt文件设置修改权限?

PPT文件会应用在会议、演讲、课件等工作生活中&#xff0c;当我们制作好了PPT之后&#xff0c;保护内容防止在演示时出错是很重要的&#xff0c;那么如何将PPT文件设置成禁止修改模式呢&#xff1f;今天分享几个方法给大家。 方法一 将PPT文件直接保存或者另存为一份文件&…

C++入门学习(1)命名空间和输入输出

前言 在C语言和基本的数据结构学习之后&#xff0c;我们终于迎来了期待已久的C啦&#xff01;C发明出来的意义就是填补一些C语言的不足&#xff0c;让我们更加方便的写代码&#xff0c;所以今天我们就来讲一下C语言不足的地方和在C中的解决办法&#xff01; 一、命名空间 在学习…

前端框架Vue学习 ——(五)前端工程化Vue-cli脚手架

文章目录 Vue-cliVue项目-创建Vue项目-目录结构Vue项目-启动Vue项目-配置端口Vue项目开发流程 Vue-cli 介绍&#xff1a;Vue-cli 是 Vue 官方提供的一个脚手架&#xff0c;用于快速生成一个 Vue 的项目模版 安装 NodeJS安装 Vue-cli npm install -g vue/cliVue项目-创建 图…

嬴图 | LLM+Graph:大语言模型与图数据库技术的协同

前言 2022年11月以来&#xff0c;大语言模型席卷全球&#xff0c;在自然语言任务中表现卓越。尽管存在一系列伦理、安全等方面的担心&#xff0c;但各界对该技术的热情和关注并未减弱。 本文不谈智能伦理方面的问题&#xff0c;仅集中于Ulitpa嬴图在应用中的一些探索与实践&a…

01-单节点部署clickhouse及简单使用

1、下载rpm安装包&#xff1a; 官网&#xff1a;https://packages.clickhouse.com/rpm/stable/ clickhouse19.4版本之后只需下载3个rpm安装包&#xff0c;上传到节点目录即可 2、rpm包安装&#xff1a; 安装顺序为conmon->server->client 执行 rpm -ivh ./clickhouse-…

【深度学习 AIGC】stable diffusion webUI 使用过程,参数设置,教程,使用方法

文章目录 docker快速启动vae.ckpt或者.safetensorsCFG指数/CFG Scale面部修复/Restore facesRefinerTiled VAEClip Skipprompt提示词怎么写roop Upscaler visibility (if scale 1) docker快速启动 如果你想使用docker快速启动这个项目&#xff0c;你可以按下面这么操作&#…

(Git)git clone报错——SSL certificate problem: self signed certificate

(Git)git clone报错——SSL certificate problem: self signed certificate 克隆代码时报错 问题分析 提示信息为SSL认证失败&#xff0c;可以关闭SSL的认证。 公司bitbucket只支持https地址&#xff0c;需要client配置忽略https证书检验。 解决方法 在克隆前输入下边命令&…

Linux学习第35天:Linux LCD 驱动实验(二):星星之火可以燎原

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 三、LCD驱动程序编写 需要注意的地方&#xff1a; ①、 LCD 所使用的 IO 配置。 ②、 LCD 屏幕节点修改&#xff0c;修改相应的属性值&#xff0c;换成我们所使…

Spring Data Redis + RabbitMQ - 基于 string + hash 实现缓存,计数(高内聚)

目录 一、Spring Data Redis 1.1、缓存功能(分析) 1.2、案例实现 一、Spring Data Redis 1.1、缓存功能(分析) hash 类型存储缓存相比于 string 类型就有更多的更合适的使用场景. 例如,我有以下这样一个 UserInfo 信息 假设这样一个场景就是:万一只想获取其中某一个…

利用shp文件构建mask【MATLAB和ARCGIS】两种方法

1 ARCGIS &#xff08;推荐&#xff01;&#xff01;&#xff01;-速度很快&#xff09; 利用Polygon to Raster 注意&#xff1a;由于我们想要的mask有效值是1&#xff0c;在进行转换的时候&#xff0c;注意设置转换字段【Value field】 【Value field】通过编辑shp文件属性表…

centos 7部署Mysql8.0主从

Mysql官网中关于部署主从的网址 环境准备&#xff1a; 搭建虚拟机和安装Mysql之前的文章中已经涉及&#xff0c;在此不再赘述。 主从IPMysql账号密码主192.168.213.4root/Root1234!从192.168.213.5root/Root1234! 1、主数据库设置 配置my.cnf 一般存放于/etc/。 主从配…

【git】TortoiseGit图标不显示 及 文件夹中.git文件夹不显示

&#xff08;一&#xff09;文件夹中.git文件夹不显示 在 文件夹选项-查看-高级设置 中&#xff0c; 将 隐藏文件和文件夹中的不显示&#xff0c;标记为“显示隐藏的文件、文件夹和驱动器” &#xff08;二&#xff09;TortoiseGit图标不显示 【情况一】是否有正确安装 Tort…

inne所属公司抢注“童年时光”商标仍被冻结

根据中国商标网查询&#xff0c;国家知识产权局已于2023年3月10日裁定&#xff0c;被告inne所属的南京童年时光生物技术有限公司注册的“童年时光”商标无效。随着这起保健品行业品牌资产争夺事件的发酵&#xff0c;更多的细节得到披露&#xff0c;至此&#xff0c;一个从“代理…

【软件STM32cubeIDE下H73xx配置串口uart1+中断接收/DMA收发+HAL库+简单数据解析-基础样例】

#【软件STM32cubeIDE下H73xx配置串口uart1中断接收/DMA收发HAL库简单数据解析-基础样例】 1、前言2、实验器件3-1、普通收发中断接收实验第一步&#xff1a;代码调试-基本配置&#xff08;1&#xff09;基本配置&#xff08;3&#xff09;时钟配置&#xff08;4&#xff09;保存…

shiro 框架使用学习

简介 Shiro安全框架是Apache提供的一个强大灵活的安全框架Shiro安全框架提供了认证、授权、企业会话管理、加密、缓存管理相关的功能&#xff0c;使用Shiro可以非常方便的完成项目的权限管理模块开发 Shiro的整体架构 1、Subject ​ Subject即主体&#xff08;可以把当前用户…

微信小程序将后端返回的图片文件流解析显示导页面

说明 由于请求接口后端返回的图片格式不是一个完整的url,也不是其他直接能显示的图片格式&#xff0c;是一张图片 后端根据模板与二维码生成图片,返回二进制数据 返回为文件流的格式,用wx.request请求的时候&#xff0c;就自动解码成为了下面这样的数据数据格式,这样的数据没…

idea文件比对

idea文件比对 1.项目内的文件比对2.项目间的文件比对3. 剪切板对比4. 版本历史(不同分支和不同commit)对比 1.项目内的文件比对 在项目中选择好需要比对的文件(类)&#xff0c;然后选择Compare Files Mac下的快捷键是Commandd&#xff0c; 这样的比对像是git冲突解决一样 …

STM32 GPIO 描述

一、GPIO功能描述 每个GPIO端口有两个32位配置寄存器(GPIOx_CRL&#xff0c;GPIOx_CRH) &#xff0c;两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR) &#xff0c;一个32位置位/复位寄存器(GPIOx_BSRR)&#xff0c;一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR…

Flutter:安装依赖报错doesn‘t support null safety

项目中需要引用http依赖&#xff0c;在pubspec.yaml文件中添加如下信息&#xff1a; 当同步时&#xff0c;报错信息如下&#xff1a; [myflutter] flutter pub upgrade Resolving dependencies... The current Dart SDK version is 3.1.3. Because myflutter depends on http &…

【leetcode】19.删除链表的倒数第 N 个结点

题目 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 示例 3&a…