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++) {
		//创建子线程并且让子线程执行事件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);
	}
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Java 进阶篇】JSP EL 详解

在 Java Web 开发中&#xff0c;JavaServer Pages&#xff08;JSP&#xff09;是一种强大的技术&#xff0c;用于创建动态 Web 应用程序。JSP 的一个关键方面是 Expression Language&#xff08;EL&#xff09;表达语言&#xff0c;它允许您在 JSP 页面中嵌入 Java 代码&#x…

React动态生成二维码和毫米(mm)单位转像素(px)单位

一、使用qrcode.react生成二维码&#xff0c;qrcode.react - npm 很简单&#xff0c;安装依赖包&#xff0c;然后引用就行了 npm install qrcode.react或者 yarn add qrcode.react直接上写好的代码 import React, {useEffect, useState} from react; import QRCode from qr…

缓存-Spring Cache 缓存抽象

缓存-Spring Cache 缓存抽象 Spring从版本3.1开始提供非侵入的将Cache集成到Spring应用的方式。Spring Cache提供Cache的统一抽象&#xff0c;支持集成各种不同的缓存解决方案。从4.1版本开始&#xff0c;提供了注解和更多的定制参数。 Spring Cache 抽象提供了对Java方法的缓存…

结合双向LSTM和注意力机制的DQN-CE算法船舶能量调度

Title:Ship Energy Scheduling with DQN-CE Algorithm Combining Bi-directional LSTM and Attention Mechanism 【Applied Energy】结合双向LSTM和注意力机制的DQN-CE算法船舶能量调度(中科院1区Top,IF 11.2) 具体实现方法可以参考原文:论文地址 欢迎大家引用和交流,具体…

C++入门(2)

目录 1. 内联函数1.1概念1.2特性 2. auto关键字2.1 为什么要有auto2.2 auto 简介2.3 auto的使用细则 3.基于范围的for循环(C11)3.1 范围for的语法3.2 范围for的使用条件 4.指针空值nullptr(C11)4.1 C98中的指针空值4.2 用nullptr表示指针空值 1. 内联函数 1.1概念 用inline修饰…

【单链表】无头单项不循环(1)

目录 单链表 主函数test.c test1 test2 test3 test4 头文件&函数声明SList.h 函数实现SList.c 打印SLPrint 创建节点CreateNode 尾插SLPushBack 头插SLPushFront 头删SLPopBck 尾删SLPopFront 易错点 本篇开始链表学习。今天主要是单链表&OJ题目。 单链…

23个优秀开源免费BI仪表盘

BI也称为商业智能&#xff0c;是收集、分析和展示数据以支持决策者做出明智的业务决策的过程。BI帮助组织将其原始的生产数据转化为有意义的见解或者知识&#xff0c;以推动其业务战略。BI能够为组织改善决策、提高效率和提升资源利用率。 BI仪表盘是BI系统的重要组成部分&…

Websocket @ServerEndpoint不能注入@Autowired

在websocket中使用ServerEndpoint无法注入Autowired、Value 问题分析 Spring管理采用单例模式&#xff08;singleton&#xff09;&#xff0c;而 WebSocket 是多对象的&#xff0c;即每个客户端对应后台的一个 WebSocket 对象&#xff0c;也可以理解成 new 了一个 WebSocket&…

安全操作(安卓推流)程序

★ 安全操作项目 项目描述&#xff1a;安全操作项目旨在提高医疗设备的安全性&#xff0c;特别是在医生离开操作屏幕时&#xff0c;以减少非授权人员的误操作风险。为实现这一目标&#xff0c;我们采用多层次的保护措施&#xff0c;包括人脸识别、姿势检测以及二维码识别等技术…

Web逆向-某网络学院学习的”偷懒“思路分析

接到求助&#xff0c;帮朋友完成20课时的网络学习。 我想都没想就接下了&#xff0c;寻思找个接口直接把学习时间提交上去&#xff0c;易如反掌。 最不济最不济&#xff0c;咱还能16x播放&#xff0c;也简单的很 然鹅&#xff0c;当我登陆的时候&#xff0c;发现自己还是太天真…

