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

[C++ 网络协议] 多线程服务器端

具有代表性的并发服务器端实现模型和方法:

多进程服务器:通过创建多个进程提供服务。

多路复用服务器:通过捆绑并统一管理I/O对象提供服务。

多线程服务器:通过生成与客户端等量的线程提供服务。✔

目录

1. 线程的概念

1.1 为什么要引入线程

1.2 线程和进程的差异

2.线程函数

2.1 线程的创建

2.1 分离线程

3. 线程存在的问题和临界区

4. 线程安全

5. 线程同步

5.1 互斥量(Mutual Exclusion)

5.1.1 概念

5.1.2 互斥量的创建

5.1.3 互斥量的销毁

5.1.4 上锁和解锁

5.2 信号量

5.2.1 概念

5.2.2 创建信号量

5.2.3 销毁信号量

5.2.4 post和wait

6. 多线程服务器端的实现


1. 线程的概念

1.1 为什么要引入线程

之前学习的内容中,讲解了多进程服务器端的实现方法,明确了其缺点:

  1. 创建进程的过程会带来一定的开销
  2. 为了完成进程间的数据交换,要进行特殊的IPC技术(管道通信等)
  3. 每秒多次的上下文切换(进程A和进程B之间切换运行,操作系统要先将进程A的相关信息移出内存,再读入进程B的相关信息),带来的巨大开销

所以,为了保持多进程的优点,同时在一定程度上客服其缺点,就引入了线程,也被称为“轻量级进程”,其相比于进程有如下优点:

  1. 线程的创建和上下文切换比进程的创建和上下文切换更快。
  2. 线程间的通信,无需特殊技术。

1.2 线程和进程的差异

对于进程来说,每次创建新进程,都要复制旧进程的整个内存区域,包括:全局数据区、堆区、栈区。但如果创建进程只是为了获得多个代码执行流,那么就不应该复制整个进程的内存区域。如图:

所以,线程共享数据区、堆区,而分离栈区,进程是分离整个内存区。

进程和线程可以定义为如下形式:

进程:在操作系统构成单独执行流的单位

线程:在进程中构成单独执行流的单位

它们的关系如图:

2.线程函数

2.1 线程的创建

#include<pthread.h>int pthread_create(
pthread_t* restrict thread,            //保存新创建线程ID的变量地址值
const pthread_attr_t* restrict attr,   //传递线程属性的参数,传递NULL,创建默认属性的线程
void* (*start_routine)(void*),         //线程的main函数,单独执行流中执行的函数地址值
void* restrict arg                     //第三个参数调用函数时要传入的参数信息的变量地址值
);
成功返回0
失败返回其他值

restrict关键字:它只可以用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式.即它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改。这样做的好处是,能帮助编译器进行更好的优化代码,生成更有效率的汇编代码。

线程的代码在编译时命令行需要添加-lpthread的声明来连接线程库,如:

gcc thread.c -o thr -lpthread

传递多个参数的方法:定义一个结构体,存放参数,然后进行指针的转换。

struct thread_param
{int fd;sockaddr_in addr;
};int main()
{......thread_param params;params.fd=clientfd;params.addr=clientAddr;pthread_create(&clientthread,NULL,thread_client_handle,(void*)&params);
}void* thread_client_handle(void* args)
{thread_param params=*(thread_param*)args;int clientfd=params.fd;sockaddr_in clientAddr=params.addr;......
}

2.1 分离线程

#include<pthread.h>int pthread_join(
pthread_t thread,    //要分离的线程ID
void** statrus       //保存线程的main函数的返回值的指针变量地址值
);
成功返回0
失败返回其他值

函数功能:阻塞住主线程的运行,直到这个子线程运行结束,被销毁后,主线程才会继续执行。

#include<pthread.h>int pthread_detach(
pthread_t thread      //要分离的线程ID
);
成功返回0
失败返回其他值

函数功能:不会阻塞主线程的运行,子线程自己运行结束后,自己进行销毁。调用该函数后,不能再调用pthread_join。

3. 线程存在的问题和临界区

当有多个线程同时访问一个共享数据时,就很大概率导致意想不到的错误发生。例如:

