B站就业班视频代码搬运 p54
但是我跟老师的代码还是有点区别。。老师那里居然ev复用。。那么数组里那些结构体都用不上??
注意,本篇不是epoll反应堆。
I/O多路复用一共有select , poll ,epoll等模型,但是真正的高并发的话是epoll。原因:selcet 和poll都是把监控的文件描述符从应用程序拷贝到内核,然后挨个询问再把有事件发生的拷贝到程序内存,总之消耗很大,最好不好超过1024。所以掌握epoll更好。
当epoll采用默认的水平触发模式的时候,可以认为是一个更快速的Poll .
epoll有简单版本和复杂版本,复杂的叫做epoll反应堆模型,需要你自定义一个结构体,其中的内容你自己写,但是必须至少包括 1个文件描述符、1个回调函数、1个参数。哈哈这部分我还没看懂,所以今天搬运一个简单的epoll服务器代码啦
详细内容参考这位博主的文章
详细参考
int epoll_create(int size);
参数:必须填写一个大于0的数。
返回值
成功: 一个非零的 文件描述符,是红黑树的 树根节点。
失败:-1 并设置errno
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 */
};
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.
客户端的代码,前面一篇文章去拷贝就行了
//epoll模型
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//设置端口复用
int opt =1;
setsockopt ( sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
//定义服务器地址的 结构体,利用本机任意ip,端口为8888
struct 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 (epfd<0){
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;i
默认都是水平触发,只要缓冲区有字符,就一直读,写100个,读限制为30字符,那就读3次。如何判断出来的呢?
把上面 n =read ( XXX 最后一个参数改成2 ) 让它每次只能读俩字符。然后你再客户端中,敲10个字符。这时你会发现,服务器分6次向你返回了所有字符的大写(最后又一个空回车)
说明默认是LT模式
XXXX.events = EPOLLIN | EPOLLET; /*边沿触发 */
read函数 最后一个参数改成2 ,再把通信描述符设置成ET模式,
//将新的通信描述符 newfd上树
evarray[i].events =EPOLLIN | EPOLLET;
你发送10个字符之后,服务器只返回俩字符;你再敲回车,又返回俩字符;直到读完为止。
while 1 加上去看似很简单
//现在是一口气读完所有数据ETmoshi
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//设置端口复用
int opt =1;
setsockopt ( sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
//定义服务器地址的 结构体,利用本机任意ip,端口为8888
struct 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 (epfd<0){
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;i
但是此时会出现一个BUG 第一个客户端连接了以后,其他客户端的连接,服务器似乎不管了。。只有眼前这一个退出了才行。。
原因:read函数返回值小于0的时候,你关闭了文件描述符。。就算你不写关闭文件描述符的代码,read 或者recv函数一直在那里等待着。
疯了,这段代码居然折腾了4个小时才跑出来
epoll模型设置ET模式
你发送10个字符,read函数却一次只能读俩
如何一口气读完呢
方法:设置通信描述符newfd为 非阻塞模式
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//设置端口复用
int opt =1;
setsockopt ( sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
//定义服务器地址的 结构体,利用本机任意ip,端口为8888
struct 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 (epfd<0){
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;i