移动通网站建设,湘潭市优化办,word上下页纸张方向,网站开发 运维 招投标计算机网络#xff08;9#xff09; --- 数据链路层与MAC帧_哈里沃克的博客-CSDN博客数据链路层与MAC帧https://blog.csdn.net/m0_63488627/article/details/132178583?spm1001.2014.3001.5501 1.IO介绍 1.IO本质 1.如果数据没有出现#xff0c;那么读取文件其实会被阻塞住…计算机网络9 --- 数据链路层与MAC帧_哈里沃克的博客-CSDN博客数据链路层与MAC帧https://blog.csdn.net/m0_63488627/article/details/132178583?spm1001.2014.3001.5501 1.IO介绍 1.IO本质 1.如果数据没有出现那么读取文件其实会被阻塞住以等待资源的就绪或者数据还在网络上传输并没有到来需要等待数据到来 2.而操作系统给我们的读取接口其实是对数据的拷贝 本质IO等数据到来数据拷贝 其实拷贝数据是两个硬件之间的传输对于软件层的我们而言无法进行进一步优化又因为等待的时间其实比拷贝时间要来的多。所以拷贝在IO中的效率占比不是很大 高效IO本质减少等待的时间带来的成本 2.IO模型 1.阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式 2.非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码。往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 3.信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作 4.IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态 5.异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据) 2.非阻塞 void setNoBlock(int fd)
{int fl fcntl(fd, F_GETFL); //得到原先文件描述符的状态if (fl 0){std::cerr fcntl : strerror(errno) std::endl;}fcntl(fd, F_SETFL, fl | O_NONBLOCK); //追加文件描述符的状态信息
}int main()
{char buffer[1024];while (1){setNonBlock(0);std::cout ;fflush(stdout);ssize_t s read(0, buffer, sizeof(buffer) - 1);if (s 0){buffer[s] 0;std::cout echo# buffer std::endl;}else if (s 0){std::cout read end std::endl;break;}else{if (errno EAGAIN){std::cout 没有错只是没有数据 std::endl;}else if (errno EINTR){std::cout 系统调用被中断 std::endl;continue;}else{std::cout 出错 std::endl;break;}}}return 0;
} F_GETFL得到文件描述符的状态 F_SETFL追加文件描述符的状态信息 O_NONBLOCK非阻塞模式 3.IO多路转接 1.select 1.select表现为等待数据 2.select系统调用是用来让我们的程序监视多个文件描述符的状态变化的; 3.程序会停在select这里等待直到被监视的文件描述符有一个或多个发生了状态改变 1.nfds监视的多个文件描述符中最大的文件描述符1为输入值 2.timeout等待多个文件描述符时等待的方式。输入nullptr则表示阻塞式等待设置传入的数据结构timeval {00}表示非阻塞等待timeval {x0}表示x秒内为阻塞式等待超过5秒为非阻塞等待 3.返回值多少文件描述符就绪则返回多少个文件描述符的数返回值为0表示返回超时了返回值小于0表示select调用失败 4.select关心的时间只有三类读、写、异常。fd_set是一个位图用于表示文件描述符的集合。 5.fd_set输入的位图参数为自己的需要进行管理的文件描述符置为1返回则是内核告诉用户哪些文件描述符已经就绪了 编写代码 1.listen套接字也需要被select连接将其归类为读事件 2.检测事件只有select有这个功能设计所以需要将连接交给select进行处理 3.操作系统提供的位图大小为1024bite所以我们需要拿出一个数组fdarray大小也为1024进行管理。 namespace select_ns
{static const int defaultport 8081;static const int fdnum sizeof(fd_set) * 8;static const int defaultfd -1;class SelectServer{public:SelectServer(int port defaultport) : _port(port), _listensock(-1), fdarray(nullptr){}void Print(){std::cout fd list: ;for (int i 0; i fdnum; i){if (fdarray[i] ! defaultfd)std::cout fdarray[i] ;}std::cout std::endl;}void HandlerEvent(fd_set rfds){//? 目前一定是listensock只有这一个if (FD_ISSET(_listensock, rfds)){// 走到这里accept 函数会不会阻塞1 0// select 告诉我 listensock读事件就绪了std::string clientip;uint16_t clientport 0;int sock Sock::Accept(_listensock, clientip, clientport); // accept 等 获取if (sock 0)return;logMessage(NORMAL, accept success [%s:%d], clientip.c_str(), clientport);// sock我们能直接recv/read 吗不能整个代码只有select有资格检测事件是否就绪// 将新的sock 托管给select// 将新的sock托管给select的本质其实就是将sock添加到fdarray数组中即可int i 0;for (; i fdnum; i){if (fdarray[i] ! defaultfd)continue;elsebreak;}if (i fdnum){logMessage(WARNING, server if full, please wait);close(sock);}else{fdarray[i] sock;}Print();}}void initServer(){_listensock Sock::Socket();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);fdarray new int[fdnum];for (int i 0; i fdnum; i)fdarray[i] defaultfd;fdarray[0] _listensock; // 不变了}void start(){for (;;){fd_set rfds;FD_ZERO(rfds);int maxfd fdarray[0];for (int i 0; i fdnum; i){if (fdarray[i] defaultfd)continue;FD_SET(fdarray[i], rfds); // 合法 fd 全部添加到读文件描述符集中if (maxfd fdarray[i])maxfd fdarray[i]; // 更新所有fd中最大的fd}// struct timeval timeout {1, 0};// int n select(_listensock 1, rfds, nullptr, nullptr, timeout); // ??// 一般而言要是用select需要程序员自己维护一个保存所有合法fd的数组int n select(maxfd 1, rfds, nullptr, nullptr, nullptr); // ??switch (n){case 0:logMessage(NORMAL, timeout...);break;case -1:logMessage(WARNING, select error, code: %d, err string: %s, errno, strerror(errno));break;default:logMessage(NORMAL, get a new link...);HandlerEvent(rfds);break;}}}~SelectServer(){if (_listensock 0)close(_listensock);if (fdarray)delete[] fdarray;}private:int _port;int _listensock;int *fdarray;};
} 优缺点 1.select等待的文件描述符是有上限的除非重新改内核能提高上限否则无法解决。 2.需要借助第三方数组对select的文件描述符进行管理 3.需要不断检查不同的位图进行循环管理时间成本高 4.select的第一个参数为最大fd1的目的是用于select遍历合法文件描述符的范围 2.poll 1.poll解决了select的fd有上限问题 2.解决select需要反复设置fd问题 1.fds为一个动态数组 2.nfdsfds数组的长度 3.timeoutms为单位当数0在timeout内阻塞超过时间非阻塞方式进行等待0以非阻塞方式进行等待0以阻塞方式进行等待 4.pollfd为一个结构体表示fd和对应的events事件。event表示内核告诉用户哪些事件准备就绪revent则是输出 特点输入输出分离大小可设置 编写代码 namespace poll_ns
{static const int defaultport 8081;static const int num 2048;static const int defaultfd -1;using func_t std::functionstd::string (const std::string);class PollServer{public:PollServer(func_t f, int port defaultport) : _func(f), _port(port), _listensock(-1), _rfds(nullptr){}void initServer(){_listensock Sock::Socket();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);_rfds new struct pollfd[num];for (int i 0; i num; i) ResetItem(i);_rfds[0].fd _listensock; // 不变了_rfds[0].events POLLIN;}void Print(){std::cout fd list: ;for (int i 0; i num; i){if (_rfds[i].fd ! defaultfd)std::cout _rfds[i].fd ;}std::cout std::endl;}void ResetItem(int i){_rfds[i].fd defaultfd;_rfds[i].events 0;_rfds[i].revents 0;}void Accepter(int listensock){logMessage(DEBUG, Accepter in);// 走到这里accept 函数会不会阻塞1 0// select 告诉我 listensock读事件就绪了std::string clientip;uint16_t clientport 0;int sock Sock::Accept(listensock, clientip, clientport); // accept 等 获取if (sock 0)return;logMessage(NORMAL, accept success [%s:%d], clientip.c_str(), clientport);// sock我们能直接recv/read 吗不能整个代码只有select有资格检测事件是否就绪// 将新的sock 托管给select// 将新的sock托管给select的本质其实就是将sock添加到fdarray数组中即可int i 0;for (; i num; i){if (_rfds[i].fd ! defaultfd)continue;elsebreak;}if (i num){logMessage(WARNING, server if full, please wait);close(sock);}else{_rfds[i].fd sock;_rfds[i].events POLLIN;_rfds[i].revents 0;}Print();logMessage(DEBUG, Accepter out);}void Recver(int pos){logMessage(DEBUG, in Recver);// 1. 读取request// 这样读取是有问题的char buffer[1024];ssize_t s recv(_rfds[pos].fd, buffer, sizeof(buffer) - 1, 0); // 这里在进行读取的时候会不会被阻塞1 0if (s 0){buffer[s] 0;logMessage(NORMAL, client# %s, buffer);}else if (s 0){close(_rfds[pos].fd);ResetItem(pos);logMessage(NORMAL, client quit);return;}else{close(_rfds[pos].fd);ResetItem(pos);logMessage(ERROR, client quit: %s, strerror(errno));return;}// 2. 处理requeststd::string response _func(buffer);// 3. 返回response// write bugwrite(_rfds[pos].fd, response.c_str(), response.size());logMessage(DEBUG, out Recver);}// 1. handler event rfds 中不仅仅是有一个fd是就绪的可能存在多个// 2. 我们的select目前只处理了read事件void HandlerReadEvent(){for (int i 0; i num; i){// 过滤掉非法的fdif (_rfds[i].fd defaultfd)continue;if (!(_rfds[i].events POLLIN)) continue;// 正常的fd// 正常的fd不一定就绪了// 目前一定是listensock只有这一个if (_rfds[i].fd _listensock (_rfds[i].revents POLLIN))Accepter(_listensock);else if(_rfds[i].revents POLLIN)Recver(i);else{}}}void start(){int timeout -1;for (;;){int n poll(_rfds, num, timeout);switch (n){case 0:logMessage(NORMAL, timeout...);break;case -1:logMessage(WARNING, poll error, code: %d, err string: %s, errno, strerror(errno));break;default:logMessage(NORMAL, have event ready!);HandlerReadEvent();break;}}}~PollServer(){if (_listensock 0)close(_listensock);if (_rfds)delete[] _rfds;}private:int _port;int _listensock;struct pollfd *_rfds;func_t _func;};
} 3.epoll 1.接口 epoll_create创建一个epoll epoll_ctl加入准备好的文件描述符 epoll_event为一个结构体其中的events表示文件描述符的事件epoll_data_t为一个联合体。 epfd表示添加的epoll文件描述符 op表示添加epoll结构的文件描述符需要进行什么操作 fd为文件描述符 epoll_wait捞取准备好的文件描述符进行执行返回值为可以处理的文件描述符数量 2.实现原理 1.数据一定会从驱动层发送到此操作系统中。 2.先通过epoll_create创建epoll的文件描述符该文件描述符指向所谓的epoll模型 3.epoll模型中一旦需要关注某个文件描述符的从套接字处接收那么通过epoll_ctl能对文件描述符和需要处理的事件一起放入epoll结构体中。由于需要管理epoll_ctl的过程一并将epoll结构收录到操作系统的epoll模型的红黑树中进行管理。 4.红黑树中的文件描述符如果准备就绪那么就会通过epoll_wait将epoll的结构插入到准备队列中那么当启动epoll_wait就会一连串的进行所加载的文件 3.编程 namespace epoll_ns
{static const int defaultport 8888;static const int size 128;static const int defaultvalue -1;static const int defalultnum 64;class EpollServer{public:EpollServer(uint16_t port defaultport, int num defalultnum): _num(num), _revs(nullptr), _port(port), _listensock(defaultvalue), _epfd(defaultvalue){}void initServer(){// 1. 创建socket_listensock Sock::Socket();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);// 2. 创建epoll模型_epfd epoll_create(size);if (_epfd 0){logMessage(FATAL, epoll create error: %s, strerror(errno));exit(EPOLL_CREATE_ERR);}// 3. 添加listensock到epoll中struct epoll_event ev;ev.events EPOLLIN;ev.data.fd _listensock;epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock, ev);// 4.申请就绪事件的空间_revs new struct epoll_event[_num];logMessage(NORMAL, init server success);}void HandlerEvent(int readyNum){logMessage(DEBUG, HandlerEvent in);for (int i 0; i readyNum; i){uint32_t events _revs[i].events;int sock _revs[i].data.fd;if (sock _listensock (events EPOLLIN)){//_listensock读事件就绪, 获取新连接std::string clientip;uint16_t clientport;int fd Sock::Accept(sock, clientip, clientport);if (fd 0){logMessage(WARNING, accept error);continue;}// 获取fd成功可以直接读取吗不可以放入epollstruct epoll_event ev;ev.events EPOLLIN;ev.data.fd fd;epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, ev);}else if (events EPOLLIN){// 普通的读事件就绪}else{// 其他事件不进行操作}}logMessage(DEBUG, HandlerEvent out);}void start(){int timeout -1;for (;;){int n epoll_wait(_epfd, _revs, _num, timeout);switch (n){case 0:logMessage(NORMAL, timeout ...);break;case -1:logMessage(WARNING, epoll_wait failed, code: %d, errstring: %s, errno, strerror(errno));break;default:logMessage(NORMAL, have event ready);HandlerEvent(n);break;}}}~EpollServer(){if (_listensock ! defaultvalue)close(_listensock);if (_epfd ! defaultvalue)close(_epfd);if (_revs)delete[] _revs;}private:uint16_t _port;int _listensock;int _epfd;struct epoll_event *_revs;int _num;};
}