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

手机网站页面设计建设一个电子商务网站的基本步骤

手机网站页面设计,建设一个电子商务网站的基本步骤,湖南智能网站建设平台,广东省建设厅三库一平台在介绍本文之前#xff0c;先提出一个问题 什么是IO#xff1f; 等数据拷贝 1.等 - IO事件就绪#xff08;检测功能成分#xff09; 2.数据拷贝 高效的IO就是#xff1a;单位时间#xff0c;等的比重越小#xff0c;IO的效率越高 五种IO模型 IO模型#xff1a; 阻塞式…在介绍本文之前先提出一个问题 什么是IO 等数据拷贝 1.等 - IO事件就绪检测功能成分 2.数据拷贝 高效的IO就是单位时间等的比重越小IO的效率越高 五种IO模型 IO模型 阻塞式IO钓鱼中一直盯着鱼竿直到鱼上钩就钓【自己等阻塞自己钓】 非阻塞IO钓鱼中看一会手机再看看鱼上钩没上钩就钓【自己等非阻塞 / 轮询自己钓】 信号驱动式IO在鱼竿上放个铃铛听到铃铛上说明上钩直接钓其它时间一直看手机【没有直接等自己钓】 多路转接多路复用一次带来几百个鱼竿一起钓哪个上钩就钓哪个【自己等一次检测多个自己钓】 异步IO让别人帮自己钓鱼但是别人钓鱼的成果要给自己【没有自己等没有自己钓但是要拿结果】 5中IO模型中效率最高的是多路转接. 除了异步IO外其它4个都是同步IO。同步IO和异步IO的区别就是有没有参与IO细节参与了的就是同步IO没参与就是异步IO 正常使用中90%是阻塞IO部分是非阻塞和多路转接 阻塞IO 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式. 阻塞IO是最常见的IO模型 非阻塞IO 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码 非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用 信号驱动IO 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作 IO多路转接 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件 描述符的就绪状态 异步IO 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据) 任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少 代码编写 阻塞IO #include iostream #include unistd.h #include fcntl.h int main() {char buffer[1024];while(1){ssize_t sread(0,buffer,sizeof(buffer)-1);if(s0){buffer[s]0;std::cout echo# buffer std::endl;}else if(s0){std::cout read end std::endl;}else{}}return 0; }这时它会卡住只有你输入才会有反应 输入后 非阻塞IO 一个文件描述符, 默认都是阻塞IO. fcntl int fcntl(int fd, int cmd, ... /* arg */ )第一个参数fd要设置哪个文件描述符 第二个参数cmd要做什么 第三个参数arg要设置什么状态 fcntl函数五种功能 复制一个现有的描述符cmdF_DUPFD . 获得/设置文件描述符标记(cmdF_GETFD或F_SETFD). 获得/设置文件状态标记(cmdF_GETFL或F_SETFL). 获得/设置异步I/O所有权(cmdF_GETOWN或F_SETOWN). 获得/设置记录锁(cmdF_GETLK,F_SETLK或F_SETLKW. util.hpp #include iostream #include unistd.h #include fcntl.h void SetNonBlock(int fd) {int flagfcntl(fd,F_GETFL);if(flag0){std::cout fctnl failed std::endl;return;}int nfcntl(fd,F_SETFL,flag|O_NONBLOCK); }使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图). 然后再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数 test.cpp #includeutil.hpp #include string.h void print() {std::cout 我在空闲时间做其他事情 std::endl; } int main() {char buffer[1024];SetNonBlock(0);while(1){ssize_t sread(0,buffer,sizeof(buffer)-1);if(s0){buffer[s-1]0;std::cout echo# buffer std::endl;}else if(s0){std::cout read end std::endl;break;}else{//1.当我不在输入的时候底层没有数据会返回错误但是这并不是错误只是以错误的形式返回了// std::cout s: s errno: strerror(errno) std::endl;//查看man手册 man 2 readEAGAIN || EWOULDBLOCK 不算//EINTR被信号中断if(errnoEAGAIN){std::cout 我没有错,只是没有数据 std::endl;print();}else if(errnoEINTR){continue;}else{std::cout 出错 std::endl;break;}}// sleep(1);}return 0; }多路转接—select 文件描述符状态的变化1. 可读不可读的变化 2. 可写不可写的变化 3. 异常不异常的变化 因此等fd其实就是在等事件1. 读事件就绪 2. 写事件就绪 3. 异常事件 系统提供select函数来实现多路复用输入/输出模型 select只负责一件事情等等待文件描述符事件变化 select系统调用是用来让我们的程序监视多个文件描述符的状态变化的 程序会停在select这里等待直到被监视的文件描述符有一个或多个发生了状态改变 select函数 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)第一个参数nfds所要等的众多的文件描述符中最大的文件描述符1. 第二、三、四个参数readfds、writedfs、exceptfds是读文件描述符集. 输入时用户告诉OS要注意我设置的多个fd中的读事件是否就绪. 输出时OS告诉用户你让我注意的多个fd中有哪些已经就绪. fd_set是位图结构的. 以读事件readfds为例 1.比特位的位置代表fd的编号”是否“的概念 2.输入时用户告诉内核要注意我设置的多个fd是否中的事件是否就绪. 3.输出时内核告诉用户用户曾经让我注意的多个fd中有哪些事件已经就绪 比如readfds的位图结构为0110 1010输入时用户给OS的这里需要OS注意4个fd然后输出时readfds的位图结构为0000 0010这就就说明OS注意的4个fd中只有第7位的fd事件已就绪. 输入和输出用的是同一张位图. 第五个参数timeout为结构timeval用来设置select()的等待时间. 这里等待的策略问题也可以分为① 阻塞等待 ② 非阻塞等待 ③ 设定deadline 这个timeout就是设置一个deadline一个时间如果阻塞超时就会立马返回如果在时间之前完成就返回剩余的时间值 NULL则表示select没有timeout select将一直被阻塞直到某个文件描述符上发生了事件; 0仅检测描述符集合的状态然后立即返回并不等待外部事件的发生。 特定的时间值如果在指定的时间段里没有事件发生 select将超时返回、 返回值int 0 有几个fd就绪了 0timeout0报错了 编写一段select代码只实现读 sock.hpp #pragma once#include iostream #include string #include cstring #include unistd.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h class Sock {const static int backlog 5;public:static int Socket(){// 1. 创建socket文件套接字对象int sock socket(AF_INET, SOCK_STREAM, 0);if (sock 0){std::cout create socket error std::endl;exit(-1);}std::cout create socket success: sock std::endl;int opt 1;//防止bind失败setsockopt(sock, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, opt, sizeof(opt));return sock;}static void Bind(int sock, int port){// 2. bind绑定自己的网络信息struct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET;local.sin_port htons(port);local.sin_addr.s_addr INADDR_ANY;if (bind(sock, (struct sockaddr *)local, sizeof(local)) 0){std::cout bind socket error std::endl;exit(-1);}std::cout bind socket success std::endl;}static void Listen(int sock){// 3. 设置socket 为监听状态if (listen(sock, backlog) 0) // 第二个参数backlog后面在填这个坑{std::cout listen socket error std::endl;exit(-1);}std::cout listen socket success std::endl;}static int Accept(int listensock, std::string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len sizeof(peer);int sock accept(listensock, (struct sockaddr *)peer, len);if (sock 0)std::cout accept error, next std::endl;else{std::cout accept a new link success, get new sock: sock std::endl; *clientip inet_ntoa(peer.sin_addr);*clientport ntohs(peer.sin_port);}return sock;} };selectserver.hpp #pragma once #include sock.hpp namespace select_ns {// 因为是位图所有要*8static const int fdnum sizeof(fd_set) * 8;static const int port 8081;static const int defaultfd -1;class SelectServer{public:SelectServer(): _port(port), _listensock(-1), fdarray(nullptr){}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 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 Accepter(int listensock){// 目前一定是listensock只有一个// 走到这里accept函数不会阻塞std::string clientip;uint16_t clientport 0;int sock Sock::Accept(_listensock, clientip, clientport);if (sock 0){return;}std::cout accept success,clientip: clientip.c_str() clientport: clientport;int i 0;for (; i fdnum; i){if (fdarray[i] ! defaultfd)continue;elsebreak;}if (i fdnum){std::cout server if full,please wait std::endl;close(sock);}else{fdarray[i] sock;}Print();}void Recver(int sock,int pos){char buffer[1024];//这样读取是有问题的因为没有保证读到的是否是一个完整的报文//但是我们这里这样写ssize_t srecv(sock,buffer,sizeof(buffer)-1,0);if(s0){buffer[s]0;std::cout client say # buffer std::endl;}//对方关闭文件描述符else if(s0){close(sock);//下次不让操作系统去关心他了fdarray[pos]defaultfd;return ;}else{close(sock);fdarray[pos]defaultfd;return ;}//处理requesstd::string respbuffer;write(sock,resp.c_str(),resp.size());}void HandlerEvent(fd_set rfds){for(int i0;ifdnum;i){//过滤非法fdif(fdarray[i]defaultfd){continue;}//正常的fd//在FD_ISSET里面才是就绪的fdif(fdarray[i]_listensockFD_ISSET(fdarray[i],rfds)){Accepter(_listensock);}else if(FD_ISSET(fdarray[i],rfds)){Recver(fdarray[i],i);}else{}}}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);if (maxfd fdarray[i])maxfd fdarray[i];}struct timeval timeout {1, 0};int n select(maxfd 1, rfds, nullptr, nullptr, nullptr);switch (n){case 0:std::cout timeout.. std::endl;break;case -1:std::cout select err,code: errno err string strerror(errno);default:// 说明有事件就绪了目前只有一个监听事件就绪了std::cout get a new link...;HandlerEvent(rfds);break;}}}private:int _port;int _listensock;int *fdarray;}; }test.cpp #includeselectserver.hpp #includememory using namespace std; int main(int argc,char* argv[]) {unique_ptrselect_ns::SelectServer svr(new select_ns::SelectServer());svr-InitServer();svr-start();return 0; }select编码特征 select之前要进行所有参数的重置之后要遍历所有的合法fd进行事件检测 select需要用户自己维护第三方数组来保存所有的合法fd方便select进行批量处理 一旦特定的fd事件就绪本次读取或者写入不会被阻塞 select优缺点 1.优点 占用资源少并且高效对比之前的多进程、多线程 2.缺点 每一次都要进行大量的重置工作效率比较低 每一次能够坚持的fd数量是有上限的 每一次都需要内核到用户用户到内核传递位置参数出现较为大量的数据拷贝工作 select编码特别不方便需要用户自己维护数组 select底层需要同步遍历的方式检测所有需要检测的fd传入最大maxfd 1 多路转接—poll poll函数 int poll(struct pollfd *fds, nfds_t nfds, int timeout); struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */ };第一个参数fds是struct pollfd*指针类型可以当作一个数组来看类型的这个结构体里包括所要等的文件描述符fd请求事件events用户告诉内核请求的哪些事件就绪revent内核告诉用户. 针对事件poll将事件进行了拆分输入和输出进行了分离. 因此传入的相当于一个数组数组中的每一个元素告诉内核一个文件描述符fd的哪一个事件要关心.每一个fd都可能有不同的事件要关心或者发生.。 第二个参数nfds表示fds数组的长度。 第三个参数timeout表示poll函数的超时事件单位是mstimeout是输入型参数、单位是msselect的timeout是输入输出型单位是s.timeout设置为1000如果没有链接到来是每1s检测一次timeout为0是一直在检测是非阻塞式的检测timeout为-1则不检测代表的是永久阻塞。 events和revents的取值: 这里重点是POLLIN和POLLOUT一个是读一个是写这些都是宏如果想要读和写当然是可以用 异或| 来连接的。 返回值 返回值小于0, 表示出错; 返回值等于0, 表示poll函数等待超时; 返回值大于0, 表示poll由于监听的文件描述符就绪而返回 总结一下可以用一个结构体来表示一个文件描述符所对应的events用户告诉内核、revents内核告诉用户相关的事件用指针代表数组可以让poll关心多个文件描述符每一个文件描述符的输入输出事件都可以使用不同的位图来表征所以select上的参数都可以使用poll来取代了。select需要一个第三方数组而poll则不需要了poll可以把这个结构体数组作为全局数组就可以让poll进行事件监听又可以让新链接把文件描述符加进来。如果将来有一个文件描述符不关心事件了就可以将event清空并且将fd设置为-1poll在底层就自动不会去关心了。 使用一下poll PollServe.hpp #pragma once #include sock.hpp #include poll.h namespace poll_ns {static const int num 2048;static const int port 8081;static const int defaultfd -1;class pollServer{public:pollServer(): _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].eventsPOLLIN;_rfds[0].revents0;}void ResetItem(int i){_rfds[i].fddefaultfd;_rfds[i].events0;_rfds[i].revents0;}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 Accepter(int listensock){// 目前一定是listensock只有一个// 走到这里accept函数不会阻塞std::string clientip;uint16_t clientport 0;int sock Sock::Accept(_listensock, clientip, clientport);if (sock 0){return;}std::cout accept success,clientip: clientip.c_str() clientport: clientport;int i 0;for (; i num; i){if (_rfds[i].fd ! defaultfd)continue;elsebreak;}if (i num){std::cout server if full,please wait std::endl;close(sock);}else{_rfds[i].fd sock;_rfds[i].eventsPOLLIN;_rfds[i].revents0;}Print();}void Recver(int pos){char buffer[1024];//这样读取是有问题的因为没有保证读到的是否是一个完整的报文//但是我们这里这样写ssize_t srecv(_rfds[pos].fd,buffer,sizeof(buffer)-1,0);if(s0){buffer[s]0;std::cout client say # buffer std::endl;}//对方关闭文件描述符else if(s0){close(_rfds[pos].fd);//下次不让操作系统去关心他了ResetItem(pos);return ;}else{close(_rfds[pos].fd);ResetItem(pos);return ;}//处理requesstd::string respbuffer;write(_rfds[pos].fd,resp.c_str(),resp.size());}void HandlerEvent(){for(int i0;inum;i){//过滤非法fdif(_rfds[i].fddefaultfd){continue;}if(!(_rfds[i].eventsPOLLIN)) continue;//正常的fd//在FD_ISSET里面才是就绪的fdif((_rfds[i].reventsPOLLIN)_rfds[i].fd_listensock) {Accepter(_listensock);}else if(_rfds[i].reventsPOLLIN){Recver(i);}else{}}}void start(){int timeout-1;for (;;){int npoll(_rfds,num,timeout);switch (n){case 0:std::cout timeout.. std::endl;break;case -1:std::cout select err,code: errno err string strerror(errno);default:// 说明有事件就绪了目前只有一个监听事件就绪了std::cout get a new link...;HandlerEvent();break;}}}private:int _port;int _listensock;struct pollfd* _rfds;}; }实验现象和select一致这里不在演示 poll去掉了select的一些缺点优点更多了. 优点 1.poll的输入和输出进行了分离pollfd结构包含了要监视的event和发生的event不再使用select“参数-值”传递的方式. 接口使用比select更方便 2.poll没有了最大数量限制 (但是数量过大后性能也是会下降) 缺点 3.和select函数一样 poll返回后需要轮询pollfd来获取就绪的描述符 4.每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中 5.同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降 多路转接—epoll 学了select和poll目前多路转接还存在这一些问题 select、poll都是基于多个fd进行遍历检测来识别事件链接多的时候一定会引起遍历周期的增加。对应事件用户告诉内核内核通知用户需要使用的数据结构数组需要由程序员自己维护。 因此为了解决这些问题epoll出现了。虽然epoll和poll的名称很像但是实际上是完全不同的。 epoll几乎解决了多路转接方案的所有缺点具备之前所说的一切优点是现在性能最好的多路IO就绪通知方法。 epoll接口 不同与上面两个epoll有3个接口。但是无论有多少个接口核心工作都是只负责等包括1. 用户告诉内核 2. 内核告诉用户。 1.epoll_create() int epoll_create(int size);size参数不重要现在一般设置为特定的一个值即可128、256或者512等。 重点是返回值如果成功就返回一个文件描述符。会创建出一个epoll模型。 2.epoll_ctl int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);完成用户告诉内核的工作。 第一个参数输入创建成功epoll模型之后所返回的文件描述符 第二个参数要操作的方式增加ADD修改MOD删除DEL 第三个参数需要监听的fd 第四个参数用户告诉内核要监听的事件 总结对epoll模型中特定的文件描述符所要关心的事件进行操作增加、修改、删除。 第二个参数的取值: 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_ctl的时候向epoll模型中添加对应的文件描述符及其关心事件时这个events代表的是用户告诉内核当在epoll内返回的时候拿到的事件是内核告诉用户。在接口上做了分离。 typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64; }epoll_data_t;3.epoll_wait int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);完成内核告诉用户的工作 第一个参数输入创建成功epoll模型之后所返回的文件描述符 第二个参数输出型参数请求的哪些事件就绪**内核告诉用户** 第三个参数告知这个events有多大 第四个参数与poll中的timeout完全相同 从epoll模型中提取events将已经就绪的事件返回。 工作原理 OS如何知道网络中的数据到来了呢 网卡先得到数据然后会向CPU发送硬件中断调用OS预设的中断函数负责从外设进行数据拷贝从外设拷贝到内核缓冲区中。 如何理解数据在硬件上流动 电信号的流动高电压流到低电压可以把高电压当作1低电压当作0。 epoll模型 epoll存在一个回调机制会针对特定的一个或者多个fd设定对应的回调机制当fd缓冲区中有数据的时候进行回调。 只要调用了这个函数拷贝函数底层有数据到来的时候fd就不需要再轮询检测底层是否有数据只要设定好回调就会自动通知。 回调函数作用 获取就绪的fd 获取就绪的事件是什么 构建queue_node节点 将节点链入就绪队 创建epoll模型的时候会创建一颗红黑树是一个空树节点字段 用户向关心哪一个文件描述上的什么事件就由这个字段决定。 struct rb_struct {int fd; // 哪一个文件描述符由fd决定int events; // 什么事件由events决定events是位图结构与poll中的一样 }因此这个字段是为了解决用户告诉内核的问题。 调用epoll_ctl这个函数本质就是在这个红黑树中新插入对应的节点。OS维护了这个红黑树那么当用户历史上注册过很多的文件描述符和事件OS就可以知道用户关心哪个文件描述符的哪些事件。 key: fd、sockfd 创建epoll模型还会创建一个队列结构是一个空队列节点字段 内核中在红黑树中哪些fd上的哪些事件已经就绪由这个字段决定。 struct queue_node {int fd;int revents; }因此这个字段是为了解决内核告诉用户的问题 所以上面的三个函数的作用分别是 epoll_create: 创建整个epoll模型建立好回调机制创建一颗空的红黑树创建一个就绪队列 epoll_ctl: **操作红黑树**向红黑树中新增节点、查找红黑树节点找到后修改、删除红黑树中节点这个红黑树等价于poll中的数组 epoll_wait: **检测就绪队列**检测就绪队列是否为空不为空就直接从就绪队列中获取节点 一旦建立epoll模型本质上哪些fd上的哪些event就绪整个过程用户是不用关心的都是OS自动做的。 那么epoll为什么高效呢 采用红黑树来做管理epoll_ctl的数据结构是红黑树针对的场景是用户告诉内核的问题红黑树节点是没有上限的所以插入的文件描述符也是没有上限的。红黑树的管理成本非常低采用回调机制彻底解放OS不需要OS主动轮询大大提高了检测效率OS在拷贝函数时只要有数据就通过回调的方式获取对应的就绪文件描述符、就绪事件构建就绪队列链入到就绪队列不需要OS在底层遍历每一个文件描述符了只会为就绪的节点提供服务未就绪的节点不需要管就不再需要O(N)的时间遍历了epoll_wait调用的时候不再需要进行遍历了直接在就绪队列中取节点即可。就绪队列有数据时就会可能告诉用户有就绪的数据了。有人拿节点。有人放节点这就是生产者消费者模型但是是不需要加锁了epoll的代码考虑了线程安全的问题epoll底层是多线程、多进程安全的 epoll.hpp #pragma once #include sock.hpp #include sys/epoll.h namespace epoll_ns {static const int num 2048;static const int port 8081;static const int defaultfd -1;static const int size 128;class epollServer{public:epollServer(): _port(port), _listensock(-1), _epfd(defaultfd), _revs(nullptr){}~epollServer(){if (_listensock ! defaultfd)close(_listensock);if (_epfd ! defaultfd)close(_listensock);if (_revs ! nullptr)delete[] _revs;}void InitServer(){_listensock Sock::Socket();Sock::Bind(_listensock, port);Sock::Listen(_listensock);//创建epoll模型_epfd epoll_create(size);if (_epfd 0){std::cout epoll create error std::endl;}//将listensock添加到epoll中struct epoll_event event;//当时间就绪被捞取上来的时候要知道哪一个时间就绪了event.data.fd _listensock;event.events EPOLLIN;epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock, event);//申请就绪事件的空间_revs new struct epoll_event[num];}void Accepter(){// 目前一定是listensock只有一个// 走到这里accept函数不会阻塞std::string clientip;uint16_t clientport 0;int sock Sock::Accept(_listensock, clientip, clientport);std::cout accept success,clientip: clientip.c_str() clientport: clientport;if (sock 0){return;}struct epoll_event event;event.data.fd sock;event.events EPOLLIN;epoll_ctl(_epfd, EPOLL_CTL_ADD, sock, event);}void Recver(int sock){char buffer[1024];//这样读取是有问题的因为没有保证读到的是否是一个完整的报文//但是我们这里这样写ssize_t s recv(sock, buffer, sizeof(buffer) - 1, 0);if (s 0){buffer[s] 0;std::cout client say # buffer std::endl;}//对方关闭文件描述符else if (s 0){//下次不让操作系统去关心他了epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);close(sock);return;}else{epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);close(sock);return;}std::string resp buffer;//这个发送其实也有问题因为你不知道当前的发送条件是就绪的send(sock, resp.c_str(), resp.size(), 0);}void HandlerEvent(int readyNum){for (int i 0; i num; i){int sock _revs[i].data.fd;uint32_t event _revs[i].events;if (sock _listensock (event EPOLLIN)){std::cout Accpter std::endl;//_listensock读事件就绪,获取新连接Accepter();}else if (event EPOLLIN){std::cout Recver std::endl;//普通读事件就绪Recver(sock);}else{}}}void start(){int timeout 1000;for (;;){// n的个数是就绪事件的个数int n epoll_wait(_epfd, _revs, num, -1);switch (n){case 0:std::cout timeout.. std::endl;break;case -1:std::cout select err,code: errno err string strerror(errno);default:// 说明有事件就绪了目前只有一个监听事件就绪了std::cout get a new link...;HandlerEvent(n);break;}}}private:int _port;int _listensock;int _epfd;struct epoll_event *_revs;}; }1.这里fd为什么是5 因为epoll模型占用了fd 4 工作方式 epoll有2种工作方式1.水平触发(LT) 2.边缘触发(ET) 水平触发(LT)如果接收缓冲区里有数据OS就会一直发消息通知你底层有数据 边缘触发(ET)如果接收缓冲区里有数据OS通知你一次你没管OS就不会再通知了 LT只要底层有数据就会一直通知你这是多路转接的默认模式。程序员在上层编码的时候可以暂时不把数据读取完毕不用担心底层不通知你进而导致的数据丢失的问题。 ET只有底层在从无到有、从有到多变化的时候才会通知你。这就需要程序员一旦收到通知就必须将自己收到的数据从内核中全部读取完成否则可能会有数据丢失的风险。 一般来说是ET更高效ET通知的量最小拷贝的数据量也是最小的。本质是让上层尽快取走数据的一种机制有更大的窗口大小。让对方不用对于滑动窗口流量控制做太多的控制可以尽快将数据交付并且对方的延迟应答等其它提高效率的策略也能够在TCP层面上体现出来进而提高效率。 我怎么知道数据被取完了呢 read、recv不经过读取是无法得知的只有读取了之后才能知道是否取完了。 while(true) read(); errno - break 在网络中读取的所有文件描述符默认是阻塞的阻塞的特点是有数据就直接常规返回没有数据就调用read此时服务器会因为没有数据而被阻塞所以因为必须要循环读直到读取出错或者全部读取完再读一次才能知道没有数据了。那么这最后一次读取一定会导致read/recv阻塞住。 因此ET模式下所有的fd、sock必须处于非阻塞模式。LT模式设阻塞模式不会受影响但是也建议设置为非阻塞模式。 LT工作模式 epoll默认状态下就是LT工作模式 当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理, 或者只处理一部分。 如果只读了1K数据, 缓冲区中还剩1K数据, 在第二次调用 epoll_wait 时, epoll_wait仍然会立刻返回并通知socket读事件就绪。 直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回。 支持阻塞读写和非阻塞读写。 ET工作模式 如果我们在第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是 epoll 的默认行为。使用 ET 能够减少 epoll 触发的次数但是代价就是强迫着程序员一次响应就绪过程中就把所有的数据都处理完。 相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些。但是在 LT 情况下如果也能做到每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的。 另一方面, ET 的代码复杂程度更高了。 如何写入 读写事件就绪读底层有数据 - recv - 底层没有数据不会读取 写底层有空间 - send - 底层如果没有空间不应该再写入 默认我们只设置让epoll帮我们关心读事件没有关心写事件 为什么没有关心写事件因为最开始的时候写空间一定是就绪的 但是运行中可能会存在条件不足即写空间被写满了 写 如果LT模式一定是要先检测有没有对应的空间检测方式先打开对写事件的关心epoll会自动进行事件派发然后才写入。LT模式下只要打开了写入我们想要实现的代码就会自动进行调用sender方法进行发送如果是ET模式也可以采用上面的方法不过一般用ET追求高效直接发送通过发送是否全部发送完成来决定是否要进行打开写事件进行关心 a. 先发送发完就完了 b. 先发送如果没有发完打开写事件关心让epoll自动帮我们进行发送 注意一般写事件关心不能常打开一定是在需要的时候再进行打开不需要的时候就要关闭对写事件的关心 当我们开启对写事件关心的时候首次或者每次打开都会自动触发一次epoll事件就绪 reactor Reactor 对象的作用是监听和分发事件Acceptor 对象的作用是获取连接Handler 对象的作用是处理业务 单 Reactor 单进程的方案因为全部工作都在同一个进程内完成所以实现起来比较简单不需要考虑进程间通信也不用担心多进程竞争。 但是这种方案存在 2 个缺点 第一个缺点因为只有一个进程无法充分利用 多核 CPU 的性能第二个缺点Handler 对象在业务处理时整个进程是无法处理其他连接的事件的如果业务处理耗时比较长那么就造成响应的延迟
http://www.tj-hxxt.cn/news/136068.html

