多路IO复用之epoll Linux环境 C语言实现

一、epoll原理

        监听集合中所有的文件描述符, 如果有文件描述符数据准备好了,直接解除阻塞,把数据准备好的文件描述符放到指定内存中。


二、epoll常用函数

1、创建集合空间

        int epoll_create(int size);

 参数:

        size: 指定集合空间的大小

 返回值:成功返回与集合关联的文件描述符,失败返回-1


2、管理集合空间的文件描述符

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

参数:

        epfd: 与集合空间关联的文件描述符

        op: 操作命令

        EPOLL_CTL_ADD:向集合空间中添加文件描述符指令

        EPOLL_CTL_DEL:从集合空间中删除文件描述符

        fd:  操作的文件描述符

        event: 如果是删除操作,该参数忽略,直接传NULL

如果是添加操作,通过该结构体告诉内核监听某个文件描述符的某个事件       

typedef union epoll_data {
    void  *ptr;
    int  fd;
    uint32_t  u32;
    uint64_t  u64;
} epoll_data_t;
struct epoll_event {
    uint32_t events;  /* 设置监听事件:  EPOLLIN表示读事件*/
    epoll_data_t data; /* data是一个联合体,通过联合体中的fd成员设置文件描述符*/
};

3、监听集合

        int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

功能:监听指定集合中所有的文件描述符,如果所有的文件描述符都没有数据准备好, epoll_wait函数阻塞等待。如果有文件描述符数据准备好,epoll_wait直接将直接将准备好的文件描述符信息放到指定数组中。

参数:

        epfd: 与集合空间关联的文件描述符

        events: 传入参数,接收有数据准备好文件描述符信息数组的起始地址

        maxevents: 数组大小

        timeout: 设置超时时间,以毫秒为单位

        >0 :  超时时间

        =0 :  非阻塞函数

        -1 :   永久阻塞

返回值:有三种情况

        >0 : 准备好的文件描述符个数

        =0 :超时时间到

        -1 :出错


三、epoll的特点

1、集合在内核中的内部实现是红黑树

2、监听文件描述符的个数可以指定

3、有数据准备好的文件描述,直接告诉应用层, 放到指定的内存中。


四、三种服务端基本并发方案的比较:

1、多进程:

占用资源太多 只要系统资源允许,同时并发数可以任意

2、多线程:

占用资源较少,但同时并发数受系统描述符数组大小的控制

3、多路复用epoll:

占用资源最少,但仅用于对任意客户端请求的处理都是短平快的场合,且同时并发数受系统描述符数组大小的控制

实际项目中可能会采用以上三种方案的组合,例如:多进程+多线程,多进程+epoll


五、示例代码

模板:

#if   0

1、创建集合空间
 int epoll_create(int size);

2、管理集合中的文件描述符
 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
epfd: 与集合关联的文件描述符
op  :  命令参数
       EPOLL_CTL_ADD: 添加文件描述符
	   EPOLL_CTL_DEL: 删除文件描述符
fd  : 操作的文件描述符
event :  如果是删除操作,该参数忽略直接传NULL 
         如果是添加操作,通过该参数设置监听的事件
		   typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      /* EPOLLIN: 读事件 */
               epoll_data_t data;        /* data.fd:  设置文件描述符 */
           };
返回值: 成功返回0, 出错返回-1

3、监听集合中所有的文件描述符
 int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
参数:
epfd: 与集合关联的文件描述符
events: 承接有数据准备好的文件描述符内存的起始地址
maxevents: 最大元素个数
timeout: 设置超时时间(以毫秒为单位)
	      > 0: 设置超时时间
		  = 0: 非阻塞函数
		  - 1: 永久阻塞函数
返回值:大于0表示有数据准备好的文件描述数量, 0表示超时时间到, -1表示出错

#endif


代码:

client.c
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

// 信号处理函数
void  handle(int sig){
	printf("resv sig: %d\n", sig);
}

int main(){
	int sockfd, ret;
	struct sockaddr_in seraddr; 
	char buff[1024];

	signal(SIGPIPE,  handle);

	// 创建监听套接字
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0){
		perror("socket");
		return -1;
	}

	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(8888);
	inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr );
	// 连接服务端
	ret = connect(sockfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr_in));
	if(ret < 0){
		perror("connnect");
		return -1;
	}

	// 发送请求
	while(1){
		printf("请输入数据:");
        fflush(stdin);
		scanf("%s", buff);
		ret = write(sockfd, buff, strlen(buff));
		if(ret < 0){
			perror("write");
			return -1;
		}
	}

	// 关闭套接字
	close(sockfd);

	return 0;
}
server.c
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/epoll.h>

// 成功返回监听套接字, 出错返回-1
int  sockfd_init(){
	int sockfd, ret;

	//创建监听套接字
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0){
		perror("socket");
		return -1;
	}

	//设置套接字端口复用选项
	int opt = 1;
	ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    if(ret < 0){
		perror("setsockopt");
		return -1;
	}

	struct sockaddr_in seraddr; 
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(8888);
	inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr );
	//绑定ip+port
	ret = bind(sockfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr_in));
	if(ret < 0){
		perror("bind");
		return -1;
	}


	//通知 内核监听
	ret = listen(sockfd, 10);
	if(ret < 0){
		perror("listen");
		return -1;
	}

	return sockfd;
}

int main()
{
	int sockfd, ret, cfd, efd;

	//创建集合空间
	efd = epoll_create(10);
	if(efd < 0){
		perror("epoll_create");
		return -1;
	}

	//创建监听套接字
    sockfd = sockfd_init();
    if(sockfd < 0){
		return -1;
	}

    // 将sockfd加入到集合中
    struct epoll_event ev,evs[10];
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    epoll_ctl(efd,EPOLL_CTL_ADD,sockfd,&ev);

    int count;
	char buff[1024] = "";
    while(1){
        //监听集合中所有文件描述符
		printf("wait...\n");
        count = epoll_wait(efd,evs,10,-1);
        printf("wait over...\n");
        if(count < 0){
			perror("epoll_wait");
			break;
		}
        for(int i = 0; i < count; i++){
            int tfd = evs[i].data.fd;
            if(tfd == sockfd){
                //1、接收客户端
				printf("accept..\n");
				cfd = accept(sockfd, NULL, NULL);
				printf("accept  over..\n");
            
                if(cfd < 0){
				    perror("accept");
				    continue;
			    }
                //2、将cfd加入集合
                ev.data.fd = cfd;
                epoll_ctl(efd,EPOLL_CTL_ADD,cfd,&ev);
            }
            else{//已经建立连接的客户端发来数据
                printf("read..\n");
				ret = read(tfd, buff, 1024);
				printf("read  over..\n");
				if(ret < 0){
					perror("read");
					close(tfd);
					epoll_ctl(efd, EPOLL_CTL_DEL,tfd, NULL);
					continue;
				}
				else if(0 == ret){
					printf("tcp  broken...\n");
					close(tfd);
					epoll_ctl(efd, EPOLL_CTL_DEL,tfd, NULL);
					continue;
				}
                buff[ret] = '\0';
				printf("buff: %s\n", buff);
            }
        }
    }

    return 0;
}

输出:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值