Linux网络编程04

news2024/11/28 4:52:09

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

发送方过程零拷贝
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++) {
		//创建子线程并且让子线程执行事件handleEvent
		pthread_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.c

while(1) {
	//...
	while(pthreadPool->tyaskQueue.size == 0 && pthreadPool->exitFlag == 0) {
		pthread_cond_wait(...);
	}
	//子线程被唤醒
	if(pthread->exitFlag != 0) {
		pthread_exit(NULL);
	}
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

成集云 | 电商平台、ERP、WMS集成 | 解决方案

电商平台ERPWMS 方案介绍 电商平台即是一个为企业或个人提供网上交易洽谈的平台。企业电子商务平台是建立在Internet网上进行商务活动的虚拟网络空间和保障商务顺利运营的管理环境&#xff1b;是协调、整合信息流、货物流、资金流有序、关联、高效流动的重要场所。企业、商家…

多功能声学综合馆:空间的“膜法师”

建筑是文明的承载者&#xff0c;是历史的见证者&#xff0c;也是城市的象征。但在现代都市中&#xff0c;我们不仅需要“固定”的建筑&#xff0c;更需要灵活与多功能性的空间。而这&#xff0c;正是多功能声学综合馆为我们带来的“膜法”。 不仅仅是建筑&#xff0c;更是艺术的…

盘点13种即插即用的涨点模块,含注意力机制、卷积变体、Transformer变体

朋友们&#xff0c;你们想发paper的时候有没有被创新点、改模型、改代码折磨过&#xff1f;今天我教你们一个前期又快又省事的方法&#xff0c;就是用即插即用的模块“缝合”&#xff0c;加入自己的想法快速搭积木炼丹。 这种方法可以简化模型设计&#xff0c;减少冗余工作&am…

AI技术再刷屏!明星集体“说”外语,有何风险?

近日&#xff0c;一段美国歌手泰勒斯威夫特“说”中文的短视频在网络刷屏&#xff0c;引发热议。 视频中&#xff0c;泰勒斯威夫特“说”着流利中文&#xff0c;音色和讲母语时的音色类似&#xff0c;甚至连口型都能对上。 类似的视频还有很多外国人“说”地道中文、很多中国…

智慧班牌系统全套解决方案 智慧校园云平台

随着智能的不断发展&#xff0c;学校也有了更多智能化的应用&#xff0c;传统教育信息化水平低、校园和班级文化建设、日常教学管理缺少有力的数字抓手&#xff0c;家校通缺乏渠道&#xff0c;无法及时掌握孩子在校情况&#xff0c;学校教育和家庭教育出现断层&#xff0c;存在…

如何本地搭建SeaFile私有云盘并实现远程连接

文章目录 1. 前言2. SeaFile云盘设置2.1 Owncould的安装环境设置2.2 SeaFile下载安装2.3 SeaFile的配置 3. cpolar内网穿透3.1 Cpolar下载安装3.2 Cpolar的注册3.3 Cpolar云端设置3.4 Cpolar本地设置 4.公网访问测试5.结语 1. 前言 现在我们身边的只能设备越来越多&#xff0c…

剑指JUC原理-13.线程池

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码&#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44d;三连支持&…

【NI-DAQmx入门】NI-DAQmx之MATLAB/SIMULINK支持

Data Acquisition Toolbox™ 提供用于配置数据采集硬件、将数据读入 MATLAB 和 Simulink 以及将数据写入 DAQ 模拟和数字输出通道的应用程序和函数。该工具箱支持多种 DAQ 硬件&#xff0c;包括来自 National Instruments™ 和其他供应商的 USB、PCI、PCI Express 、PXI 和 PXI…

Verilog使用vscode

使用vscode打开.v文件 Tools setting texteditor vscode文件路径 [line number]:[file name] 安装插件 搜索Verilog 添加使用最多的 添加自动纠错动能&#xff0c;将vivado自带的语法纠错工具添加到环境变量中 完成后命令行检测是否成功 vscode扩展中修改 还可添加

分模块设计与开发

一&#xff0c;分析&#xff08;在项目开始时就要确认好每个模块的功能&#xff09; 将一个大型项目拆分成几个模块每个模块分别开发将组件中的实体类和工具类拆分出来方便复用优点&#xff1a;方便维护扩展&#xff1b;也方便模块之间的相互调用资源共享 二&#xff0c;怎么实…

day04_变量丶基本数据类型丶基本数据类型转换

前置知识 计算机世界中只有二进制。那么在计算机中存储和运算的所有数据都要转为二进制。包括数字、字符、图片、声音、视频等。 进制 进制也就是进位计数制&#xff0c;是人为定义的带进位的计数方法 。不同的进制可以按照一定的规则进行转换。 进制的分类 十进制&#x…

STM32 IIC 实验

1. 可以选择I2C1&#xff0c;也可以选择I2C2&#xff0c;或者同时选择&#xff0c;同时运行 配置时钟信号 为节约空间&#xff0c;选择这两个&#xff0c;然后选择GENERATE CODE 二、HAL_I2C_Mem_Write I2C_HandleTypeDef *hi2c&#xff1a;I2C设备句柄 uint16_t DevAddress&am…

棱镜七彩亮相工控中国大会,以软件供应链安全助力新型工业化高质量发展

2023年11月1日-3日&#xff0c;2023第三届工控中国大会在苏州国际会议中心举办&#xff0c;本届大会由中国电子信息产业发展研究院、中国工业经济联合会、国家智能制造专家委员会、国家产业基础专家委员会、江苏省工业和信息化厅、江苏省国有资产监督管理委员会、苏州市人民政府…

MapReduce性能优化之小文件问题和数据倾斜问题解决方案

文章目录 MapReduce性能优化小文件问题生成SequenceFileMapFile案例 &#xff1a;使用SequenceFile实现小文件的存储和计算 数据倾斜问题实际案例 MapReduce性能优化 针对MapReduce的案例我们并没有讲太多&#xff0c;主要是因为在实际工作中真正需要我们去写MapReduce代码的场…

虚幻5.1 常见的效果关闭方式

常见的虚幻效果关闭方式 1.Bloom ProjectSettings->Rendering->Default Settings->Bloom PostProcessVolume->Lens->Bloom 2.Ambient Occlusion/Screen Space Ambient Occlusion(SSAO) ProjectSettings->Rendering->Default Settings->Ambient Occl…

芯片(集成电路)对应大学什么专业?

2022年6月18日&#xff0c;2022软科中国大学专业排名正式发布。此次排名&#xff0c;共有990所高校的30242个专业上榜。榜单中不仅呈现了大学在每个专业的实际排名&#xff0c;还提供了专业评级信息&#xff08;全国排名前2%或前2名作为A专业的标准&#xff09;。 其中&#x…

常用设计模式——策略模式

策略模式是什么 策略模式&#xff08;Strategy&#xff09;&#xff1a;针对一组算法&#xff0c;将每一个算法封装起来&#xff0c;从而使得它们可以相互替换。 比如我们一个软件的会员等级&#xff0c;每一个等级都会有对应的一些等级权益&#xff0c;那么每一个等级权益就…

ucharts 图表

<template><view><cu-custom bgColor"bg-gradual-blue" :isBack"true"><block slot"content">出库统计图</block></cu-custom><view><uni-segmented-control :current"current" :values…

360压缩安装一半不动了怎么办?分享三个简单的方法!

如果您在使用360压缩时遇到安装一半就卡住不动的问题&#xff0c;下面是一些建议和解决方案&#xff0c;主要包括四个方面的解决办法&#xff0c;排查是否是电脑硬件问题、是否为电脑软件问题、是否为360本身的问题&#xff0c;都无法解决&#xff0c;那么只能选择安装其他压缩…

Codeforces Round 906 (Div. 2--ABC)

A.Doremys Paint 3 题目 一个元素全为整数的数组&#xff0c;如果满足相邻两个元素和相同&#xff0c;我们就认定此数组is good。给定一个长度为n的数组a&#xff0c;可以任意改变元素顺序&#xff0c;判定数组a是否is good。 输入 首行t测试数据数量&#xff0c; 每组数据…