当有2个线程访问同一个全局变量num初始值为100,并都要对其进行+1操作,理想情况下得到的值应该是102,但可能会有如下情况的发生:

        1.线程A首先访问变量num,要给num进行+1的操作,其会做以下事情

                A.首先从全局数据区中读取num的值。

                B.再将其传递到CPU,让CPU计算后,得到其结果。

                C.再把值写入到num变量中。

        2.在线程A执行到C之前,此时线程B就读取了num的值,然后当线程B执行完以上步骤时,此时num的值已经变为了101,但是线程A此时也同时进行了C步,那么num最终的结果将仍然是101,这并不是理想的结果。

所以可能会出现各种不同的问题。这样所有线程都执行的函数,这类函数内部就存在临界区

临界区的位置是在:函数内同时运行多个线程时引起问题的多条语句构成的代码块

我们要如何避免这种线程共享数据的问题的发生?

        1.使用线程安全的函数

        2.实现线程同步

4. 线程安全

根据临界区是否引起问题,函数可以分为两类:

        1.线程安全函数:被多个线程同时调用也不会引发问题的函数。一般来说,大部分标准函数都是线程安全函数。

        2.非线程安全函数:与线程安全函数相反,多个线程调用可能会引发问题。

怎么使用线程安全函数?

        1.平台在定义非线程安全函数的同时,就提供了具有相同功能的线程安全函数。具有线程安全函数的名称后缀通常为_r。例如:gethostbyname(非线程安全函数),gethostbyname_r(线程安全函数)。

        2.或者可以在头文件处,声明_REENTRANT宏,声明了此宏,就会将程序内的函数自动改为线程安全函数调用。

或者在编译时加上gcc -D_REEDTRANT thread.c -o thread -lpthread

5. 线程同步

线程同步一般涉及如下两个情况:

        1.同时访问统一内存空间时的情况。(互斥量)

        2.指定访问统一内存空间的线程执行顺序的情况。(信号量)

5.1 互斥量(Mutual Exclusion)

5.1.1 概念

互斥量是一种同步技术,其提供锁机制,当某一个线程在调用临界区代码时,可以使用互斥量将临界区保护起来,即将其锁住,其他线程将会等待此线程解锁之后,才进入调用临界区代码。

5.1.2 互斥量的创建

方式一:

#include<pthread.h>int pthread_mutex_init(
pthread_mutex_t* mutex,            //创建互斥量时传递保护互斥量的变量地址
const pthread_mutexattr_t* attr    //传递创建的互斥量的属性,传递NULL则默认
);
成功返回0
失败返回其他值

方式二:

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

不推荐第二种方式,因为不容易发现错误。

5.1.3 互斥量的销毁

#include<pthread.h>int pthread_mutex_destroy(
pthread_mutex_t* mutex,            //销毁互斥量时传递的互斥量地址值
);
成功返回0
失败返回其他值

5.1.4 上锁和解锁

#include<pthread.h>int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
成功返回0
失败返回其他值

注意:上锁(lock)后一定要记得解锁(unlock),不然其他线程将一直处于阻塞状态,造成死锁。

5.2 信号量

5.2.1 概念

信号量也是一种同步技术,利用二进制信号量(只用0和1)来完成控制线程顺序为中心的同步方法。

5.2.2 创建信号量

#include<semaphore.h>int sem_init(
sem_t* sem,            //创建信号量时传递保存的信号量的变量地址值
int pshared,           //传递其他值时,创建可以多个进程共享的信号量//传递0时,创建只允许一个进程使用的信号量
unsigned int value     //创建的信号量的初始值
);
成功返回0
失败返回其他值

5.2.3 销毁信号量

#include<semaphore.h>int sem_destroy(sem_t* sem);    //销毁时传递需要销毁的信号量变量地址值
成功返回0
失败返回其他值

5.2.4 post和wait

#include<semaphore.h>//sem是传递保存信号量读取值的变量地址值
int sem_wait(sem_t* sem);    //信号量-1,类似于互斥量的lock
int sem_post(sem_t* sem);    //信号量+1,类似于互斥量的unlock
成功返回0
失败返回其他值

调用sem_wait时,因为信号量的值不能<0,所以当线程执行sem_wait时sem的值是0时,那么线程此时就会阻塞住,当sem的值是1,线程会继续执行,同时会将sem减为0。如:

sem_t signal=1;
sem_wait(&signal);    //signal变为0,且继续执行
...
sem_post(&signal);    //signal变为1sem_t signal2=0;
sem_wait(&signal2);    //signal为0,阻塞状态
...
sem_post(&signal2);    //signal变为1

保证线程的访问顺序,需要两个信号量,如下面的代码,需要保证线程A先处理,线程B再取值。