相关文章:

  • 如何给网站做下载附件建设网站郑州
  • 电子商务网站开发技术便宜的购物网站排名
  • 做网站挂广告赚多少钱网络推广的优势
  • 网站建设是在商标哪个类别网站代码特效广告
  • 网站首页布局设计原理农产品电子商务网站开发
  • 网站开发术语wordpress深度开发
  • 网站动态域名广州网站设计总部
  • 网站开发公司能不能去网站网站建设公司上海
  • 网站组织结构图app一键生成平台免费软件
  • 开源cms建站foxplayer wordpress
  • 网站开发视频教程全国企业工商信息查询系统
  • 临沂seo网站推广淘宝客网站如何做排名
  • 嘉兴制作网站企业个人网站模板怎么用
  • 建网站 方法中国纵横168网站建设系统
  • 打开网站总显示建设中甘肃网站seo技术厂家
  • 网站建设人员组织社区cms
  • 微信公众号运营要求天津seo建站
  • 网站建设的一些知识河南零距离文化传播 网站建设
  • 一个域名可以做多少个二级网站网站的回到顶部怎么做
  • 网站开发视频压缩上传开发手机应用网站
  • 网站开发外包售后维护合同范本网页设计导航栏代码模板
  • 网站改版死链接做淘宝差不多的网站
  • 高端上海网站设计公司价格php mysql网站开发项目式教程
  • 网站域名网站建设和维护价格
  • 黑龙江 网站建设公众号开发网站
  • 建网站金坛哪家强?软文推广500字
  • ps网站头部响应式网站内容布局
  • 怎么用wordpress打开网站想学电商去哪学
  • 电子商务网站策划企业如何建设免费网站
  • 网站建设销售信阿里 域名解析 网站建设