在之前的文章中提到过Readn 函数:
ssize_t Readn(int fd, void *vptr, size_t n)
试想这样一种情况:
1、server 循环使用 epoll_wait,监听 fd,fd 发生读事件,epoll_wait 通知 server。
2、server 接到通知,调用 Readn 函数,读取 500 字节。
3、但是,client 就发送了 200 字节,不足 500 字节。
4、此时,server 会阻塞在 Readn 函数上。
5、意味着 server 无法执行下一次 epoll_wait,意味着即使 client 再给 server 发数据,server 也得不到通知。
6、server 得不到通知,就无法读数据,就无法解除 Readn 函数的阻塞。
结果:造成“死锁”现象。
解决:将“套接字的文件描述符”设置为非阻塞 。
int flag = fcntl(cfd, F_GETFL); // 获得文件原属性
flag |= O_NONBLOCK; // 添加非阻塞属性
fcntl(connfd, F_SETFL, flag); // 使非阻塞属性生效
epoll的“边沿模式”下的“非阻塞读”模型
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(void)
{
struct sockaddr_in servaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int efd, flag;
listenfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 绑定端口和IP
listen(listenfd, 20); // 设置同时访问上限数
struct epoll_event event; // epoll_ctl 传入参数
struct epoll_event resevent[10]; // epoll_wait 传出参数
int res, len;
efd = epoll_create(10); // 创建epoll树根
event.events = EPOLLIN | EPOLLET; /* ET 边沿触发,默认是水平触发 */
printf("Accepting connections ...\n");
truct sockaddr_in cliaddr;
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); // 阻塞等待连接,获得connfd
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port)); // 获得客户端信息
flag = fcntl(connfd, F_GETFL); /* 修改connfd为非阻塞读 */ // connfd 设置为非阻塞
flag |= O_NONBLOCK;
fcntl(connfd, F_SETFL, flag);
event.data.fd = connfd; // 至此epoll_ctl 传入参数设置完毕
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); // 将connfd加入监听红黑树
while (1)
{
printf("epoll_wait begin\n");
res = epoll_wait(efd, resevent, 10, -1); // 最多10个, 阻塞监听
printf("epoll_wait end res %d\n", res);
if (resevent[0].data.fd == connfd)
{
while ((len = read(connfd, buf, MAXLINE / 2)) > 0)
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}
// 如果是 非阻塞,边沿触发:read 不会阻塞,wait 只调用 1 次,while 循环 2 次
// 如果是 非阻塞,水平触发:read 不会阻塞,wait 需调用 2 次,while 循环 2 次