static sem_t sem_one;
static sem_t sem_two;int main()
{sem_init(&sem_one,0,0);    //sem_one初始化为0sem_init(&sem_two,0,1);    //sem_two初始化为1......//线程A先create和join
}void* Handle(void* arg)      //线程A
{sem_wait(&sem_two);      //先进入执行,将sem_two置为0,等待线程B执行结束,将sem_two置为1,再第二次执行......sem_post(&sem_one);
}void* accu(void* arg)        //线程B
{sem_wait(&sem_one);      //等待线程A执行到sem_post(&sem_one)将其置为1......sem_post(&sem_two);
}

6. 多线程服务器端的实现(聊天室)

实现思路:

服务器端作为一个中转站,将一个客户端发送的消息,发送给另一个客户端。

map容器:key是ip地址,value是文件描述符,每连接一个客户端把这个客户端的ip地址和文件描述符以键值对的形式存入map中。

message结构体:客户端之间接收和发送的数据,里面存有要发送的客户端的ip地址,以及聊天消息内容。

mutex互斥量:保护对map进行插入的代码段的临界区。

服务器端代码:

#define _REENTRANT
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<semaphore.h>
#include<iterator>
#include<map>struct thread_param
{int fd;sockaddr_in addr;
};struct message
{char ip[17];char content[100];
};void* thread_client_handle(void* args);std::map<std::string,int> mapClient;pthread_mutex_t mutex;int main()
{int serverfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);if(serverfd==-1){std::cout<<"创建套接字错误!"<<std::endl;return 0;}int soreuse=true;socklen_t soreuselen=sizeof(soreuse);setsockopt(serverfd,SOL_SOCKET,SO_REUSEADDR,(void*)&soreuse,soreuselen);sockaddr_in serverAddr;memset(&serverAddr,0,sizeof(serverAddr));serverAddr.sin_family=AF_INET;serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);serverAddr.sin_port=htons(9130);if(-1==bind(serverfd,(sockaddr*)&serverAddr,sizeof(serverAddr))){std::cout<<"绑定套接字失败!"<<std::endl;return 0;}if(-1==listen(serverfd,2)){std::cout<<"监听失败!"<<std::endl;return 0;}while(1){sockaddr_in clientAddr;memset(&clientAddr,0,sizeof(clientAddr));socklen_t clientAddrLen=sizeof(clientAddr);int clientfd=accept(serverfd,(sockaddr*)&clientAddr,&clientAddrLen);pthread_mutex_init(&mutex,NULL);pthread_t clientthread;thread_param params;params.fd=clientfd;params.addr=clientAddr;pthread_create(&clientthread,NULL,thread_client_handle,(void*)&params);pthread_detach(clientthread);}pthread_mutex_destroy(&mutex);close(serverfd);
}void* thread_client_handle(void* args)
{thread_param params=*(thread_param*)args;int clientfd=params.fd;sockaddr_in clientAddr=params.addr;if(clientfd==-1){std::cout<<"客户端连接失败!"<<std::endl;return NULL;}else{pthread_mutex_lock(&mutex);std::string ip=std::string(inet_ntoa(clientAddr.sin_addr));mapClient.insert(std::make_pair(ip,clientfd));std::cout<<"客户端:"<<ip<<"已连接!"<<std::endl;pthread_mutex_unlock(&mutex);}char buff[1024];int readLen;while((readLen=read(clientfd,buff,sizeof(buff)))){message* msg=(message*)buff;if(msg){std::string peerIp=std::string(msg->ip);std::map<std::string,int>::iterator it=mapClient.find(peerIp);if(it!=mapClient.end()){int peerfd=it->second;write(peerfd,(const char*)msg,readLen);}else{std::cout<<"找不到您要交流的对象或您交流的对象已退出"<<std::endl;break;}}}close(clientfd);
}

客户端实现思路:

先输入要交流的对象的IP地址,然后创建一个子线程,主线程写,子线程读。

客户端代码:

