Epoll编程的基本步骤

Epoll编程

  • 解决select的数量限制
  • 使用与高并发 ,例如c10k

epoll用作处理大量文字描述符,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发外,还提供了边缘触发,这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

epoll结构体

typedef union epoll_data {  
    void *ptr;  
    int fd;  
    __uint32_t u32;  
    __uint64_t u64;  
} epoll_data_t;  

struct epoll_event {  
    __uint32_t events; /* Epoll events */  
    epoll_data_t data; /* User data variable */  
};  
#include<sys/epoll.h>

epoll接口

  • int epoll_create(int size)

这个函数创建一个 epoll 的句柄,
size 用来告诉内核这个监听的数目一共有多大。需要注意的是,当创建好 epoll 句柄后,
它会一直占用一个文件描述符,直到调用 close() 关闭才会释放

注册函数

  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

这个函数是 epoll 的事件注册函数,相当于告诉 epoll 你要监测什么样的事件。

  • 第一个 参数是 epoll_create() 函数创建的句柄
  • 第二个 参数表示动作,用三个宏来表示:
    • EPOLL_CTL_ADD:注册新的 fd 到 epfd 中;
    • EPOLL_CTL_MOD:修改已经注册的 fd 的监听事件;
    • EPOLL_CTL_DEL:从 epfd 中删除一个fd;
  • 第三个 参数是需要监听的 fd;
  • 第四个 参数是告诉内核需要监听什么事, struct epoll_event 结构如下:
