Linux高级IO
五种IO模型
具象化理解
IO:等 + 数据拷贝
read/recv:
1、等 - IO事件就绪 - 检测功能成分在里面
2、数据拷贝
问:什么叫做高效的IO?
答:单位时间,等的比重越小,IO的效率越高。
IO模型:钓鱼(等 + 钓)
1、张三:一直盯着鱼漂(自己等(阻塞),自己钓) ----- 阻塞式
2、李四:不定时一下鱼漂,同时干着自己当前要干的事情(自己等(非阻塞/轮询),自己钓) -----非阻塞式
3、王五:鱼竿上放一个叮当,当有鱼咬钩了就收杆查看(没有直接等(信号通知),自己钓) -----信号驱动式
4、赵六:有很多鱼竿,同时在钓鱼(来回在岸边检测所有鱼漂)(自己等(一次检测多个),自己钓) ------多路复用(多路转接)
5、田七:让别人(给他一个桶和电话)来帮他钓鱼,完成钓鱼的全过程,桶满了通过电话通知田七(没有自己等,没有自己钓,但是要拿结果) ------ 异步IO
问:什么叫做高效的钓鱼方式?
答:等的比重非常低,大部分时间都是在“钓鱼”。
问:上面的5种IO模型中,谁的效率最高?
答:赵六。
区分同步IO VS 异步IO
当进程在进行IO的场景的时候,如果该进程参与了IO的具体过程(自己调用read或者recv),就叫作同步IO。如果没有参与IO的细节(自己没有调用read或者recv),就叫作异步IO。
五种IO模型
- 阻塞IO是最常见的IO模型
- 非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码
- 信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作
- IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态
- 异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)
问:信号驱动和异步IO有什么区别?
答:信号的话需要进程参与IO种的拷贝过程,但是异步IO进程什么都不参与。
小结:
任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少。
高级IO重要概念
同步通信 vs 异步通信
同步和异步关注的是消息通信机制。
- 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回. 但是一旦调用返回,就得到返回值了; 换句话说,就是由调用者主动等待这个调用的结果;
- 异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果; 而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用.
另外, 我们回忆在讲多进程多线程的时候, 也提到同步和互斥. 这里的同步通信和进程之间的同步是完全不相互干的概念
- 进程/线程同步也是进程/线程之间直接的制约关系
- 是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、 传递信息所产生的制约关系. 尤其是在访问临界资源的时候
阻塞 vs 非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态
- 阻塞调用是指调用结果返回之前,当前线程会被挂起. 调用线程只有在得到结果之后才会返回.
- 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程
其他高级IO
非阻塞IO,纪录锁,系统V流机制,I/O多路转接(也叫I/O多路复用),readv和writev函数以及存储映射 IO(mmap),这些统称为高级IO
非阻塞IO
fcntl
一个文件描述符, 默认都是阻塞IO
函数原型如下:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
传入的cmd的值不同, 后面追加的参数也不相同
fcntl函数有5种功能:
- 复制一个现有的描述符(cmd=F_DUPFD).
- 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
- 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
- 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
- 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
实现函数SetNoBlock
基于fcntl, 我们实现一个SetNoBlock函数, 将文件描述符设置为非阻塞
#ifndef _UTIL_HPP
#define _UTIL_HPP
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
namespace Util
{//将文件描述符设置为非阻塞bool SetNonBlack(int sock){int flag = fcntl(sock, F_GETFL);if(flag == -1) return false;int n = fcntl(sock, F_SETFL, flag | O_NONBLOCK);if(n == -1) return false;return true;}
};
#endif
- 使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图).
- 然后再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数
轮询方式读取标准输入
using namespace std;
using namespace Util;
int main()
{//0SetNonBlack(0);char buffer[1024];while(true){sleep(1);scanf("%s", buffer);cout << "刚刚获取的内容是: " << buffer << endl; buffer[0] = '\0';}return 0;
}
I/O多路转接之select
初识select
系统提供select函数来实现多路复用输入/输出模型
- select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
- 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变(1、可读 2、可写 3、异常)
select函数原型
select的函数原型如下:
#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数解释:
参数nfds是需要监视的最大的文件描述符值+1(比如我们想要监视的文件描述符为1,3,5,7,那么我们需要设置的nfds就需要为8);
作用:该函数在底层实际上会进行一个遍历,实际上该参数决定的就是遍历的终止条件,限定一个区间即[0,nfds)。
readfds,writefds,exceptfds分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合。如果不需要检测就将其设置为NULL。
注意:fd_set就是位图结构。存储文件描述符。
输入输出型参数:
输入:
- readfds:用户告诉OS,我所设置的多个fd中的读事件是否已经就绪
输出:
- readfds:OS告诉用户,你让我检测的多个fd中,有哪些已经就绪了
参数timeout为结构timeval,用来设置select()的等待时间
参数timeout取值
- NULL:则表示select()没有timeout, select将一直被阻塞,直到某个文件描述符上发生了事件;
- 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
- 特定的时间值:如果在指定的时间段里没有事件发生, select将超时返回。
关于fd_set结构
其实这个结构就是一个整数数组, 更严格的说, 是一个 “位图”. 使用位图中对应的位来表示要监视的文件描述符
提供了一组操作fd_set的接口, 来比较方便的操作位图:
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
测试fd_set结构体大小
代码:
int main()
{// 是一种类型,位图类型,能定义变量,那么就一定有大小,就一定有上限fd_set fds;// fd_set是用位图表示多个fd的cout << sizeof(fds) << endl;
}
运行结果:
发现fd_set大小为128字节。总共有1024个比特位,即可以设置进1024个文件描述符。
关于timeval结构
timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为 0。
函数返回值
- 执行成功则返回文件描述符状态已改变的个数(只能通过遍历位图的方式进行检测)
- 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回(没有任何一个就绪)
- 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds, writefds, exceptfds和timeout的值变成不可预测
错误值可能为
- EBADF 文件描述词为无效的或该文件已关闭
- EINTR 此调用被信号所中断
- EINVAL 参数n 为负值
- ENOMEM 核心内存不足
常见的程序片段
fs_set readset;FD_SET(fd,&readset);select(fd+1,&readset,NULL,NULL,NULL);if(FD_ISSET(fd,readset)){……}
问:我们学的select和之前学的read/write/recv/send有什么区别吗?
答:我们前面学习的read/write/recv/send,无论是任何一种函数,他们参数中的文件描述符只有一个,所以他们除了自身所负责的将内核数据缓冲区中的数据拷贝到应用层缓冲区上之外,还要等待文件描述符是否就绪(事实上,这个时间所占的比重相当之大),这就造成了效率的严重下降,即他们负责的任务由拷贝变成了等待+拷贝。和这些函数不同的是,select负责的只有等待,他不负责任何的拷贝功能。
问:select在等待,等待的究竟是什么呢?
答:一般我们在等fd,其实是在等事件:1、读事件(将内核中的数据缓冲区中的数据拷贝到应用层缓冲区中来)2、写事件(将应用层缓冲区中的数据拷贝到内核中的数据缓冲区中,假如是send,拷贝到内核中的数据缓冲区之后,通过tcp或者udp协议发送到对方主机)3、异常事件。
事实上,这三种事件也分别对应着三个参数,即readfds、writefds、exceptfds。
问:能不能详细展开说一下readfds参数?
答:以读事件为例:
1、fd_set:文件描述符集合,以位图形式存储,比特位的位置代表fd的编号;比特位的内容代表“是否”的概念。
2、输入输出型参数:
输入(用户告诉内核):只需要帮我关心一下我所指定的fd,如果我指定的文件描述符有读事件就绪,就告诉我。
例如:0100 1110,0、4、5、7号文件描述符都不需要关心,只需要帮我关心一下1、2、3、6号文件描述符是否有读事件就绪即可。
输出(内核告诉用户):用户曾经让我关心的文件描述符,有哪些读事件已经就绪了,如果已经就绪,就将其比特位置为1,未就绪的文件描述符和用户不让我关心的文件描述符所对应的比特位都是0。
例如:0000 0010,首先先对位图结构进行清零操作,然后如果用户让我关心的文件描述符读事件已经就绪,就将该位图的比特位结构置为1,在这个例子,用户让我关心的1、2、3、6文件描述符中只有1号文件描述符读事件已经就绪,所以就将1号文件描述符的比特位置为1,其它的比特位均为0。
问:判断writefds和exceptfds是否就绪的条件是什么?
答:writefds:发送缓冲区是否有足够的空间能够让应用层把数据拷贝过去,如果没有足够的空间,就是写条件不就绪。
exceptfds:文件描述符发生了异常事件,如果发生了异常事件,就是异常条件就绪。
问:阻塞的等待策略都是有什么?
答:
1、阻塞等待
2、非阻塞等待:等待不到立马返回
3、设定deadline,在deadline之内阻塞式等待,超时立马返回。
注意:在deadline之内等待成功之后返回,会将剩余的时间通过timeout返回。该时间是一个绝对的时间段
socket就绪条件
读就绪
- socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
- socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
- 监听的socket上有新的连接请求;
- socket上有未处理的错误;
写就绪
- socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记 SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
- socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE 信号;
- socket使用非阻塞connect连接成功或失败之后;
- socket上有未读取的错误;
异常就绪
- socket上收到带外数据. 关于带外数据, 和TCP紧急模式相关(回忆TCP协议头中, 有一个紧急指针的字段)
select的特点
- 可监控的文件描述符个数取决与sizeof(fd_set)的值. 我这边服务器上sizeof(fd_set)=128,每bit表示一个文件 描述符,则我服务器上支持的最大文件描述符是128*8=1024.
- 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd
- 一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断
- 二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数
备注: fd_set的大小可以调整,可能涉及到重新编译内核
select代码练习
SelectServer.cpp
#include <sys/select.h>
#include <iostream>
#include "Sock.hpp"
#define NUM (sizeof(fd_set) * 8)
#define DFL -1
int fdsArray[NUM] = {0}; // 保存历史上所有的合法fd
int gnum = sizeof(fdsArray) / sizeof(fdsArray[0]);
using namespace std;
static void usage(std::string process)
{cerr << "\nUsage: " << process << " port\n"<< endl;
}static void showArray(int arr[], int num)
{cout << "当前合法sock list# ";for (int i = 0; i < num; i++){if (arr[i] == DFL)continue;elsecout << arr[i] << " ";}cout << endl;
}
// readfds:现在包含的就是已经就绪的sock
static void HandlerEvent(int listensock, fd_set &readfds)
{for (int i = 0; i < gnum; i++){if (fdsArray[i] == DFL)continue;if (i == 0 && fdsArray[i] == listensock){// 我们是如何得知哪些fd,上面的读事件就绪呢?if (FD_ISSET(listensock, &readfds)){// 具有了一个新链接cout << "已经有一个新连接到来了, 需要获取(读取/拷贝)了" << endl;string clientip;uint16_t clientport = 0;int sock = Sock::Accept(listensock, &clientip, &clientport); // 此处不会阻塞if (sock < 0)return;cout << "获取新连接成功: " << clientip << ":" << clientport << "| sock: " << sock << endl;// read/write -- 不能直接进行,因为我们read不知道底层是否就绪!只能通过select知道// 想办法把新的fd托管给selectint i = 0;for (; i < gnum; i++){if (fdsArray[i] == DFL)break;}if (i == gnum){cerr << "我的服务器已经到了最大的上限了,无法再承载更多同时保持的连接了" << endl;close(sock);}else{fdsArray[i] = sock; // 将sock添加到select中,进行进一步的监听了showArray(fdsArray, gnum);}}} // end if (i == 0 && fdsArray[i] == listensock)else{// 处理普通sock的IO事件if (FD_ISSET(fdsArray[i], &readfds)){// 一定是一个合法的普通的IO类sock就绪了// read/recv读取即可char buffer[1024];ssize_t s = recv(fdsArray[i], buffer, sizeof(buffer), 0); // 不会发生阻塞,因为fds[i]中的是已经就绪的if (s > 0){buffer[s] = 0;std::cout << "client[" << fdsArray[i] << "]# " << buffer << endl;}else if (s == 0){cout << "client[" << fdsArray[i] << "] quit, server close" << fdsArray[i] << endl;close(fdsArray[i]);fdsArray[i] = DFL; // 去除对该文件描述符的select事件监听showArray(fdsArray, gnum);}else{cout << "client[" << fdsArray[i] << "] error server close" << fdsArray[i] << endl;close(fdsArray[i]);fdsArray[i] = DFL; // 去除对该文件描述符的select事件监听showArray(fdsArray, gnum);}}}}
}
// ./SelectServer 8080
// 只关心读事件
int main(int argc, char *argv[])
{if (argc != 2){usage(argv[0]);exit(1);}// fd_set fds;// cout << sizeof(fds) << endl;int listensock = Sock::Socket();Sock::Bind(listensock, atoi(argv[1]));Sock::Listen(listensock);for (int i = 0; i < gnum; i++)fdsArray[i] = DFL;fdsArray[0] = listensock;while (true){// 在每次进行select的时候进行我们的参数重新设定int maxFd = DFL;fd_set readFds; // 读文件描述符集FD_ZERO(&readFds);for (int i = 0; i < gnum; i++){if (fdsArray[i] == DFL)continue; // 1. 过滤不合法的fdFD_SET(fdsArray[i], &readFds); // 2. 添加所有的合法的fd到readfds中,方便select同一进行if (maxFd < fdsArray[i])maxFd = fdsArray[i]; // 3. 更新出最大值}struct timeval timeout = {100, 0};// 如何看待监听套接字,获取新连接的,本质需要先三次握手,前提是给我发送syn -> 建立连接的本质,其实也是IO,一个建立好的连接// 我们称之为: 读事件就绪!listensocket只需要关心读事件就绪!// accept:等 + "数据拷贝"// int sock = Sock::Accept(listensock, );// 编写多路转接代码的时候,必须保证条件就绪了,才能调用IO类函数!int n = select(maxFd + 1, &readFds, nullptr, nullptr, &timeout);switch (n){case 0:cout << "time out ...:" << (unsigned long)time(nullptr) << endl;break;case -1:cerr << errno << " : " << strerror(errno) << endl;break;default:HandlerEvent(listensock, readFds);// 等待成功// 1. 刚启动的时候,只有一个fd,listensock// 2. server 运行的时候,sock才会慢慢变多// 3. select 使用位图,采用输入输出型参数的方式,来进行 内核<->用户信息 的传递,每一次调用select,都需要对历史数据和sock进行重新设置!// 4. listensock,永远都要被设置进readfds中// 5. select 就绪的时候,可能是listen就绪,也可能是普通的IO sock就绪了break;}}
}
Sock.hpp
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <cerrno>
#include <cassert>
class Sock
{
public:static const int gbacklog = 20;static int Socket(){int listenSock = socket(PF_INET, SOCK_STREAM, 0);if (listenSock < 0){exit(1);}int opt = 1;setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));return listenSock;}static void Bind(int socket, uint16_t port){struct sockaddr_in local; // 用户栈memset(&local, 0, sizeof local);local.sin_family = PF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;// 2.2 本地socket信息,写入sock_对应的内核区域if (bind(socket, (const struct sockaddr *)&local, sizeof local) < 0){exit(2);}}static void Listen(int socket){if (listen(socket, gbacklog) < 0){exit(3);}}static int Accept(int socket, std::string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len = sizeof(peer);int serviceSock = accept(socket, (struct sockaddr *)&peer, &len);if (serviceSock < 0){// 获取链接失败return -1;}if(clientport) *clientport = ntohs(peer.sin_port);if(clientip) *clientip = inet_ntoa(peer.sin_addr);return serviceSock;}
};
运行截图:
select总结
select编码特征
a. select之前要进行所有参数的重置,之后,要遍历所有的合法fd进行事件检测
b. select需要用户自己维护第三方数组,来保存所有的合法fd,方便select进行
fs_set readset; FD_SET(fd,&readset); // 事前添加 select(fd+1,&readset,NULL,NULL,NULL);// select if(FD_ISSET(fd,readset)){……}// 事后判断
c. 一旦特定的fd事件就绪,本次读取或者写入不会被阻塞
select优缺点
- 优点:占用资源少,并且高效(对比之前的多进程 和多线程)
- 缺点
- 每一次调用select之前都要进行大量的重置工作,效率比较低
- 每一次能够监听和检测的fd数量是有上限的
- 每一次都需要内核到用户,用户到内核传递位图参数,较为大量的数据拷贝工作
- select编码特别不方便,需要用户自己维护数组
- select底层需要通过遍历的方式,检测所有需要检测的fd(最大maxfd+1)
I/O多路转接之poll
poll的出现主要是为了解决select的两大缺点:
- select的输入和输出是同一个参数,所以每次进行select调用,都要对参数进行重新设定
- select所能等待的文件描述符是有上限的
poll函数接口
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// pollfd结构
struct pollfd {int fd; /* file descriptor: 文件描述符*/ short events; /* requested events: 请求事件,用户告诉内核*/short revents; /* returned events: 返回事件,内核告诉用户*/
};
参数说明
- fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返 回的事件集合
- nfds表示fds数组的长度
- timeout表示poll函数的超时时间, 单位是毫秒(ms)
events和revents的取值
上面最重要的就是POLLIN
和POLLOUT
。如果我们想要同时关心读和写,我们可以这样进行设置:POLLIN | POLLOUT
。
问:poll等待的数目是否存在上限?
答:不存在上限。
返回结果
- 返回值小于0, 表示出错;
- 返回值等于0, 表示poll函数等待超时;
- 返回值大于0, 表示poll由于监听的文件描述符就绪而返回
socket就绪条件
同select
poll的优点
不同与select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现
- pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比 select更方便
- poll并没有最大数量限制 (但是数量过大后性能也是会下降)
poll的缺点
poll中监听的文件描述符数目增多时
- 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符
- 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中
- 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降
代码练习
#include <sys/select.h>
#include <iostream>
#include <poll.h>
#include "Sock.hpp"
#define NUM 1024
#define DFL -1
struct pollfd fdsArray[NUM]; // 保存历史上所有的合法fd
using namespace std;
static void usage(std::string process)
{cerr << "\nUsage: " << process << " port\n"<< endl;
}static void showArray(struct pollfd arr[], int num)
{cout << "当前合法sock list# ";for (int i = 0; i < num; i++){if (arr[i].fd == DFL)continue;elsecout << arr[i].fd << " ";}cout << endl;
}
// readfds:现在包含的就是已经就绪的sock
static void HandlerEvent(int listensock)
{for (int i = 0; i < NUM; i++){if (fdsArray[i].fd == DFL)continue;if (i == 0 && fdsArray[i].fd == listensock){// 我们是如何得知哪些fd,上面的读事件就绪呢?if (fdsArray[i].revents & POLLIN){// 具有了一个新链接cout << "已经有一个新连接到来了, 需要获取(读取/拷贝)了" << endl;string clientip;uint16_t clientport = 0;int sock = Sock::Accept(listensock, &clientip, &clientport); // 此处不会阻塞if (sock < 0)return;cout << "获取新连接成功: " << clientip << ":" << clientport << "| sock: " << sock << endl;// read/write -- 不能直接进行,因为我们read不知道底层是否就绪!只能通过select知道// 想办法把新的fd托管给selectint i = 0;for (; i < NUM; i++){if (fdsArray[i].fd == DFL)break;}if (i == NUM){cerr << "我的服务器已经到了最大的上限了,无法再承载更多同时保持的连接了" << endl;close(sock);}else{fdsArray[i].fd = sock; // 将sock添加到select中,进行进一步的监听了fdsArray[i].events = POLLIN;fdsArray[i].revents = 0;showArray(fdsArray, NUM);}}} // end if (i == 0 && fdsArray[i] == listensock)else{// 处理普通sock的IO事件if (fdsArray[i].revents & POLLIN){// 一定是一个合法的普通的IO类sock就绪了// read/recv读取即可char buffer[1024];ssize_t s = recv(fdsArray[i].fd, buffer, sizeof(buffer), 0); // 不会发生阻塞,因为fds[i]中的是已经就绪的if (s > 0){buffer[s] = 0;std::cout << "client[" << fdsArray[i].fd << "]# " << buffer << endl;}else if (s == 0){cout << "client[" << fdsArray[i].fd << "] quit, server close" << fdsArray[i].fd << endl;close(fdsArray[i].fd);fdsArray[i].fd = DFL; // 去除对该文件描述符的select事件监听fdsArray[i].events = 0;fdsArray[i].revents = 0;showArray(fdsArray, NUM);}else{cout << "client[" << fdsArray[i].fd << "] error server close" << fdsArray[i].fd << endl;close(fdsArray[i].fd);fdsArray[i].fd = DFL; // 去除对该文件描述符的select事件监听showArray(fdsArray, NUM);}}}}
}
// ./SelectServer 8080
// 只关心读事件
int main(int argc, char *argv[])
{if (argc != 2){usage(argv[0]);exit(1);}// fd_set fds;// cout << sizeof(fds) << endl;int listensock = Sock::Socket();Sock::Bind(listensock, atoi(argv[1]));Sock::Listen(listensock);for (int i = 0; i < NUM; i++){fdsArray[i].fd = DFL;fdsArray[i].events = 0;fdsArray[i].revents = 0;}fdsArray[0].fd = listensock;fdsArray[0].events = POLLIN;int timeout = -1;while (true){// 在每次进行select的时候进行我们的参数重新设定// 如何看待监听套接字,获取新连接的,本质需要先三次握手,前提是给我发送syn -> 建立连接的本质,其实也是IO,一个建立好的连接// 我们称之为: 读事件就绪!listensocket只需要关心读事件就绪!// accept:等 + "数据拷贝"// int sock = Sock::Accept(listensock, );// 编写多路转接代码的时候,必须保证条件就绪了,才能调用IO类函数!int n = poll(fdsArray, NUM, timeout);switch (n){case 0:cout << "time out ...:" << (unsigned long)time(nullptr) << endl;break;case -1:cerr << errno << " : " << strerror(errno) << endl;break;default:HandlerEvent(listensock);// 等待成功// 1. 刚启动的时候,只有一个fd,listensock// 2. server 运行的时候,sock才会慢慢变多// 3. select 使用位图,采用输入输出型参数的方式,来进行 内核<->用户信息 的传递,每一次调用select,都需要对历史数据和sock进行重新设置!// 4. listensock,永远都要被设置进readfds中// 5. select 就绪的时候,可能是listen就绪,也可能是普通的IO sock就绪了break;}}
}
poll多路转接的问题
1、链接多的时候都是基于对多个fd进行遍历检测,来识别事件,链接多的时候,就一定会引起遍历周期的增加
2、对于事件(用户告诉内核,内核通知用户),需要使用的数据结构(数组)需要由程序员自己维护
I/O多路转接之epoll
epoll初识
按照man手册的说法: 是为处理大批量句柄而作了改进的poll.
它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44) 它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法
epoll的相关系统调用
epoll 有3个相关的系统调用
epoll_create
int epoll_create(int size);
创建一个epoll的句柄:
- 自从linux2.6.8之后,size参数是被忽略的
- 用完之后, 必须调用close()关闭
返回值:
成功返回文件描述符,失败返回-1
epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数:
- 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型
- 第一个参数是epoll_create()的返回值(epoll的句柄)
- 第二个参数表示动作,用三个宏来表示
- 第三个参数是需要监听的fd
- 第四个参数是告诉内核需要监听什么事
第二个参数的取值
- EPOLL_CTL_ADD :注册新的fd到epfd中(增加)
- EPOLL_CTL_MOD :修改已经注册的fd的监听事件(修改)
- EPOLL_CTL_DEL :从epfd中删除一个fd(删除)
struct epoll_event结构如下:
struct epoll_event {uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */
};
epoll_wait
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在epoll监控的事件中已经发送的事件
- 参数events是分配好的epoll_event结构体数组
- epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个 events数组中,不会去帮助我们在用户态中分配内存)
- maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size
- 参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞).
- 如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函 数失败
epoll工作原理
操作系统如何得知,网络中的数据到来了??网卡先得到数据,会向CPU发送硬件中断,调用OS预设的中断函数,负责从外设进行数据拷贝,从外设拷贝到内核缓冲区。
问:如何理解数据在硬件中流动?
答:通过高低电压发送电脉冲的方式,向内存写数据可以类比为向内存“充电”的过程。
epoll_create所做的工作:
创建epoll模型,包括红黑树+就绪队列+回调机制,文件系统当中有个struct_file结构体,该结构体可以设置其它的参数,比如文件类型,使其指向epoll模型,则通过文件描述符就可以找到这个epoll模型。
epoll_ctl所做的工作:
向红黑树中新增节点,修改就是查找红黑树,找到对应的红黑树节点并进行修改,删除就是删除对应的红黑树节点。(本质上epoll_ctl就是对红黑树的增删改)
epoll_wait所做的工作:
获取就绪事件。只需要检测就绪队列是否为空即可,事件复杂度为O(1),如果不为空就拿取节点。
问:epoll模型为什么高效?
答:1、epoll模型是操作系统通过红黑树结构来管理文件描述符及事件,管理效率很高 2、不需要操作系统每次不定期进行轮询检测文件描述符是否就绪了,通过回调机制自动形成就绪队列,通过回调的方式获取文件描述符和就绪事件,极大解放了操作系统的工作,提高了检测效率 3、就绪队列的出现,使我们获取就绪节点的时候避免了轮询的工作,直接从队列头部拿取就绪队列即可,提高了拿取效率(拿取就绪节点的时间复杂度O(1))
注意
网上有些博客说, epoll中使用了内存映射机制
- 内存映射机制: 内核直接将就绪队列通过mmap的方式映射到用户态. 避免了拷贝内存这样的额外性能开 销
这种说法是不准确的. 我们定义的struct epoll_event是我们在用户空间中分配好的内存. 势必还是需要将内核的数据拷贝到这个用户空间的内存中的
epoll工作方式
你妈喊你吃饭的例子
你正在吃鸡, 眼看进入了决赛圈, 你妈饭做好了, 喊你吃饭的时候有两种方式:
1. 如果你妈喊你一次, 你没动, 那么你妈会继续喊你第二次, 第三次...(亲妈, 水平触发)
2. 如果你妈喊你一次, 你没动, 你妈就不管你了(后妈, 边缘触发)
epoll有2种工作方式-水平触发(LT)和边缘触发(ET)。
LT:只要底层有数据,就会一直通知你(多路转接的默认模式),可以让程序员在编码的时候,可以暂时不把数据读取完毕,不用担心底层不通知你而导致的数据丢失的问题
ET:只要底层数据从无到有,从有到多,即变化的时候,才会通知你,倒逼程序员啊一旦收到通知,就必须将自己收到的数据从内核中全部读取完毕,否则可能会有数据丢失的风险
备注:
LT VS ET:谁更高效?ET比较高效,本质:让上层尽快取走数据的一种机制,可以给对方更大的窗口大小
问:为什么ET模式下,文件描述符的读取一定要设置为非阻塞模式?
答:注意,在ET模式下,我们并不知道数据有没有被读取完,所以我们只能一直读,直到读取出错,无法读取出数据来为止:
while(true) read();
如果最后一次读取是阻塞式,那么最后一次读取,就势必会导致read/recv阻塞住!所以在ET模式下,所有的fd、sock必须出于非阻塞模式。
假如有这样一个例子:
- 我们已经把一个tcp socket添加到epoll描述符
- 这个时候socket的另一端被写入了2KB的数据
- 调用epoll_wait,并且它会返回. 说明它已经准备好读取操作
- 然后调用read, 只读取了1KB的数据
- 继续调用epoll_wait…
水平触发Level Triggered 工作模式
epoll默认状态下就是LT工作模式
- 当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分
- 如上面的例子, 由于只读了1K数据, 缓冲区中还剩1K数据, 在第二次调用 epoll_wait 时, epoll_wait 仍然会立刻返回并通知socket读事件就绪
- 直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回
- 支持阻塞读写和非阻塞读写
边缘触发Edge Triggered工作模式
如果我们在第1步将socket添加到epoll描述符的时候使用了EPOLLET标志, epoll进入ET工作模式
- 当epoll检测到socket上事件就绪时,必须立刻处理
- 如上面的例子, 虽然只读了1K的数据, 缓冲区还剩1K的数据,在第二次调用
epoll_wait
的时候,epoll_wait
不会再返回了- 也就是说,ET模式下,文件描述符上的事件就绪后,只有一次处理机会.
- ET的性能比LT性能更高(
epoll_wait
返回的次数少了很多). Nginx默认采用ET模式使用epoll- 只支持非阻塞的读写
select和poll其实也是工作在LT模式下. epoll既可以支持LT, 也可以支持ET
对比LT和ET
LT是 epoll 的默认行为. 使用 ET 能够减少 epoll 触发的次数. 但是代价就是强逼着程序猿一次响应就绪过程中就把 所有的数据都处理完
相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些. 但是在 LT 情况下如果也能做到 每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的
另一方面, ET 的代码复杂程度更高了
1、如何写入呢?
读写事件要就绪
读:底层有数据 —> recv —> 底层没有数据,不会读取
写:底层有空间 —> send —> 底层如果没有空间,不应该再写入
注意: 默认我们只设置了让epoll帮我们关心读事件,没有关心写事件
为什么没有关注写事件:因为最开始的时候,写空间一定是就绪的!
运行中可能会存在空间不满足 --- 写空间被写满了
写:
1、如果在LT模式下,一定是要先检测有没有对应的空间(先打开对写事件的关心,epoll会自动进行事件派发),然后才写入。LT模式下,只要你打开了写,我们的代码会自动进行调用sender方法,进行发送。
2、如果是ET模式,也可以采用上面的方法。不过,一般ET我们追求高效,直接发送。通过发送是否全部完成,来决定是否要进行打开对写事件进行关心
- 先发送,发完就完了 – while(true)
- 先发送,如果没有发完(outbuffer),打开写事件关心,让epoll自动帮我们进行发送
注意:一般写事件关心,不能常打开,一定是需要的时候,在进行打开,不需要就要关闭对写事件的关心
当我们
Reactor
单进程:半异步半同步 – Reactor – Linux服务最常用 – 几乎没有之一
Reactor(tcp)服务器, 即负责事件派发, 又负责IO [又负责业务逻辑的处理]
Proactor: 前摄模式 – 其他平台可能出现的模式
只负责负责事件派发,就绪的事件推送给后端的进程、线程池, 不关心 负责IO [又负责业务逻辑的处理]
g++ -lboost_system -lboost_filesystem -std=c++11 -ljsoncpp serv
-std=c++11
相关文章:
Linux高级IO
五种IO模型 具象化理解 IO:等 数据拷贝 read/recv: 1、等 - IO事件就绪 - 检测功能成分在里面 2、数据拷贝 问:什么叫做高效的IO? 答:单位时间,等的比重越小,IO的效率越高。 IO模型&am…...
机器人的手眼标定——机器人抓取系统基础系列(五)
机器人的手眼标定——机器人抓取系统基础系列(五) 前言一、机器人标定相关概念1.1 内参标定和外参标定1.2 Eye-in-Hand 和 Eye-to-Hand1.3 ArUco二维码和棋盘格标定区别 二、机器人标定基本原理2.1 机器人抓取系统坐标系2.2 标定原理 三、标定步骤和注意…...
Android 图片加载框架:Picasso vs Glide
引言 在 Android 开发中,图片加载是移动应用的核心功能之一。合理选择图片加载框架不仅能提升用户体验,还能优化内存管理和应用性能。本文将深入对比 Picasso 和 Glide 两大主流框架,结合代码示例分析它们的差异、工作原理及优化策略。 1. …...
uniapp从 vue2 项目迁移到 vue3流程
以下是必须为迁移到 vue3 进行调整的要点,以便 vue2 项目可以在 vue3 上正常运行。 1. 在index.js中创建应用程序实例 // Before - Vue 2 import Vue from vue import App from ./App // with no need for vue3 Vue.config.productionTip false // vue3 is no lon…...
DeepSeek R1 本地部署指南 (2) - macOS 本地部署
上一篇: DeepSeek R1 本地部署指南 (1) - Windows 本地部署-CSDN博客 1.安装 Ollama Ollama https://ollama.com/ 点击 Download - Download for macOS 解压下载 zip 启动程序 3. 选择版本 DeepSeek R1 版本 deepseek-r1 https://ollama.com/library/deepseek-r1 模…...
DeepSeek技术架构解析:MoE混合专家模型
一、前言 2025年初,DeepSeek V3以557万美元的研发成本(仅为GPT-4的1/14)和开源模型第一的排名,在全球AI领域掀起波澜。其核心创新之一——混合专家模型(Mixture of Experts, MoE)的优化设计,不…...
Ubuntu实时读取音乐软件的音频流
文章目录 一. 前言二. 开发环境三. 具体操作四. 实际效果 一. 前言 起因是这样的,我需要在Ubuntu中,实时读取正在播放音乐的音频流,然后对音频进行相关的处理。本来打算使用的PipewireHelvum的方式实现,好处是可以直接利用Helvum…...
2025年2月-3月后端go开发找工作感悟
整体感悟 目标 找工作首先要有一个目标,这个目标尽可能的明确,比如我要字节、拼多多之类的公司,还是要去百度、滴滴这样的,或者目标是创业公司。但是这个目标是会动态调整的,有可能我们的心态发生了变化,一…...
OpenCV图像拼接(1)自动校准之校准旋转相机的函数calibrateRotatingCamera()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::detail::calibrateRotatingCamera 是OpenCV中用于校准旋转相机的函数。它特别适用于那种相机相对于一个固定的场景进行纯旋转运动的情况&…...
【极速版 -- 大模型入门到进阶】快速了解大型语言模型
文章目录 🌊 大模型作为一种生成式人工智慧,厉害在哪儿?-> 通用能力🌊 LLM 如何生成输出:简而言之就是文字接龙🌊 GPT 之前 ...:模型规模和数据规模概览🌊 ChatGPT 有三个训练阶段…...
MySQL 锁机制详解
MySQL 锁机制详解 5.1 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、 RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有 效性是所有数…...
牛客网【模板】二维差分(详解)c++
题目链接:【模板】二维差分 1.题目分析 类比一下,因为差分因为差分是在数组里的某一段同时加上一个K二维是在二维数组中选择一个词矩阵,让词矩阵中每一个元素都加上一个K 2.算法原理 解法-:暴力解法 -> 模拟 你告诉我一个左上角和右下…...
从0到1彻底掌握Trae:手把手带你实战开发AI Chatbot,提升开发效率的必备指南!
我正在参加Trae「超级体验官」创意实践征文, 本文所使用的 Trae 免费下载链接: www.trae.ai/?utm_source… 前言 大家好,我是小Q,字节跳动近期推出了一款 AI IDE—— Trae,由国人团队开发,并且限时免费体…...
【清华大学】AIGC发展研究(3.0版)
目录 AIGC发展研究报告核心内容一、团队简介二、AI哲学三、国内外大模型四、生成式内容(一)文本生成(二)图像生成(三)音乐生成(四)视频生成 五、各行业应用六、未来展望 AIGC发展研究…...
Kafka--常见问题
1.为什么要使用 Kafka,起到什么作用 Kafka是一个高吞吐量、分布式、基于发布订阅的消息系统,它主要用于处理实时数据流 Kafka 设计上支持高吞吐量的消息传输,每秒可以处理数百万条消息。它能够在处理大量并发请求时,保持低延迟和…...
maptalks图层交互 - 模拟 Tooltip
maptalks图层交互 - 模拟 Tooltip 图层交互-模拟tooltip官方文档 <!DOCTYPE html> <html><meta charsetUTF-8 /><meta nameviewport contentwidthdevice-width, initial-scale1 /><title>图层交互 - 模拟 Tooltip</title><style typet…...
【前端】Visual Studio Code安装配置教程:下载、汉化、常用组件、基本操作
文章目录 一、Visual Studio Code下载二、汉化三、常用组件1、Auto Rename Tag2、view-in-browser3、Live Server 四、基本操作五、感谢观看! 一、Visual Studio Code下载 下载官网:https://code.visualstudio.com/ 进入官网后点击右上角的Download &…...
datetime“陷阱”与救赎:扒“时间差值”证道
时间工具陷阱,其实是工具引用的误解。 笔记模板由python脚本于2025-03-23 23:32:58创建,本篇笔记适合时间工具研究的coder翻阅。 【学习的细节是欢悦的历程】 博客的核心价值:在于输出思考与经验,而不仅仅是知识的简单复述。 Pyth…...
3DMAX曲线生成器插件CurveGenerator使用方法
1. 脚本功能简介 3DMAX曲线生成器插件CurveGenerator是一个用于 3ds Max 的样条线生成工具,用户可以通过简单的UI界面输入参数,快速生成多条样条线。每条样条线的高度值随机生成,且可以自定义以下参数: 顶点数量:每条…...
Apache漏洞再现
CVE-2021-41773路径穿越漏洞 1、开环境 sudo docker pull blueteamsteve/cve-2021-41773:no-cgid sudo docker run -dit -p 8082:80 blueteamsteve/cve-2021-41773:no-cgid 2、访问8082端口 3、打开工具 4、输入网址,检测漏洞...
git,openpnp - 根据安装程序打包名称找到对应的源码版本
文章目录 git,openpnp - 根据安装程序打包名称找到对应的源码版本概述笔记备注 - 提交时间不可以作为查找提交记录的依据END git,openpnp - 根据安装程序打包名称找到对应的源码版本 概述 想在openpnp官方最新稳定版上改一改,首先就得知道官方打包的安装程序对应的…...
SQL Server查询计划操作符(7.3)——查询计划相关操作符(11)
7.3. 查询计划相关操作符 98)Table Scan:该操作符从查询计划参数列确定的表中获取所有数据行。如果其参数列中出现WHERE:()谓词,则只返回满足该谓词的数据行。该操作符为逻辑操作符和物理操作符。该操作符具体如图7.3-98节点1所示。 图 7.3-…...
编译原理——词法分析
文章目录 词法分析:从基础到自动构造一、词法分析程序的设计一、词法分析程序的设计二、PL/0编译程序中词法分析程序的设计与实现1. 语法特定考量2. 通过状态转移表运用有限状态自动机3. 示例代码片段(用于说明的伪代码) 三、单词的形式化描述…...
Linux内核,内存分布
x86_64的物理地址范围为64bit,但是因为地址空间太大目前不可能完全用完,当前支持57bit和48bit两种虚拟地址模式。 地址模式单个空间用户地址空间内核地址空间32位2G0x00000000 - 0x7FFFFFFF0x80000000 - 0xFFFFFFFF64位(48bit)128T0x00000000 00000000 …...
AI鸟类识别技术革新生态监测:快瞳科技如何用“智慧之眼”守护自然?
在生态环境保护日益受关注的今天,“鸟类识别”已从专业科研工具演变为推动生态治理数字化的核心技术。无论是湿地保护区的珍稀候鸟监测,还是城市机场的鸟击风险预警,AI技术的精准赋能正在改写人类与自然的互动方式。作为行业领先的智能解决方…...
c++之set
一、set特性及用途? 唯一性:set 中的元素是唯一的,不会存在重复的元素。自动排序:set 中的元素会自动按照默认的升序规则进行排序。底层实现:set 通常基于红黑树实现,具有自平衡功能,因此插入、…...
【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解
目录 一、前言 二、AI视频概述 2.1 什么是AI视频 2.2 AI视频核心特点 2.3 AI视频应用场景 三、通义万相介绍 3.1 通义万相概述 3.1.1 什么是通义万相 3.2 通义万相核心特点 3.3 通义万相技术特点 3.4 通义万相应用场景 四、DeepSeek 通义万相制作AI视频流程 4.1 D…...
【操作系统】自旋锁和互斥锁
自旋锁和互斥锁是用于多线程同步的两种常见锁机制,主要区别在于等待锁的方式和适用场景。以下是它们的对比分析: 1. 等待机制 自旋锁(Spinlock)互斥锁(Mutex)线程通过 忙等待(Busy-Wait&#x…...
人工智能在医疗影像诊断中的应用与实践
引言 随着人工智能技术的飞速发展,其在医疗领域的应用逐渐成为研究和实践的热点。特别是在医疗影像诊断方面,人工智能技术凭借其强大的数据处理能力和模式识别能力,为提高诊断效率和准确性带来了新的希望。本文将探讨人工智能在医疗影像诊断中…...
Java中synchronized 和 Lock
1. synchronized 关键字 工作原理 对象锁:在Java中,每个对象都有一个与之关联的监视器锁(monitor lock)。当一个线程尝试进入由 synchronized 保护的代码块或方法时,它必须首先获取该对象的监视器锁。如果锁已经被其…...
【C语言系列】数据在内存中存储
数据在内存中存储 一、整数在内存中的存储二、大小端字节序和字节序判断2.1什么是大小端?2.2练习2.2.1练习12.2.2练习22.2.3练习32.2.4练习42.2.5练习52.2.6练习6 三、浮点数在内存中的存储3.1练习3.2浮点数的存储3.2.1 浮点数存的过程3.2.2 浮点数取的过程 3.3题目…...
qt 对QObject::tr()函数进行重定向
在 Qt 中,QObject::tr() 函数用于国际化(i18n),它用于标记需要翻译的字符串。通常情况下,tr() 函数会从翻译文件(如 .qm 文件)中查找对应的翻译字符串。如果你希望重定向 tr() 函数的行为&#…...
C#基础学习(三)值类型和引用类型:编程世界的“现金“ vs “银行卡“,以及string这个“渣男“的叛变行为
开场白 各位程序猿/媛们,今天我们来聊一聊编程世界里的"金钱观"。 你以为只有人类会纠结现金和存款的区别?不不不,C#中的值类型和引用类型每天都在上演这场大戏! 而我们的string同学,表面是…...
自动驾驶背后的数学:多模态传感器融合的简单建模
上一篇博客自动驾驶背后的数学:特征提取中的线性变换与非线性激活 以单个传感器为例,讲解了特征提取中的线性变换与非线性激活。 这一篇将以多模态传感器融合为例,讲解稍复杂的线性变换和非线性激活应用场景。 (一)权重矩阵的张量积分解 y = W x + b = [ w 11 ⋯ w 1 n ⋮…...
如何设置sudo权限
打开终端:按 Ctrl Alt T 打开终端。 编辑 sudoers 文件: 使用 visudo 命令编辑 /etc/sudoers 文件(visudo 会检查语法,避免错误): sudo visudo 添加用户权限: 在文件中找到以下行࿱…...
Codeforces Round 1012 (Div. 2) 3.23
文章目录 2025.3.23 Div2B. Pushing Balls(暴力)代码 C. Dining Hall题意思路代码 2025.3.23 Div2 Dashboard - Codeforces Round 1012 (Div. 2) - Codeforces B. Pushing Balls(暴力) 题意很好懂,每一行每一列从左…...
langfuse追踪Trace
介绍 🧠 Langfuse 是什么? Langfuse 是一个专门为 LLM 应用(如 OpenAI / LangChain / 自定义 Agent) 设计的 观测与追踪平台(Observability Platform)。 简单说,它就像是你为 AI 应用插上的 “…...
Java-模块二-2
整数类型 byte:在 Java 中占用8位(1字节),因此它的取值范围是从 -128 到 127。这是最小的整数类型,适合用于节省空间的情况。 short:这种类型的大小是16位(2字节),允许的…...
使用VS2022编译CEF
前提 选择编译的版本 CEF自动编译,在这里可以看到最新的稳定版和Beta版。 从这里得出,最新的稳定版是134.0.6998.118,对应的cef branch是6998。通过这个信息可以在Build requirements查到相关的软件配置信息。 这里主要看Windows下的编译要…...
大模型RLHF训练-PPO算法详解:Proximal Policy Optimization Algorithms
一、TL;DR 提出了一种新的策略梯度方法家族,用于强化学习,这些方法交替进行与环境交互采样数据提出了一个新的目标函数,使得能够进行多个小批量更新的多轮训练这些新方法为近端策略优化(Proximal Policy Optimization…...
【STM32实物】基于STM32的扫地机器人/小车控制系统设计
基于STM32的扫地机器人/小车控制系统设计 演示视频: 基于STM32的扫地机器人小车控制系统设计 简介:扫地机器人系统采用分层结构设计,主要包括底层硬件控制层、中间数据处理层和上层用户交互层。底层硬件控制层负责对各个硬件模块进行控制和数据采集,中间数据处理层负责对采…...
【C++初阶】从零开始模拟实现vector(含迭代器失效详细讲解)
目录 1、基本结构 1.1成员变量 1.2无参构造函数 1.3有参构造函数 preserve()的实现 代码部分: push_back()的实现 代码部分: 代码部分: 1.4拷贝构造函数 代码部分: 1.5支持{}初始化的构造函数 代码部分: …...
AI比人脑更强,因为被植入思维模型【21】冯诺依曼思维模型
定义 冯诺依曼思维模型是一种基于数理逻辑和系统分析的思维方式,它将复杂的问题或系统分解为若干个基本的组成部分,通过建立数学模型和逻辑规则来描述和分析这些部分之间的关系,进而实现对整个系统的理解和优化。该模型强调从整体到局部、再…...
Keil5调试技巧
一、引言 Keil5作为一款广泛应用于嵌入式系统开发的集成开发环境(IDE),在微控制器编程领域占据着重要地位。它不仅提供了强大的代码编辑和编译功能,还具备丰富的调试工具,帮助开发者快速定位和解决代码中的问题。本文…...
Web PKI现行应用、标准
中国现行 Web PKI 标准 中国在 Web PKI(公钥基础设施)领域制定了多项国家标准,以确保网络安全和数字证书管理的规范性。以下是一些现行的重要标准: 1. GB/T 21053-2023《信息安全技术 公钥基础设施 PKI系统安全技术要求》 该标…...
ROS多机通信(四)——Ubuntu 网卡 Mesh 模式配置指南
引言 使用Ad-hoc加路由协议和直接Mesh模式配置网卡实现的网络结构是一样的,主要是看应用选择, Ad-Hoc模式 B.A.T.M.A.N. / OLSR 优点:灵活性高,适合移动性强或需要优化的复杂网络。 缺点:配置复杂,需手动…...
【实用部署教程】olmOCR智能PDF文本提取系统:从安装到可视化界面实现
文章目录 引言系统要求1. 环境准备:安装Miniconda激活环境 2. 配置pip源加速下载3. 配置学术加速(访问国外资源)4. 安装系统依赖5. 安装OLMOCR6. 运行OLMOCR处理PDF文档7. 理解OLMOCR输出结果9. 可视化UI界面9.1 安装界面依赖9.2 创建界面应用…...
STM32单片机uCOS-Ⅲ系统11 中断管理
目录 一、异常与中断的基本概念 1、中断的介绍 2、和中断相关的名词解释 二、中断的运作机制 三、中断延迟的概念 四、中断的应用场景 五、中断管理讲解 六、中断延迟发布 1、中断延迟发布的概念 2、中断队列控制块 3、中断延迟发布任务初始化 OS_IntQTaskInit() 4…...
CTF【WEB】学习笔记1号刊
Kali的小工具箱 curl www.xxx.com:查看服务器响应返回的信息 curl -I www.xxx.com:查看响应的文件头 一、cmd执行命令 ipconfig:ip地址配置等; 二、 Kali操作 1.sudo su; 2.msfconsole 3.search ms17_010 永恒之蓝ÿ…...
cpp-友元
理解 C 中的友元(Friend) 在 C 语言中,封装(Encapsulation) 是面向对象编程的重要特性之一。它允许类将数据隐藏在私有(private)或受保护(protected)成员中,…...