c++⽹络编程下Linux的epoll技术和Windows下的IOCP模
型
⽬录
⼀、IOCP和Epoll之间的异同
1、异
2、同
⼆:Epoll理解与应⽤。
1、epoll是什么?
2、epoll与select对⽐优化
3、epoll是怎么优化select问题的
三、epoll的⼏个函数的介绍:
1、epoll_create函数
2、epoll_ctl函数
3、epoll_wait函数
4、条件触发和边缘触发
四、IOCP理解与应⽤
1、传统服务器的⽹络IO流程
2、使⽤IOCP的基本步骤
⼀、IOCP和Epoll之间的异同
1、异
1).IOCP是WINDOWS系统下使⽤。Epoll是Linux系统下使⽤。
2).IOCP是IO操作完毕之后,通过Get函数获得⼀个完成的事件通知。
Epoll是当你希望进⾏⼀个IO操作时,向Epoll查询是否可读或可写,若处于可读或可写状态,Epoll会通过epoll_wait进⾏通知。
3).IOCP封装了异步的消息事件的通知机制,同时封装了部分IO操作。但Epoll仅仅封装了⼀个异步事件的通知机制,并不负责IO读写操作。Epoll保持了事件通知和IO操作间的独⽴性,更加简单灵活。
4).基于上⾯的描述,我们可以知道Epoll不负责IO操作,所以它只告诉你当前可读可写了,并且将协议读写缓冲填充,由⽤户去读写控制,此时我们可以做出额外的许多操作。IOCP则直接将IO通道⾥的读写操作都做完了才通知⽤户,当IO通道⾥发⽣了堵塞等状况我们是⽆法控制的。
2、同
1).它们都是异步的事件驱动的⽹络模型。
2).它们都可以向底层进⾏指针数据传递,当返回事件时,除可通知事件类型外,还可以通知事件相关数据。
⼆:Epoll理解与应⽤。
1、epoll是什么?
epoll是当前在Linux下开发⼤规模并发⽹络程序的热门⼈选,epoll 在Linux2.6内核中正式引⼊,和select相似,都是I/O多路复⽤(IO multiplexing)技术。
Linux下设计并发⽹络程序,常⽤的模型有:
Apache模型(Process Per Connection,简称PPC)
TPC(Thread PerConnection)模型
select模型和poll模型。
epoll模型
2、epoll与select对⽐优化
基于select的I/O复⽤技术速度慢的原因:
1),调⽤select函数后常见的针对所有⽂件描述符的循环语句。它每次事件发⽣需要遍历所有⽂件描述符,出发⽣变化的⽂件描述符。(以前写的⽰例没加循环)
2),每次调⽤select函数时都需要向该函数传递监视对象信息。即每次调⽤select函数时向操作系统传递
监视对象信息,⾄于为什么要传?是因为我们监视的套接字变化的函数,⽽套接字是操作系统管理的。(这个才是最耗效率的)
注释:基于这样的原因并不是说select就没⽤了,在这样的情况下就适合选⽤select:1,服务端接⼊者少 2,程序应具有兼容性。
3、epoll是怎么优化select问题的
1),每次发⽣事件它不需要循环遍历所有⽂件描述符,它把发⽣变化的⽂件描述符单独集中到了⼀起。
2),仅向操作系统传递1次监视对象信息,监视范围或内容发⽣变化时只通知发⽣变化的事项。
实现epoll时必要的函数和结构体:
函数:
epoll_create:创建保存epoll⽂件描述符的空间,该函数也会返回⽂件描述符,所以终⽌时,也要调⽤close函数。(创建内存空间)
epoll_ctl:向空间注册,添加或修改⽂件描述符。(注册监听事件)
epoll_wait:与select函数类似,等待⽂件描述符发⽣变化。(监听事件回调)
结构体:
struct epoll_event
{
__uint32_t events;
epoll_data_t data;
}
typedef union epoll_data
{
void *ptr;
int fd;
__uinit32_t u32;
__uint64_t u64;
} epoll_data_t;
三、epoll的⼏个函数的介绍:
1、epoll_create函数
/**
* @brief 该函数⽣成⼀个epoll专⽤的⽂件描述符。它其实是在内核申请⼀空间,⽤来存放你想关注的socket fd上是否发⽣以及发⽣了什么事件。
*
* @param size size就是你在这个epoll fd上能关注的最⼤socket fd数
*
* @return ⽣成的⽂件描述符
*/
int epoll_create(int size);
2、epoll_ctl函数
/**
* @brief 该函数⽤于控制某个epoll⽂件描述符上的事件,可以注册事件,修改事件,删除事件。
*
* @param epfd 由 epoll_create ⽣成的epoll专⽤的⽂件描述符
* @param op 要进⾏的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除
* @param fd 关联的⽂件描述符
* @param event 指向epoll_event的指针
*
* @return 0 succ
* -1 fail
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
其中⽤到的数据结构结构如下
op值:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除⼀个fd;
typedef union epoll_data {
void *ptr;
int fd;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
常⽤的事件类型:
EPOLLIN :表⽰对应的⽂件描述符可以读;
EPOLLOUT:表⽰对应的⽂件描述符可以写;
EPOLLPRI:表⽰对应的⽂件描述符有紧急的数据可读
EPOLLERR:表⽰对应的⽂件描述符发⽣错误;
EPOLLHUP:表⽰对应的⽂件描述符被挂断;
EPOLLET:表⽰对应的⽂件描述符有事件发⽣;
例:
struct epoll_event ev;
//设置与要处理的事件相关的⽂件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
3、epoll_wait函数
/**
* @brief 该函数⽤于轮询I/O事件的发⽣
*
* @param epfd 由epoll_create ⽣成的epoll专⽤的⽂件描述符
* @param events ⽤于回传代处理事件的数组
* @param maxevents 每次能处理的事件数
* @param timeout 等待I/O事件发⽣的超时值;-1相当于阻塞,0相当于⾮阻塞。⼀般⽤-1即可
*
* @return >=0 返回发⽣事件数
* -1 错误
*/
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);
⽤改良的epoll实现回声服务端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define BUF_SIZE 100
#define EPOLL_SIZE 50
void error_handling(char *buf);
int main(int argc, const char * argv[]) {
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
socklen_t adr_sz;
int str_len, i;
char buf[BUF_SIZE];
//类似select的fd_set变量查看监视对象的状态变化,epoll_event结构体将发⽣变化的⽂件描述符单独集中到⼀起 struct epoll_event *ep_events;
struct epoll_event event;
int epfd, event_cnt;
if(argc != 2)
{
printf("Usage: %s <port> \n", argv[0]);
}
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if(serv_sock == -1)
error_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)
error_handling("bind() error");
if(listen(serv_sock, 5) == -1)
error_handling("listen() error");
//创建⽂件描述符的保存空间称为“epoll例程”
epfd = epoll_create(EPOLL_SIZE);
ep_events = malloc(sizeof(struct epoll_event) *EPOLL_SIZE);
//添加读取事件的监视(注册事件)
event.events = EPOLLIN; //读取数据事件
event.data.fd = serv_sock;
epoll_ctl(epdf, EPOLL_CTL_ADD, serv_sock, &event);
while (1)
{
//响应事件,返回发⽣事件的⽂件描述符数
event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); //传-1时,⼀直等待直到事件发⽣ if(event_cnt == -1)
{
puts("epoll_wait() error");
break;
}
//服务端套接字和客服端套接字
for (i = 0; i < event_cnt; i++) {
if(ep_events[i].data.fd == serv_sock)//服务端与客服端建⽴连接
{
adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
event.events = EPOLLIN;
event.data.fd = clnt_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
printf("connected client: %d \n", clnt_sock);
}
else //连接之后传递数据
linux怎么读文件内容{
str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
if(str_len == 0)
{
//删除事件
epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
close(ep_events[i].data.fd);
printf("closed client: %d \n", ep_events[i].data.fd);
}
else
{
write(ep_events[i].data.fd, buf, str_len);
}
}
}
}
close(serv_sock);
close(epfd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
epoll客户端代码:
#define _GNU_SOURCE
#include "sysutil.h"
#include "buffer.h"
#include <sys/epoll.h>
int main(int argc, char const *argv[])
{
//创建client套接字
int sockfd = tcp_client(0);
/
/调⽤⾮阻塞connect函数
int ret = nonblocking_connect(sockfd, "localhost", 9981, 5000); if(ret == -1)
{
perror("Connect Timeout .");
exit(EXIT_FAILURE);
}
//将三个fd设置为Non-Blocking
activate_nonblock(sockfd);
activate_nonblock(STDIN_FILENO);
activate_nonblock(STDOUT_FILENO);
buffer_t recvbuf; //sockfd -> Buffer -> stdout
buffer_t sendbuf; //stdin -> Buffer -> sockfd
//初始化缓冲区
buffer_init(&recvbuf);
buffer_init(&sendbuf);
//创建epoll
int epollfd = epoll_create1(0);
if(epollfd == -1)
ERR_EXIT("create epoll");
struct epoll_event events[1024];
uint32_t sockfd_event = 0;
uint32_t stdin_event = 0;
uint32_t stdout_event = 0;
epoll_add_fd(epollfd, sockfd, sockfd_event);
epoll_add_fd(epollfd, STDIN_FILENO, stdin_event);
epoll_add_fd(epollfd, STDOUT_FILENO, stdout_event);
while(1)
{
//重新装填epoll事件
sockfd_event = 0;
stdin_event = 0;
stdout_event = 0;
//epoll⽆法每次都重新装填,所以给每个fd添加⼀个空事件 if(buffer_is_readable(&sendbuf))
{
sockfd_event |= kWriteEvent;
}
if(buffer_is_writeable(&sendbuf))
{
stdin_event |= kReadEvent;
}
if(buffer_is_readable(&recvbuf))
{
stdout_event |= kWriteEvent;
}
if(buffer_is_writeable(&recvbuf))
{
sockfd_event |= kReadEvent;
}
epoll_mod_fd(epollfd, sockfd, sockfd_event);
epoll_mod_fd(epollfd, STDIN_FILENO, stdin_event);
epoll_mod_fd(epollfd, STDOUT_FILENO, stdout_event);
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论