typedef union epoll_data {  
void* ptr;  
int fd;  
__uint32_t u32;  
__uint64_t u64;  
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};```

events 可以是以下几个宏的集合:
EPOLLIN:表示对应的文件描述符可以读(包括对端正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将 EPOLL 设为边缘触发 (Edge Triggered) 模式,这是相对于水平触发 (Level Triggered) 来说的。
EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入到 EPOLL 队列里
## 收集在epoll监控的事件中已经发送的事件   
 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
* 参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)
> 等待事件的产生,类似于 select() 调用。参数 events 用来从内核得到事件的集合,
> maxevents 告诉内核这个 events 有多大,这个 maxevents 的值不能大于创建 epoll_create() 时的 size ,
> 参数 timeout 是超时时间(单位:毫秒, 0 会立即返回, -1 会一直阻塞)。该函数返回需要处理的事件数目,如返回 0 表示已超时。


## 原理
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销



## demo

#include <stdio.h>

#include <unistd.h>

#include <errno.h>

#include <sys/epoll.h>

int main(int argc, char** argv)
{
int ep = epoll_create(1024);
if (ep < 0) {
printf(“epoll_create failed\n”);
return 1;
}

epoll_event event;
event.events = EPOLLIN;
event.data.fd = STDIN_FILENO;
 
//添加读事件
int status = epoll_ctl(ep, EPOLL_CTL_ADD, STDIN_FILENO, &event);
if (status < 0) {
    printf("epoll_ctl failed\n");
    return 1;
}
 
epoll_event events[1024];
for (;;) {
    int nsd = epoll_wait(ep, events, 1024, -1);
    if (nsd > 0) {
        for (int i = 0; i < nsd; i++) {
            if (events[i].data.fd == STDIN_FILENO) {
                char buf[100] = {0};
                read(events[i].data.fd, buf, 100);
                printf("STDIN: %s\n", buf);
            }
        }
    }
}
return 0;

}


* demo server

#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#define MAXLINE 10
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5555
#define INFTIM 1000

void setnonblocking(int sock)
{
int opts;
opts = fcntl(sock, F_GETFL);

if(opts < 0) {
	perror("fcntl(sock, GETFL)");
	exit(1);
}

opts = opts | O_NONBLOCK;

if(fcntl(sock, F_SETFL, opts) < 0) {
	perror("fcntl(sock, SETFL, opts)");
	exit(1);
}

}

int main(int argc, char *argv[])
{
printf(“epoll socket begins.\n”);
int i, maxi, listenfd, connfd, sockfd, epfd, nfds;
ssize_t n;
char line[MAXLINE];
socklen_t clilen;

struct epoll_event ev, events[20];

epfd = epoll_create(256);

struct sockaddr_in client   addr;
struct sockaddr_in serveraddr;

listenfd = socket(AF_INET, SOCK_STREAM, 0);

setnonblocking(listenfd);

ev.data.fd = listenfd;
ev.events = EPOLLIN | EPOLLET;

epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr = "127.0.0.1";
inet_aton(local_addr, &(serveraddr.sin_addr));
serveraddr.sin_port = htons(SERV_PORT);

bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

listen(listenfd, LISTENQ);

maxi = 0;

for(; ;) {
	nfds = epoll_wait(epfd, events, 20, 500);

	for(i = 0; i < nfds; ++i) {
		if(events[i].data.fd == listenfd) {
			printf("accept connection, fd is %d\n", listenfd);
			connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
			if(connfd < 0) {
				perror("connfd < 0");
				exit(1);
			}

			setnonblocking(connfd);

			char *str = inet_ntoa(clientaddr.sin_addr);
			printf("connect from %s\n", str);

			ev.data.fd = connfd;
			ev.events = EPOLLIN | EPOLLET;
			epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
		}
		else if(events[i].events & EPOLLIN) {
			if((sockfd = events[i].data.fd) < 0) continue;
			if((n = read(sockfd, line, MAXLINE)) < 0) {
				if(errno == ECONNRESET) {
					close(sockfd);
					events[i].data.fd = -1;
				} else {
					printf("readline error");
				}
			} else if(n == 0) {
				close(sockfd);
				events[i].data.fd = -1;
			}
			
			printf("received data: %s\n", line);

			ev.data.fd = sockfd;
			ev.events = EPOLLOUT | EPOLLET;
			epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
		}
		else if(events[i].events & EPOLLOUT) {
			sockfd = events[i].data.fd;
			write(sockfd, line, n);

			printf("written data: %s\n", line);

			ev.data.fd = sockfd;
			ev.events = EPOLLIN | EPOLLET;
			epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
		}
	}
}

}


client

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>

#define MAXBUF 1024
/*********************************************************************

  • filename: select-client.c
  • 演示网络异步通讯,这是客户端程序
    *********************************************************************/

int main(int argc, char **argv)
{
int sockfd, len;

struct sockaddr_in dest;
char buffer[MAXBUF + 1];

fd_set rfds;
struct timeval tv;

int retval, maxfd = -1;

if (argc != 3)
{
    printf("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息", argv[0], argv[0]);
    exit(0);
}

// 创建一个 socket 用于 tcp 通信 
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
{
    perror("Socket");
    exit(errno);
}

// 初始化服务器端(对方)的地址和端口信息
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(argv[2]));

if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) 
{
    perror(argv[1]);
    exit(errno);
}

// 连接服务器 
if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) 
{
    perror("Connect ");
    exit(errno);
}

printf("\n准备就绪,可以开始聊天了……直接输入消息回车即可发信息给对方\n");

while (1) 
{
    // 把集合清空 
    FD_ZERO(&rfds);
    // 把标准输入句柄0加入到集合中
    FD_SET(0, &rfds);

    maxfd = 0;
    // 把当前连接句柄sockfd加入到集合中
    FD_SET(sockfd, &rfds);

    if (sockfd > maxfd)
        maxfd = sockfd;

    // 设置最大等待时间 
    tv.tv_sec = 3;
    tv.tv_usec = 0;

    // 开始等待
    retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);

    if (retval == -1) 
{
        printf("将退出,select出错! %s", strerror(errno));
        break;
    } 
    else if (retval == 0) 
{
        /* printf("没有任何消息到来,用户也没有按键,继续等待……\n"); */
        continue;
    } 
    else 
{
        if (FD_ISSET(sockfd, &rfds)) // 连接的socket上有消息到来则接收对方发过来的消息并显示 
    {               
            bzero(buffer, MAXBUF + 1);
            // 接收对方发过来的消息,最多接收 MAXBUF 个字节 
            len = recv(sockfd, buffer, MAXBUF, 0);
	
            if (len > 0)
	{
                printf("接收消息成功:'%s',共%d个字节的数据\n", buffer, len);
	}
            else 
	{
                if (len < 0)
	    {
                    printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
	    }
                else
	    {
                    printf("对方退出了,聊天终止!\n");
	    }
                break;
            }
        }
        
        if (FD_ISSET(0, &rfds)) // 用户按键了,则读取用户输入的内容发送出去
    {              
            bzero(buffer, MAXBUF + 1);
            fgets(buffer, MAXBUF, stdin);
            if (!strncasecmp(buffer, "quit", 4)) 
	{
                printf("自己请求终止聊天!\n");
                break;
            }
            
            // 发消息给服务器
            len = send(sockfd, buffer, strlen(buffer) - 1, 0);
            if (len < 0) 
	{
                printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buffer, errno, strerror(errno));
                break;
            } 
            else
	{
                printf("消息:%s\t发送成功,共发送了%d个字节!\n", buffer, len);
	}
        }
    }
}

// 关闭连接 
close(sockfd);
return 0;

}


---------

会有个别错字或字母

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值