wordpress建站心得,山东网站建设报价,建设网站公司域名,个人网站建设第一步B站就业班视频代码搬运 p54
但是我跟老师的代码还是有点区别。。老师那里居然ev复用。。那么数组里那些结构体都用不上#xff1f;#xff1f;
注意#xff0c;本篇不是epoll反应堆。
I/O多路复用一共有select , poll ,epoll等模型#xff0c;但是真正的高并发的话是epo…B站就业班视频代码搬运 p54
但是我跟老师的代码还是有点区别。。老师那里居然ev复用。。那么数组里那些结构体都用不上
注意本篇不是epoll反应堆。
I/O多路复用一共有select , poll ,epoll等模型但是真正的高并发的话是epoll。原因selcet 和poll都是把监控的文件描述符从应用程序拷贝到内核然后挨个询问再把有事件发生的拷贝到程序内存总之消耗很大最好不好超过1024。所以掌握epoll更好。
当epoll采用默认的水平触发模式的时候可以认为是一个更快速的Poll . epoll有简单版本和复杂版本复杂的叫做epoll反应堆模型需要你自定义一个结构体其中的内容你自己写但是必须至少包括 1个文件描述符、1个回调函数、1个参数。哈哈这部分我还没看懂所以今天搬运一个简单的epoll服务器代码啦
详细内容参考这位博主的文章
详细参考
1.基本函数介绍
1.1 创建epoll红黑树 int epoll_create(int size); 参数必须填写一个大于0的数。
返回值 成功 一个非零的 文件描述符是红黑树的 树根节点。 失败-1 并设置errno
1.2 添加监控的文件描述符上树、修改、删除 int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event); epfd就是上面那个create 函数的返回值。
op的值 EPOLL_CTL_ADD 将一个文件描述符添加到epoll树上 EPOLL_CTL_MOD 改变树上某个文件描述符的一些属性设定。 EPOLL_CTL_DEL 将某个制定的文件描述符从列表中删除此时第四个参数可以写NULL 但是注意有BUG 自己man epoll_ctl 自己看一下BUG吧我没看
fd: 你要处理的文件描述符。
struct epoll_event *event 肯定是这个名叫event的地址啦那么event的数据结构是啥它是一种结构体名字叫做 epoll_event . typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; 这个void*万能指针在后面的 epoll反应堆模型里有很大用处当然这里没啥用 这里用fd就行了 struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; 1.3 回收 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); epfd: 红黑树的树根 struct epoll_event *event 这里不是单个event的地址了 而是数组的首地址这个数组每个成员都是结构体epoll_event
maxevents: 一般是1024 反正必须大于0
timeout: 你自己规定的超时时间单位是毫秒
-1 永远不算超时一直等待、阻塞
0 立即返回
返回值
成功 在 I/O读写中已经就绪的文件描述符的个数, 如果一个都没有就返回0
失败 -1 and errno is set appropriately.
2.使用epoll的高并发服务器
客户端的代码前面一篇文章去拷贝就行了
//epoll模型
#include unistd.h
#include stdlib.h
#include stdio.h
#include string.h
#include sys/types.h
#include netinet/in.h
#include sys/socket.h
#include arpa/inet.h
#include ctype.h
#include errno.h
#include sys/epoll.hint main() {int sfd socket(AF_INET, SOCK_STREAM, 0); //设置端口复用int opt 1;setsockopt ( sfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(int));//定义服务器地址的 结构体,利用本机任意ip端口为8888struct sockaddr_in servad;bzero(servad, sizeof(servad));servad.sin_family AF_INET;servad.sin_port htons(8888);servad.sin_addr.s_addr htonl (INADDR_ANY);int ret bind(sfd, (struct sockaddr*)servad, sizeof( servad));//监听描述符sfd 设置完成listen(sfd,128); //创建一颗 epoll红黑树int epfd epoll_create(1024); if (epfd0){perror (epoll create fail\n);return -1;} //将监听描述符添加到树上struct epoll_event sev;sev.events EPOLLIN;sev.data.fd sfd;epoll_ctl (epfd, EPOLL_CTL_ADD,sfd,sev);struct epoll_event evarray[1024];int nready 0; while (1) {//超时时间设置-1所以进程会无限阻塞在这一步除非有了变化才往下走nready epoll_wait (epfd,evarray,1024,-1);if (nready 0) {if (errno EINTR){continue;}break;}for (int i 0;inready;i){int sockfd evarray[i].data.fd;//第一种情况有客户端的新请求if (sockfd sfd) {int newfd accept (sfd,NULL, NULL);//将新的通信描述符newfd上树evarray[i].events EPOLLIN;evarray[i].data.fd newfd;epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, (evarray[i]));continue;}//第二种情况眼前的描述符有可读事件发生 char buf[256];//读数据memset(buf,0x00,sizeof(buf));int n read (sockfd, buf, sizeof(buf));//int n recv (sockfd, buf, sizeof(buf), 0);if (n 0){ printf(这里是服务器端, read error or client close\n); close (sockfd);//sockfd 下树这里第四个参数可是NULLepoll_ctl (epfd, EPOLL_CTL_DEL, sockfd, NULL);continue;}else {printf (这里是服务器端n %d ,读到的是%s \n, n,buf);//将小写字母都转化wie大写for (int j 0; jn; j) { buf[j] toupper (buf[j]); } //发送数据write (sockfd, buf, n);}}}
close (sfd);
close (epfd);
return 0;
}3. 如何测试你的epoll是ET (边缘触发 还是LT 水平触发
默认都是水平触发只要缓冲区有字符就一直读写100个读限制为30字符那就读3次。如何判断出来的呢
3.1 判断epoll是LT的代码
把上面 n read ( XXX 最后一个参数改成2 让它每次只能读俩字符。然后你再客户端中敲10个字符。这时你会发现服务器分6次向你返回了所有字符的大写最后又一个空回车
说明默认是LT模式
3.2 ET 模式怎么设置
XXXX.events EPOLLIN | EPOLLET; /*边沿触发 */
read函数 最后一个参数改成2 再把通信描述符设置成ET模式 //将新的通信描述符 newfd上树 evarray[i].events EPOLLIN | EPOLLET; 你发送10个字符之后服务器只返回俩字符你再敲回车又返回俩字符直到读完为止。
3.3 ET模式下就想一口气读完所有数据
while 1 加上去看似很简单
//现在是一口气读完所有数据ETmoshi
#include unistd.h
#include stdlib.h
#include stdio.h
#include string.h
#include sys/types.h
#include netinet/in.h
#include sys/socket.h
#include arpa/inet.h
#include ctype.h
#include errno.h
#include sys/epoll.hint main() {int sfd socket(AF_INET, SOCK_STREAM, 0); //设置端口复用int opt 1;setsockopt ( sfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(int));//定义服务器地址的 结构体,利用本机任意ip端口为8888struct sockaddr_in servad;bzero(servad, sizeof(servad));servad.sin_family AF_INET;servad.sin_port htons(8888);servad.sin_addr.s_addr htonl (INADDR_ANY);int ret bind(sfd, (struct sockaddr*)servad, sizeof( servad));//监听描述符sfd 设置完成listen(sfd,128); //创建一颗 epoll红黑树int epfd epoll_create(1024); if (epfd0){perror (epoll create fail\n);return -1;} //将监听描述符添加到树上struct epoll_event sev;sev.events EPOLLIN;sev.data.fd sfd;epoll_ctl (epfd, EPOLL_CTL_ADD,sfd,sev);struct epoll_event evarray[1024];int nready 0; while (1) {//超时时间设置-1所以进程会无限阻塞在这一步除非有了变化才往下走nready epoll_wait (epfd,evarray,1024,-1);if (nready 0) {if (errno EINTR){continue;}break;}for (int i 0;inready;i){int sockfd evarray[i].data.fd;//第一种情况有客户端的新请求if (sockfd sfd) {int newfd accept (sfd,NULL, NULL);//将新的通信描述符newfd上树evarray[i].events EPOLLIN | EPOLLET;evarray[i].data.fd newfd;epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, (evarray[i]));continue;}//第二种情况眼前的描述符有可读事件发生 char buf[256];while (1) {//读数据memset(buf,0x00,sizeof(buf));int n read (sockfd, buf, 2);//int n recv (sockfd, buf, sizeof(buf), 0);if (n 0){ printf(这里是服务器端, read error or client close\n); close (sockfd);epoll_ctl (epfd, EPOLL_CTL_DEL, sockfd, NULL);break;}else {printf (这里是服务器端n %d ,读到的是%s \n, n,buf);//将小写字母都转化wie大写for (int j 0; jn; j) { buf[j] toupper (buf[j]); } //发送数据write (sockfd, buf, n);}}}}
close (sfd);
close (epfd);
return 0;
}但是此时会出现一个BUG 第一个客户端连接了以后其他客户端的连接服务器似乎不管了。。只有眼前这一个退出了才行。。
原因read函数返回值小于0的时候你关闭了文件描述符。。就算你不写关闭文件描述符的代码read 或者recv函数一直在那里等待着。
3.3 如何解决新问题 ET模式
将传递信息的文件描述符通信描述符设置为非阻塞(未完待续