linux 高级IO

IO=等(要进行io是要有条件的,要有数据或者有空间)+拷贝。高效体现在等待的时间所占比重越低越高效。

阻塞IO:数据没有就绪,read不返回。在内核将数据准备好之前, 系统调用会一直等待。所有的套接字, 默认都是阻塞方式。

非阻塞IO:非阻塞轮询,如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码。

信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作。

IO多路转接: 虽然从流程图上看起来和阻塞IO类似,实际上最核心在于IO多路转接能够同时等待多个文件 描述符的就绪状态。

异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。

同步IO和异步IO的区别在于是否参与了IO的过程。

非阻塞

void SetNonBlock(int fd)
{int fl = fcntl(fd, F_GETFL);//获取文件状态标记if (fl < 0){std::cerr << "error string: " << strerror(errno) << " error code: " << errno << std::endl;return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);//设置状态,成为非阻塞。
}int main()
{char buffer[64];SetNonBlock(0);LoadTask();while (true){printf(">>> ");fflush(stdout);// 1. 成功  2. 结束  3. 出错(一旦底层没有数据就绪,以出错的形式返回,但是不算真正的出错,因为一旦fd被设置成为非阻塞,会有底层数据没有就绪的情况)ssize_t n = read(0, buffer, sizeof(buffer) - 1); // (检测条件是否就绪)等 + 拷贝if (n > 0){buffer[n - 1] = 0;std::cout << "echo# " << buffer << std::endl;}else if (n == 0)//读到文件结尾{std::cout << "end file" << std::endl;break;}else{if (errno == EAGAIN || errno == EWOULDBLOCK)//{// 底层数据没有准备好,希望你下次继续来检测HandlerALLTask();//底层数据没就绪就做别的事情sleep(1);std::cout << "data not ready" << std::endl;continue;}else if (errno == EINTR){// 这次IO被信号中断,也需要重新读取continue;}else{// 一旦fd被设置成为非阻塞,std::cerr << "read error? "<< "error string: " << strerror(errno) << " error code: " << errno << std::endl;break;}// break;}}
}

多路转接

#include <iostream>
#include <string>
#include <cstring>
#include <sys/select.h>
#include "Sock.hpp"
#include "Log.hpp"const static int gport = 8888;typedef int type_t;
// static const int defaultfd = -1; // 暂时class SelectServer
{static const int N = (sizeof(fd_set) * 8);public:SelectServer(uint16_t port = gport) : port_(port){}void InitServer(){listensock_.Socket();listensock_.Bind(port_);listensock_.Listen();for (int i = 0; i < N; i++)fdarray_[i] = defaultfd;}void Accepter(){// std::cout << "有一个新连接到来了" << std::endl;// Accept, 这里在进行Accept会不会被阻塞??不会的!因为我们已经前面已经确认listen套接字有新的连接到来了std::string clientip;uint16_t clientport;int sock = listensock_.Accept(&clientip, &clientport);if (sock < 0)return;// 得到了对应的sock, 我们可不可以进行read/recv,读取sock?不能// 你怎么知道sock上有数据就绪了?不知道,所以我们需要将sock交给select,让select进行管理!logMessage(Debug, "[%s:%d], sock: %d", clientip.c_str(), clientport, sock);// 要让select 帮我们进行关心,只要把sock添加到fdarray_[]里面即可!int pos = 1;for (; pos < N; pos++){if (fdarray_[pos] == defaultfd)break;}if (pos >= N)//说明遍历整个数组,没有找到一个空余的位置容纳套接字,服务器也没有能力去处理新连接了,直接close掉。{close(sock);logMessage(Warning, "sockfd array[] full");}else{fdarray_[pos] = sock;//找到空余位置了}}// echo servervoid HandlerEvent(fd_set &rfds){for (int i = 0; i < N; i++){if (fdarray_[i] == defaultfd)continue;if ((fdarray_[i] == listensock_.Fd()) && FD_ISSET(listensock_.Fd(), &rfds))//如果当前遍历的文件描述符是listen套接字并且在就绪的文件描述符集合里,我们获取连接{Accepter();}else if ((fdarray_[i] != listensock_.Fd()) && FD_ISSET(fdarray_[i], &rfds))//否则的话就是普通文件描述符就绪,可以开始读了。{// ServiceIO();// BUGint fd = fdarray_[i];char buffer[1024];ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0); // 读取会被阻塞吗?不会if (s > 0){buffer[s-1] = 0;std::cout << "client# " << buffer << std::endl;// 发送回去也要被select管理的,因为如何知道发送缓冲区也是就绪的了,发送缓冲区一旦打满了就只能阻塞了。TODAstd::string echo = buffer;echo += " [select server echo]";send(fd, echo.c_str(), echo.size(), 0);}else{if (s == 0)//说明客户端将连接关闭了logMessage(Info, "client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);elselogMessage(Warning, "recv error, client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);close(fdarray_[i]);fdarray_[i] = defaultfd;}}}}void Start(){// 1. 这里我们能够直接获取新的链接吗?// 2. 最开始的时候,我们的服务器是没有太多的sock的,甚至只有一个sock!listensock// 3.在最开始accept的时候,我们是无法知道已经有连接准备就绪的,所以这里如果直接accept就成了阻塞等待了,// 4. 在网络中, 新连接到来被当做读事件就绪!连接就绪我们要做的就是accept而不是read// listensock_.Accept(); 不能!// demo1fdarray_[0] = listensock_.Fd();while (true){// struct timeval timeout = {0, 0};// 因为rfds是一个输入输出型参数,注定了每次都要对rfds进行重置,重置,必定要知道我历史上都有哪些fd?fdarray_[]// 因为服务器在运行中,accept得到的sockfd的值一直在动态变化,所以maxfd也一定在变化, maxfd是不是也要进行动态更新,也要依赖fdarray_[]fd_set rfds;FD_ZERO(&rfds);int maxfd = fdarray_[0];for (int i = 0; i < N; i++){if (fdarray_[i] == defaultfd)continue;// 合法fdFD_SET(fdarray_[i], &rfds);if (maxfd < fdarray_[i])maxfd = fdarray_[i];}int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);switch (n){case 0:logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));break;case -1:logMessage(Warning, "%d: %s", errno, strerror(errno));break;default:// 成功了logMessage(Debug, "有一个就绪事件发生了: %d", n);//就绪事件不一定是listen套接字的新连接就绪了,也有可能是别的套接字数据已经就绪了,可以开始读了。HandlerEvent(rfds);DebugPrint();break;}// sleep(1);}}void DebugPrint(){std::cout << "fdarray[]: ";for (int i = 0; i < N; i++){if (fdarray_[i] == defaultfd)continue;std::cout << fdarray_[i] << " ";}std::cout << "\n";}~SelectServer(){listensock_.Close();}private:uint16_t port_;Sock listensock_;type_t fdarray_[N];// 管理所有的文件描述符fd,select服务器使用的时候需要程序员来维护一个第三方数组来进行对已经获得的socket进行管理。
};
//该版本关注读写事件
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <sys/select.h>
#include "Sock.hpp"
#include "Log.hpp"const static int gport = 8888;#define READ_EVENT (0x1)
#define WRITE_EVENT (0x1 << 1)
#define EXCEPT_EVENT (0x1 << 2)typedef struct FdEvent//一个文件描述符有可能关心读事件也有可能关心写事件
{int fd;uint8_t event;std::string clientip;uint16_t clientport;
} type_t;// static const int defaultfd = -1; // 暂时
static const int defaultevent = 0;class SelectServer
{static const int N = (sizeof(fd_set) * 8);public:SelectServer(uint16_t port = gport) : port_(port){}void InitServer(){listensock_.Socket();listensock_.Bind(port_);listensock_.Listen();for (int i = 0; i < N; i++){fdarray_[i].fd = defaultfd;fdarray_[i].event = defaultevent;fdarray_[i].clientport = 0;//string默认就是空的}}void Accepter(){// std::cout << "有一个新连接到来了" << std::endl;// Accept, 这里在进行Accept会不会被阻塞??不会的!std::string clientip;uint16_t clientport;int sock = listensock_.Accept(&clientip, &clientport);if (sock < 0)return;// 得到了对应的sock, 我们可不可以进行read/recv,读取sock?不能// 你怎么知道sock上有数据就绪了?不知道,所以我们需要将sock交给select,让select进行管理!logMessage(Debug, "[%s:%d], sock: %d", clientip.c_str(), clientport, sock);// 要让select 帮我们进行关心,只要把sock添加到fdarray_[]里面即可!int pos = 1;for (; pos < N; pos++){if (fdarray_[pos].fd == defaultfd)break;}if (pos >= N){close(sock);logMessage(Warning, "sockfd array[] full");}else{fdarray_[pos].fd = sock;// fdarray_[pos].event = (READ_EVENT | WRITE_EVENT);fdarray_[pos].event = READ_EVENT;fdarray_[pos].clientip = clientip;fdarray_[pos].clientport = clientport;}}void Recver(int index){// ServiceIO();// BUGint fd = fdarray_[index].fd;char buffer[1024];ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0); // 读取会被阻塞吗?不会if (s > 0){buffer[s - 1] = 0;std::cout << fdarray_[index].clientip << ":" << fdarray_[index].clientport << "# " << buffer << std::endl;// 发送回去也要被select管理的,TODOstd::string echo = buffer;echo += " [select server echo]";send(fd, echo.c_str(), echo.size(), 0);}else{if (s == 0)logMessage(Info, "client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);elselogMessage(Warning, "recv error, client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);close(fdarray_[index].fd);fdarray_[index].fd = defaultfd;fdarray_[index].event = defaultevent;fdarray_[index].clientip.resize(0);fdarray_[index].clientport = 0;}}// echo servervoid HandlerEvent(fd_set &rfds, fd_set &wfds){for (int i = 0; i < N; i++){if (fdarray_[i].fd == defaultfd)continue;if ((fdarray_[i].event & READ_EVENT) && (FD_ISSET(fdarray_[i].fd, &rfds)))//如果文件描述符想关心读事件并且读事件发生了{// 处理读取,1. accept 2. recvif (fdarray_[i].fd == listensock_.Fd()){Accepter();}else if (fdarray_[i].fd != listensock_.Fd()){Recver(i);}else{}}else if((fdarray_[i].event & WRITE_EVENT) && (FD_ISSET(fdarray_[i].fd, &wfds))){//TODO}else {}}}void Start(){// 1. 这里我们能够直接获取新的链接吗?// 2. 最开始的时候,我们的服务器是没有太多的sock的,甚至只有一个sock!listensock// 3. 在网络中, 新连接到来被当做 读事件就绪!// listensock_.Accept(); 不能!// demo1fdarray_[0].fd = listensock_.Fd();fdarray_[0].event = READ_EVENT;//listen套接字只需要关心读事件,不关心clientip和clientportwhile (true){// struct timeval timeout = {0, 0};// 因为rfds是一个输入输出型参数,注定了每次都要对rfds进行重置,重置,必定要知道我历史上都有哪些fd?fdarray_[]// 因为服务器在运行中,sockfd的值一直在动态变化,所以maxfd也一定在变化, maxfd是不是也要进行动态更新, fdarray_[]fd_set rfds;//设定两个文件描述符集合,一个关心读事件fd_set wfds;//一个关心写事件FD_ZERO(&rfds);FD_ZERO(&wfds);int maxfd = fdarray_[0].fd;for (int i = 0; i < N; i++){if (fdarray_[i].fd == defaultfd)continue;// 合法fdif (fdarray_[i].event & READ_EVENT)//关心读事件FD_SET(fdarray_[i].fd, &rfds);if (fdarray_[i].event & WRITE_EVENT)//关心写事件FD_SET(fdarray_[i].fd, &wfds);if (maxfd < fdarray_[i].fd)maxfd = fdarray_[i].fd;}int n = select(maxfd + 1, &rfds, &wfds, nullptr, nullptr);//现在select会关心读写两个事件,之前只关心读事件。switch (n){case 0:logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));break;case -1:logMessage(Warning, "%d: %s", errno, strerror(errno));break;default:// 成功了logMessage(Debug, "有一个就绪事件发生了: %d", n);HandlerEvent(rfds, wfds);DebugPrint();break;}// sleep(1);}}void DebugPrint(){std::cout << "fdarray[]: ";for (int i = 0; i < N; i++){if (fdarray_[i].fd == defaultfd)continue;std::cout << fdarray_[i].fd << " ";}std::cout << "\n";}~SelectServer(){listensock_.Close();}private:uint16_t port_;Sock listensock_;type_t fdarray_[N]; // 管理所有的fd
};

select的缺点:每次调用select,都需要手动设置fd集合,从接口使用角度来说也非常不便; 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大;同时每次调用select都需要在内核遍历传递进来的所有fd;这个开销在fd很多时也很大;select支持的文件描述符数量太小。

//poll解决了select支持的文件描述符数量太小存在上限的问题
#include <iostream>
#include <string>
#include <cstring>
#include <sys/poll.h>
#include "Sock.hpp"
#include "Log.hpp"const static int gport = 8888;
const static int N = 4096;
const static short defaultevent = 0;
typedef struct pollfd type_t;class PollServer
{public:PollServer(uint16_t port = gport) : port_(port), fdarray_(nullptr){}void InitServer(){listensock_.Socket();listensock_.Bind(port_);listensock_.Listen();fdarray_ = new type_t[N]; //现在空间没有上限了for (int i = 0; i < N; i++){fdarray_[i].fd = defaultfd;fdarray_[i].events = defaultevent;fdarray_[i].revents = defaultevent;}}void Accepter(){// std::cout << "有一个新连接到来了" << std::endl;// Accept, 这里在进行Accept会不会被阻塞??不会的!std::string clientip;uint16_t clientport;int sock = listensock_.Accept(&clientip, &clientport);if (sock < 0)return;// 得到了对应的sock, 我们可不可以进行read/recv,读取sock?不能// 你怎么知道sock上有数据就绪了?不知道,所以我们需要将sock交给select,让select进行管理!logMessage(Debug, "[%s:%d], sock: %d", clientip.c_str(), clientport, sock);// 要让select 帮我们进行关心,只要把sock添加到fdarray_[]里面即可!int pos = 1;for (; pos < N; pos++)//fdarray_数组{if (fdarray_[pos].fd == defaultfd)break;}if (pos >= N){close(sock); // poll -> 动态扩容,扩容失败logMessage(Warning, "sockfd array[] full");}else//找到了闲置的位置{fdarray_[pos].fd = sock;fdarray_[pos].events = POLLIN; // (POLLIN|POLLOUT)fdarray_[pos].revents = defaultevent;}}// echo servervoid HandlerEvent(){for (int i = 0; i < N; i++){int fd = fdarray_[i].fd;short revent = fdarray_[i].revents;if (fd == defaultfd)continue;if ((fd == listensock_.Fd()) && (revent & POLLIN))//如果是listen套接字并且连接事件就绪开始accept获取连接{Accepter();}else if ((fd != listensock_.Fd()) && (revent & POLLIN))//如果普通套接字数据就绪就开始读数据{// ServiceIO();// BUGchar buffer[1024];ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0); // 读取会被阻塞吗?不会if (s > 0){buffer[s-1] = 0;std::cout << "client# " << buffer << std::endl;// fdarray_[i].events |= POLLOUT;//读完数据之后现在开始关心该文件描述符是否空间就绪,让我们可以开始写了。// 发送回去也要被select管理的,TODOstd::string echo = buffer;echo += " [select server echo]";send(fd, echo.c_str(), echo.size(), 0);}else{if (s == 0)logMessage(Info, "client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);elselogMessage(Warning, "recv error, client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);close(fd);fdarray_[i].fd = defaultfd;fdarray_[i].events = defaultevent;fdarray_[i].revents = defaultevent;}}}}void Start(){// 1. 这里我们能够直接获取新的链接吗?// 2. 最开始的时候,我们的服务器是没有太多的sock的,甚至只有一个sock!listensock// 3. 在网络中, 新连接到来被当做 读事件就绪!// listensock_.Accept(); 不能!// demo1fdarray_[0].fd = listensock_.Fd();fdarray_[0].events = POLLIN;//关心什么时候数据就绪可以开始读了while (true){int timeout = -1;//阻塞int n = poll(fdarray_, N, timeout); // poll内部会对文件描述符的合法性做甄别,不用担心初始化为-1的问题。fdarray_内容管理,合法fd,event全部放入到fdarray_最左侧switch (n){case 0:logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));break;case -1:logMessage(Warning, "%d: %s", errno, strerror(errno));break;default:// 成功了logMessage(Debug, "有一个就绪事件发生了: %d", n);HandlerEvent();DebugPrint();break;}// sleep(1);}}void DebugPrint(){std::cout << "fdarray[]: ";for (int i = 0; i < N; i++){if (fdarray_[i].fd == defaultfd)continue;std::cout << fdarray_[i].fd << " ";}std::cout << "\n";}~PollServer(){listensock_.Close();if(fdarray_) delete []fdarray_;}private:uint16_t port_;Sock listensock_;type_t *fdarray_; // 管理所有的fd
};

poll和select+依旧需要操作系统去进行线性遍历,去检测所有的文件描述符就绪情况,也就是进程区特征的文件当中等待,有一个就绪了就去唤醒select的系统调用,然后系统调用遍历一遍文件,哪些就绪了再加上,一旦需要管理的文件描述符多了成本就太高了。

TCP的PSH标志位就是再操作系统层面让sockfd对应的文件数据处于就绪状态,通知应用层读取。

网卡一旦有数据了,就会像cpu发送中断,cpu此时会识别中断号,然后调用中断向量表对应的下标方法,把数据从外设拷贝到内存。select和poll并不是检测硬件上面是否有数据,而是检测套接字对应的文件结构体中是否有收到数据,检测的是已经把数据从外设拷贝到内存后才通过select/poll监测是否有数据。

epoll采用红黑树维护用户关心的文件描述符,所以增删查改的效率非常高。并且不用再在底层线性遍历所有的文件来确认哪些文件描述符已经就绪了,因为已经把所有的就绪的节点放到了就绪队列当中,上层在检查是否有事件的时候只用检查就绪队列是否为恐就可以了,这就是epoll高效的原因。

V1版本

//Epoll.hpp
#pragma once
#include <iostream>
#include <string>
#include <stdlib.h>
#include <sys/epoll.h>
#include "Log.hpp"
#include "Err.hpp"static const int defaultepfd = -1;
static const int gsize = 128;class Epoller
{
public:Epoller() : epfd_(defaultepfd){}void Create(){epfd_ = epoll_create(gsize);//这个gsize参数不重要,只要是大于零都可以。成功的话返回一个文件描述符if (epfd_ < 0){logMessage(Fatal, "epoll_create error, code: %d, errstring: %s", errno, strerror(errno));exit(EPOLL_CREAT_ERR);}}// 用户 -> 内核bool AddEvent(int fd, uint32_t events)//向其中一个文件描述符添加事件{struct epoll_event ev;ev.events = events;ev.data.fd = fd;             //用户数据, epoll底层不对该数据做任何修改,就是为了给未来wait就绪返回的!int n = epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev);//第一个参数是eppol模型,第二个是control操作,第三个是目标文件描述符,第四个是对应的事件是什么if(n < 0){logMessage(Fatal, "epoll_ctl error, code: %d, errstring: %s", errno, strerror(errno));return false;}return true;//此时红黑树和就绪队列已经创建完成了}bool DelEvent(int fd){// epoll在操作fd的时候,有一个要求,fd必须合法!return epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, nullptr) == 0;//移除的时候就不关心事件了,返回值为零表示移除成功}int Wait(struct epoll_event *revs, int num, int timeout){// return epoll_wait(epfd_, revs, num, timeout);//成功就返回就绪文件描述符个数,并将这些文件描述符相关的状态拷贝到revs当中}int Fd(){return epfd_;}void Close(){if(epfd_ != defaultepfd) close(epfd_); }~Epoller(){}private:int epfd_;
};
//EpollServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <cassert>
#include <functional>
#include "Epoller.hpp"
#include "Sock.hpp"
#include "Log.hpp"const static int gport = 8888;using func_t = std::function<std::string (std::string)>;class EpollServer
{const static int gnum = 64;public:EpollServer(func_t func, uint16_t port = gport) : func_(func), port_(port){}void InitServer(){listensock_.Socket();listensock_.Bind(port_);listensock_.Listen();epoller_.Create();logMessage(Debug, "init server success");}int Start(){// 1. 将listensock添加到epoll中!bool r = epoller_.AddEvent(listensock_.Fd(), EPOLLIN);assert(r);(void)r;int timeout = -1;//1000就是每个1stimeout一次,如果是0就是非阻塞等待,如果是-1就是阻塞式等待。while (true){int n = epoller_.Wait(revs_, gnum, timeout);//switch (n){case 0:logMessage(Debug, "timeout...");//break;case -1:logMessage(Warning, "epoll_wait failed");//失败了break;default:logMessage(Debug, "有%d个事件就绪了", n);//HandlerEvents(n);//处理n个事件break;}}}void HandlerEvents(int num){for (int i = 0; i < num; i++)//不用遍历到gnum{int fd = revs_[i].data.fd;uint32_t events = revs_[i].events;logMessage(Debug, "当前正在处理%d上的%s", fd, (events&EPOLLIN) ? "EPOLLIN" : "OTHER");if (events & EPOLLIN){if (fd == listensock_.Fd()){// 1. 新连接到来// logMessage(Debug, "get a new link ...");std::string clientip;uint16_t clientport;int sock = listensock_.Accept(&clientip, &clientport);//如果新连接到来了不获取,那么listen套接字就会一直处于就绪状态。if (sock < 0)continue;logMessage(Debug, "%s:%d 已经连上了服务器了", clientip.c_str(), clientport);// 1.1 此时在这里,我们能不能进行recv/read ? 不能,只有epoll知道sock上面的事件情况,将sock添加到epoll中bool r = epoller_.AddEvent(sock, EPOLLIN);assert(r);(void)r;}else//读取事件就绪{// 我们目前无法保证我们读到一个完整的报文// 为什么?完整报文由应用层协议规定, 本质就是你没有应用层协议!// 怎么办?自定义应用层协议char request[1024];ssize_t s = recv(fd, request, sizeof(request) - 1, 0); if (s > 0){request[s-1] = 0; // \r\nrequest[s-2] = 0; // \r\nstd::string response = func_(request);send(fd, response.c_str(), response.size(), 0);}else{if (s == 0)logMessage(Info, "client quit ...");elselogMessage(Warning, "recv error, client quit ...");// 在处理异常的时候,先从epoll中移除,然后再关闭epoller_.DelEvent(fd);close(fd);}} // else 读取事件}     // fi : 读事件就绪}}~EpollServer(){listensock_.Close();epoller_.Close();}private:uint16_t port_;//服务器一定要提供端口号Sock listensock_;//监听端口Epoller epoller_;//struct epoll_event revs_[gnum];func_t func_;//数据处理逻辑函数
};

v2版本

select、poll、epoll最基本的情况:一旦有时间就绪,如果底层一直不取的话,底层会一直通知我事件就绪了,换句话说只要底层有数据,我们recve的时候,再也不担心数据没有读完的事情发送。属于水平触发Level Triggered 工作模式。

而边缘触发Edge Triggered工作模式:
1. 一次通知就是一 次系统调用返回,一次返回必定对应一次调用,ET有效减少系统调用次数!
2. ET倒逼程序员尽快取走所有的数据,本质是让TCP底层更新出更大的接受窗口,从而在较
大概率上,提供对方的滑动窗口的大小,提高发送效率。

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

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

相关文章

nginx容器映射配置文件后,启动一直报错提示:failed (13: Permission denied)的排查

问题现象&#xff1a; 使用harbor 的install.sh 创建docker-compose之后&#xff0c;出现nginx容器一直重启。 查看日志发现是&#xff1a;配置文件无权限。报错信息如下&#xff1a; Sep 2 16:43:13 172.28.0.1 nginx[1344]: 2024/09/02 08:43:13 [emerg] 1#0: open() “/e…

百度地图绘制电子围栏(包括移动端绘制操作)以及检测坐标是否在电子围栏内

由于本人在PC端仅使用了多边形绘制&#xff0c;但矩形跟多边形用法基本一样&#xff0c;圆形并未使用&#xff0c;如不符合读者需求也可以参考一下。 绘制后得到的数据可能不同&#xff0c;但绘制方法仅仅是传递的参数不同。 关于给坐标数组在地图绘制图形的效果在移动端部分包…

深度学习系列74:语音中的mel谱

1 mel谱介绍 一个人说一句话&#xff0c;其 waveform 可以很不一样&#xff0c;但是 spectrogram 基本上会相似&#xff0c;甚至有人可以通过 spectrogram 来判断说话的内容。语谱图的横坐标是时间&#xff0c;纵坐标是频率&#xff0c;坐标点值为语音数据能量。由于是采用二维…

# 利刃出鞘_Tomcat 核心原理解析(十一)-- WebSocket -- 1

利刃出鞘_Tomcat 核心原理解析&#xff08;十一&#xff09;-- Tomcat 附加功能 WebSocket – 1 一、Tomcat专题 - WebSocket - 介绍 1、Tomcat 附加功能&#xff1a;websocket 介绍 1&#xff09;websocket &#xff1a;是 HTML5 新增的协议&#xff0c;它的目的是在浏览器…

动态规划法-资源分配问题

动态规划法 - 资源分配问题 问题描述 把4个份额的资源分配给3个工程&#xff0c;给定利润表如下表所示&#xff0c;写出资源的最优分配方案的求解过程。 4份资源分配给3个工程的利润表 步骤一&#xff1a;求各个阶段不同分配份额时的最大利润及分配份额 目标 我们的目标是…

53 mysql pid 文件的创建

前言 接上一篇文章 mysql 启动过程中常见的相关报错信息 在 mysql 中文我们在 “service mysql start”, “service mysql stop” 经常会碰到 mysql.pid 相关的错误信息 比如 “The server quit without updating PID file” 我们这里来看一下 mysql 中 mysql.pid 文件的…

微积分复习笔记 Calculus Volume 1 - 1.3Trigonometric Functions

1.3 Trigonometric Functions - Calculus Volume 1 | OpenStax

自己开发完整项目一、登录功能-05(动态权限控制)

一、上节回顾 在上一节中&#xff0c;我们介绍了如何通过数据库查询用户的权限&#xff0c;并对方法级别的接口使用注解的方式进行权限控制&#xff0c;之后通过用户携带的tocken进行解析权限&#xff0c;判断是否可以访问。 具体步骤&#xff1a; 1.在查询用户信息的时候将用户…

map和set的封装

目录 一、红黑树的改造 1.1节点的定义 二、红黑树的迭代器 2.1用节点封装迭代器 2.2迭代器的实现 2.3map和set的迭代器 三、insert的返回值 3.1insert返回值的用处 3.2operator[ ]的实现 四、key不能修改的问题 封装map和set一般分为六步&#xff1a; 封装map和set …

MFC生成dll的区别

主要分三种&#xff1a; A. 动态链接库(dll) B.具有导出项的(dll)动态链接库 C.MFC动态链接库 对比项目&#xff1a;可以根据需要选择哪种dll方便 添加自定义导出功能Demo 1. 添加导出实现接口&#xff1a; A. 导出需要具有&#xff1a;__declspec(dllexport) B. 按照C语言…

Javascript LeeCode选题(汉诺塔求解)

LeeCode选题 汉诺塔递归求解move移动函数hanoi函数main方法测试代码&#xff1a;代码实现 汉诺塔递归求解 汉诺塔介绍&#xff1a; 汉诺塔的的图形&#xff08;从上到下1&#xff0c;2&#xff0c;3个&#xff09;实现&#xff1a; 这里我们可以看到因为必须要将第n个移动到…

Spring中基于redis stream 的消息队列实现方法

本文主要介绍了消息队列的概念性质和应用场景&#xff0c;介绍了kafka、rabbitMq常用消息队列中间件的应用模型及消息队列的实现方式&#xff0c;并实战了在Spring中基于redis stream 的消息队列实现方法。 一、消息队列 消息队列是一种进程间通信或者同一个进程中不同线程间的…

uni-app 获取当前位置的经纬度以及地址信息

文章目录 uni.getLocation(objc)获取经纬度和地址调试结果问题 uni-app 获取当前位置的经纬度以及地址信息 uni.getLocation(objc) uni-app官方文档定位API: uni.getLocation(OBJECT) uni.getLocation({type: wgs84,success: function (res) {console.log(当前位置的经度&…

【系统架构设计】嵌入式系统设计(1)

【系统架构设计】嵌入式系统设计&#xff08;1&#xff09; 嵌入式系统概论嵌入式系统的组成硬件嵌入式处理器总线存储器I/O 设备与接口 软件 嵌入式开发平台与调试环境交叉平台开发环境交叉编译环境调试 嵌入式系统概论 嵌入性、专用性、计算机系统是嵌入式系统的三个基本的核…

【话题讨论】VS Code:倍增编程动力,实现效率飞跃

目录 引言 一、详情介绍 功能特点 使用场景 提高工作效率 二、效率对比 2.1 高度可定制性与丰富的插件生态 2.2 智能的代码补全与导航 2.3 内置的调试器与版本控制集成 2.4 轻量级与跨平台 2.5 选择合适工具的重要性 2.6 实际案例或数据展示 三、未来趋势 3.1 编…

能见度监测站—实时监测道路能见度情况

型号&#xff1a;TH-NJD10】能见度监测站是一种专门用于自动观测和存储气象观测数据的设备&#xff0c;它通过高科技手段实时监测大气能见度的变化&#xff0c;为多个领域提供重要的数据支持。主要基于光在大气中的衰减规律。传感器系统中的发射器发出光线&#xff0c;照射到空…

shell编程--正则表达式

正则表达式 正则表达式都被置于两个正斜杠之间&#xff1b;如/l[oO]ve/ 示例 匹配数字的脚本&#xff0c;用户输入创建账号的数量 语法&#xff1a; [[ ^[0-9]$ ]] 表示必须输入数字 #!/bin/bashwhile : do read -p "输入数字&#xff1a;" numif [[ $num ~ ^[…

产品需求过程管理重要性

产品需求过程管理重要性 背景 以下都是真实事项经历回顾&#xff0c;在产品开发过程中&#xff0c;产品经理与研发团队之间的沟通至关重要。然而&#xff0c;沟通不畅或信息缺失常常导致需求无法准确传达&#xff0c;最终影响产品的成功。以下是一些常见的问题&#xff1a; 1.需…

Jmeter执行多机联合负载

1、注意事项&#xff0c;负载机必须要安装jre&#xff0c;控制机则必须安装jdk。要配置同网段ip&#xff0c;双向关闭防火墙。 每个负载机要平均承担线程数。 具体执行事项查看上面截图所示&#xff0c;控制机和负载机配置。 2、先给负载机设置ip地址&#xff0c;保持与控制…

C++项目详细分析_WebServer

前言 项目地址 项目介绍 源码详细分析 项目路径如下&#xff1a; 1.webserver.cpp 头文件和构造函数 #include "webserver.h"WebServer::WebServer() {// http_conn类对象users new http_conn[MAX_FD];// root文件夹路径char server_path[200];getcwd(server…