#include<iostream>
#include<cstring>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<pthread.h>struct message
{char ip[17];char content[100];
};void* thread_read(void* arg);std::string strIp;int main()
{int clientfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);if(clientfd==-1){std::cout<<"socket fail!"<<std::endl;return 0;}sockaddr_in clientAddr;memset(&clientAddr, 0, sizeof(clientAddr));clientAddr.sin_family = AF_INET;clientAddr.sin_port = htons(9130);clientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");if (connect(clientfd, (sockaddr*)&clientAddr, sizeof(clientAddr)) == -1){std::cout << "连接失败!" << std::endl;return 0;}std::cout<<"请输入要交流的对方的IP地址:"<<std::endl;std::cin>>strIp;pthread_t readthread;pthread_create(&readthread,NULL,thread_read,(void*)&clientfd);pthread_detach(readthread);std::string chatcontent;while(1){std::cout<<"您(输入'Q'退出聊天):";std::cin>>chatcontent;if(chatcontent.compare("Q")==0){break;}message msg;strcpy(msg.ip, strIp.c_str());strcpy(msg.content, chatcontent.c_str());write(clientfd,(const char*)&msg,sizeof(msg));}close(clientfd);
}void* thread_read(void* arg)
{int clientfd=*(int*)arg;char buff[1024];int readLen;while((readLen=read(clientfd,buff,sizeof(buff)))){message* msg=(message*)buff;std::cout<<"对方(IP:"<<strIp<<"):"<<msg->content<<std::endl;}
}

执行结果:

服务器:

客户端1:

客户端2:

其实这种通过IP地址来确定要发送的客户端的实现是有问题的:每个客户端作为主机在连接到因特网上时,电信/联通网会分配动态IP地址,所以这种向服务器端传IP地址从而建立沟通通道的效果行不通。建议是每个客户端主机都能有一个唯一标识,来进行判断。

相关文章:

[C++ 网络协议] 多线程服务器端

具有代表性的并发服务器端实现模型和方法&#xff1a; 多进程服务器&#xff1a;通过创建多个进程提供服务。 多路复用服务器&#xff1a;通过捆绑并统一管理I/O对象提供服务。 多线程服务器&#xff1a;通过生成与客户端等量的线程提供服务。✔ 目录 1. 线程的概念 1.1 为什…...

宝塔部署node后使用pm2管理上传文件路径失效问题

如何进行文件上传&#xff1f; node上传文件 vue3 elementPlus 组件封装 在本地或者以宝塔终端的形式允许 上传后是没问题的&#xff0c;直接默认对multer直接写入路径就可以了 const multer require(multer) const upload multer({ dest: ./public/avataruploads/ }) …...

postman-pre-request-scripts使用

