您的位置:首页 > 其它

epoll详解(三)-- ET模式实例

2016-06-23 23:00 435 查看
通过本文你会了解到:

1. 非阻塞预备知识点

2. 非阻塞server源码

3. 运行测试(应用linux 的 nc 工具)

非阻塞预备知识点

O_NONBLOCK
- 应用于socket描述符时,另之后对此描述符的阻塞操作(如read/write等)变为非阻塞。

EAGAIN
- 表示没有可用数据,稍后再试。产生此错误的前提条件是socket描述符为非阻塞时(即,设置为
O_NONBLOCK
标志)。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。

在非阻塞socket编程时,为保证兼容性问题,最好同时判断
EAGAIN
EWOULDBLOCK
标志。

非阻塞server源码

约定

格式为 /**/ 的注释对程序的主要流程进行解释

格式为 // 的注释对程序做必要的说明

#include <stdio.h> // for printf()
#include <stdlib.h> // for exit()
#include <unistd.h> // for read() and write()
#include <sys/epoll.h> // for epoll
#include <sys/socket.h> // for epoll
#include <netinet/in.h> //struct sockaddr_in
#include <string.h> // memset
#include <fcntl.h> // fcntl
#include <errno.h> // errno

#define EPOLL_QUEUE_LEN 32 //监听的最大连接数
#define BUF_SIZE 1024

static int set_nonblocking(int fd)
{
int flags;

flags = fcntl(fd, F_GETFL, 0);
if(flags == -1) {
perror("fcntl");
return -1;
}

flags |= O_NONBLOCK;

if(fcntl(fd, F_SETFL, flags) == -1) {
perror("fcntl");
return -1;
}

return 0;
}

static int listen_socket(int port)
{
int fd;
struct sockaddr_in sa;

fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1) {
perror("socket");
return -1;
}

memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
perror("bind");
close(fd);
return -1;
}

if(set_nonblocking(fd) == -1) {
close(fd);
return -1;
}

if(listen(fd, 5) == -1) {
perror("listen");
close(fd);
return -1;
}

return fd;
}

int main(int argc, char **argv)
{
struct epoll_event ev;
struct epoll_event events[EPOLL_QUEUE_LEN];
int epfd;
int sfd;
int nfds;

if(argc != 2) {
printf("usage: %s [port]\n", argv[0]);
exit(EXIT_FAILURE);
}

/*创建 epoll 实例*/
epfd = epoll_create(EPOLL_QUEUE_LEN);
if(epfd == -1) {
perror("epoll_create");
exit(EXIT_FAILURE);
}

/*对输入port进行监听*/
printf("server listen form port: %d\n", atoi(argv[1]));
sfd = listen_socket(atoi(argv[1]));
if(sfd == -1) {
printf("listen_socket failed\n");
exit(EXIT_FAILURE);
}

/*将server描述符添加到epoll中*/
ev.data.fd = sfd;
ev.events = EPOLLIN | EPOLLET;
if(epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}

/*主循环*/
while(1) {
int i;

/*等待epoll实例中描述符有I/O事件发生*/
nfds = epoll_wait(epfd, events, EPOLL_QUEUE_LEN, -1);
for(i = 0; i < nfds; i++) {
if(events[i].events & (EPOLLERR | EPOLLHUP)) {
//EPOLLERR - 出现错误
//EPOLLHUP - 客户端提前关闭连接(close by peer)
continue;
}

if(!(events[i].events & EPOLLIN)) { //不是IN操作
continue;
}

if(sfd == events[i].data.fd) {
/*有客户端连入server*/
struct sockaddr_in in_addr;
socklen_t in_len;
int infd;

while(1) { //非阻塞操作accept需要循环检测
infd = accept(sfd, (struct sockaddr *)&in_addr, &in_len);
if(infd == -1) {
if(errno == EAGAIN || errno == EWOULDBLOCK) {
///已接收到所有描述符
break;
} else {
perror("accept");
break;
}
}
if(set_nonblocking(infd) == -1) {
break;
}

ev.data.fd = infd;
ev.events = EPOLLIN | EPOLLET;
if(epoll_ctl(epfd, EPOLL_CTL_ADD, infd, &ev) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
printf("incoming client [fd=%d]\n", infd);
}
} else {
/*收到客户端数据*/
int done = 0;

while(1) {
ssize_t cnt;
char buf[BUF_SIZE];

memset(buf, 0, sizeof(buf));
cnt = read(events[i].data.fd, buf, sizeof(buf));
if(cnt == -1) {
if(errno == EAGAIN) {
done = 1;
break;
} else {
perror("read");
}
} else if (cnt == 0) { //fd 被关闭
done = 1;
break;
}

printf("receive data: %s\n", buf);
}

if(done == 1) {
printf("close client [fd=%d]\n", events[i].data.fd);
close(events[i].data.fd);
}
}
}
}

close(sfd);

return 0;
}


运行测试

linux中nc是一个强大的网络工具,本文只用nc来创建一个socket客户端,来测试epoll server,如果想深入了解nc可以参考linux
man nc


nc创建客户端命令:

接入server:
nc ip port
- ip 为server的IP地址 prot为server的端口

当接入server后输入数据并回车即可向server发送数据。

测试截图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息