边缘计算助力低速无人驾驶驶入多场景落地快车道

自动驾驶刮起的风&#xff0c;如今正吹向低速无人驾驶赛道。近期不完全统计显示&#xff0c;当前A股及港股正在排队IPO的自动驾驶相关企业共有12家&#xff0c;其中实现盈利的企业仅两家&#xff0c;而且实现盈利的两家企业最主要的收入并不完全源于自动驾驶领域。 相比之下&am…

mysql数据库的备份和恢复

目录 一、备份和恢复 1、备份&#xff1a; 2、备份的方法&#xff1a; 2.1物理备份&#xff1a; 2.2、逻辑备份 2.3增量备份&#xff1a; 一、备份和恢复 1、备份&#xff1a; 先备份再恢复 备份&#xff1a;完全备份&#xff0c;增量备份 完全备份&#xff1a;将整个…

JAVA中类和对象的认识

1、面向对象的初步认知 1.1 什么是面向对象 Java是一门纯面向对象的语言(Object Oriented Program&#xff0c;简称OOP)&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。面 向对象是解决问题的一种思想&#xff0c;主要依靠对象之间的交互完成一件事情。用面向对象的…

Java的JDBC编程

文章目录 一、数据库编程的必备条件二、Java的数据库编程&#xff1a;JDBC三、JDBC的工作原理四、JDBC的使用4.1 JDBC 开发案例4.2 JDBC 使用步骤总结 五、JDBC常用的接口和类5.1 JDBC API5.2 数据库连接 Connection5.3 Statement 对象5.4 ResultSet 对象 七、内容总结 一、数据…

【调度算法】并行机调度问题遗传算法

问题描述 m台相同的机器&#xff0c;n个工件&#xff0c;每个工件有1道工序&#xff0c;可按照任意的工序为每个工件分配一台机器进行加工 工件ABCDEFGHI工件编号012345678加工时间4765835510到达时间324532186交货期101530241413201810 设备数目&#xff1a;3 目标函数 最…

0X03

红包题第二弹 看到源码里面的提示 ?cmdphpinfo(); 看到源码 kk 关键点就是有两个正则表达式 第一个 preg_match("/[A-Za-oq-z0-9$]/",$cmd) 第二个 preg_match("/\~|\!|\|\#|\%|\^|\&|\*|\(|\)|\&#xff08;|\&#xff09;|\-|\_|\{|\}|\[|\]|\|\&q…

Redis 的缓存击穿,穿透,雪崩及其解决方案

1 缓存穿透 什么是缓存穿透&#xff1f; 大量请求的 key 是不合理的&#xff0c;根本不存在于缓存中&#xff0c;也不存在于数据库中 。导致这些请求直接到了数据库上&#xff0c;根本没有经过缓存这一层&#xff0c;对数据库造成了巨大的压力&#xff0c;可能直接就被这么多…

QT 实现解密m3u8文件

文章目录 概要如何解密M3U8文件呢实现思路和代码序列图网络请求解密 结论 概要 视频文件很多已M3U8文件格式来提供&#xff0c;先复习下什么是M3U8文件&#xff01;用QT的 mutimedia框架来播放视频时&#xff0c;有的视频加载慢&#xff0c;有的视频加载快&#xff0c;为啥&am…

深入了解Jedis:Java操作Redis的常见类型数据存储

目录 前言 一、Jedis介绍 1.Jedis在各方面的功能 2.特点 二、Java连接Redis 1.导入pom依赖 2.建立连接 三、Java操作Redis的常见类型数据存储 1.字符串 2.哈希表 3.列表 4.集合 5.有序集合 四、Redis的实际应用场景实例 1.会议信息实体 2.自定义注解 3.创建切面…

mermaid学习第一天/更改主题颜色和边框颜色/《需求解释流程图》

mermaid 在线官网&#xff1a; https://mermaid-js.github.io/ 在线学习文件&#xff1a; https://mermaid.js.org/syntax/quadrantChart.html 1、今天主要是想做需求解释的流程图&#xff0c;又不想自己画&#xff0c;就用了&#xff0c;框框不能直接进行全局配置&#xff0…