一、场景 二、定义模拟接口 using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using SaaS.Framework.DataTransfer; using System.Threading.Tasks;namespace SaaS.KDemo.Api.Controllers {[Route("api/[co…...

uniapp Echart X轴Y轴文字被遮挡怎么办,或未能铺满整个容器

有时候布局太小&#xff0c;使用echarts&#xff0c;x轴y轴文字容易被遮挡&#xff0c;怎么解决这个问题呢&#xff0c;或者是未能铺满整个容器。 方法1&#xff1a; 直接设置 containLabel 字段 options: { grid: { containLabel: true, },} 方法2: 间接设置&#xff0c;但是…...

学习路之PHP--laravel DingoApi

一、安装 1.进入项目目录&#xff0c;执行composer安装命令 composer require dingo/api 如果下载超时&#xff0c;换阿里云源&#xff1a; composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ 2.使用以下命令可以发布 API 的配置文件到 confi…...

项目篇——java文档搜索引擎

Java 文档搜索引擎 文章目录 Java 文档搜索引擎一、分词二、完成parser 类2.1、排除非html文件2.2、解析html以下是解析 HTML 标题的方法以下是解析 对应的 URL以下是解析 HTML的正文&#xff1a; 补充&#xff1a;倒序索引 三、实现 index 类3.1、实现索引结构3.2、索引中新增…...

5.2 磁盘CRC32完整性检测

CRC校验技术是用于检测数据传输或存储过程中是否出现了错误的一种方法&#xff0c;校验算法可以通过计算应用与数据的循环冗余校验&#xff08;CRC&#xff09;检验值来检测任何数据损坏。通过运用本校验技术我们可以实现对特定内存区域以及磁盘文件进行完整性检测&#xff0c;…...

企业内部安全与风控管理图解

企业内部安全说外部安全&#xff0c;企业领导者都非常关注&#xff0c;由于各方面原因&#xff0c;。。。力不从心&#xff0c;妥协&#xff01; 方向&#xff1a; 1、制度 结合企业实情&#xff0c;编制企业安全管理制度 2、硬件 处理常规硬件外观&#xff0c;加壳与锁定、…...

vscode基于cmake安装opencv库

一、安装相关依赖库 首先更新源 sudo apt update安装相关包 sudo apt-get install build-essential cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev libjasper若是报错&#xff1a;无法定位到 libjasper软件包 则依次执行以下命令 sud…...

Web 器学习笔记(基础)

Filter 过滤器 概念&#xff1a;表示过滤器&#xff0c;是 JavaWeb 三大组件&#xff08;Servlet、Filter、Listener&#xff09;之一 作用&#xff1a;顾名思义可以过滤资源的请求&#xff0c;并实现特殊的需求 Filter 接口及它核心的 doFilter() 方法&#xff08;执行前就是…...

uniapp中vue3使用uni.createSelectorQuery().in(this)报错

因为VUE3中使用setup没有this作用域&#xff0c;所以报错 解决办法&#xff1a;使用getCurrentInstance()方法获取组件实例 import { getCurrentInstance } from vue;const instance getCurrentInstance(); // 获取组件实例 const DOMArr uni.createSelectorQuery().in(ins…...

k8s-部署

1.k8s 集群与部署 更改所有主机名字和解析 k8s1 192.168.25.11 reg.westos.org,habbor 仓库 k8s2 192.168.25.12 master&#xff0c;k8s 集群控制节点 k8s3 192.168.25.13 node&#xff0c;k8s 集群工作节点 k8s4 192.168.25.14 node&#xff0c;k8s 集群工作节点 所有节…...

Arduino驱动MMA7260三轴加速度传感器(惯性测量传感器篇)

目录 1、传感器特性 2、控制器和传感器连线图 3、驱动程序 Arduino驱动MMA7260三轴加速度传感器芯片,可以应用到摩托车和汽车放倒报警、遥控航模、游戏手柄、人形机器人跌倒检测、硬盘冲击保护、倾斜度测量等场合。 1...

奇舞周刊第507期:通过 View Transition API 在状态之间添加丰富的过渡动画

记得点击文章末尾的“ 阅读原文 ”查看哟~ 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ 通过 View Transition API 在状态之间添加丰富的过渡动画 W3C 2023 年度全球技术大会 (TPAC2023) 于今年9月 11 - 15 日召开。W3C CSS 工作组成员 Bramus Van Damme(Google) 为本届…...

如何通过技术变现

技术变现是指将技术转化为实际价值的过程。以下是几种常见的技术变现方式&#xff1a; 软件开发与销售&#xff1a;根据市场需求开发软件&#xff0c;并将其销售给需要的企业或个人。专利许可与授权&#xff1a;将技术成果申请专利&#xff0c;通过专利许可和授权给企业使用&a…...

高效查询大量快递信息,轻松掌握技巧

在如今快节奏的生活中&#xff0c;快递已经成为我们日常不可或缺的一部分。然而&#xff0c;对于一些忙碌的人来说&#xff0c;单个查询每一个快递单号可能会浪费太多时间。因此&#xff0c;我们需要一款可以帮助我们批量查询快递的软件。 在市场上&#xff0c;有很多款专门用于…...

iperf3: error - unable to connect to server: No route to host 但嵌入式Linux设备

起因 需要测试WIFI设置为802.11n制式能否输出40MHZ带宽去做CE认证 需要一台设备WIFI 设置为STA模式 一台设备WIFI设置为AP模式 用STA模式的设备去连接AP模式的设备才能产生40MH带宽 起初用了一台设备做STA模式设备(设备A)来测试没问题了&#xff0c;要换一台设备做STA设备(设备…...

OpenCV自学笔记十七:傅里叶变换

1、Numpy实现傅里叶变换 傅里叶变换&#xff08;Fourier Transform&#xff09;是一种将信号从时域转换到频域的数学变换。它将一个连续或离散的时域信号分解为一组正弦和余弦函数的复合。 在Python中&#xff0c;可以使用NumPy库来实现傅里叶变换。具体步骤如下&#xff1a;…...

uniapp如何判断是哪个(微信/APP)平台

其实大家在开发uniapp项目的时候长长会遇到这样一个问题&#xff0c;就是针对某些小程序&#xff0c;没发去适配相关的功能&#xff0c;所以要针对不同的平台&#xff0c;进行不同的处理。 #ifdef &#xff1a; if defined 仅在某个平台编译 #ifndef &#xff1a; …...

网络安全——(黑客)自学

想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01;&#xff01;&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行

项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战&#xff0c;克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...