I/O多路复用-epoll
epoll与select和poll在使用和实现上有很大区别。首先,epoll使用一组函数来完成,而不是单独的一个函数;其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,无需向select和poll那样每次调用都要重复传入文件描述符集合事件集。
IO多路复用epoll模型优缺点
思路:单进程调用epoll_wait()函数来处理多个连接请求
优点:单进程可执行同时处理多个网络连接请求,可以达到硬件上限,性能上由于只管理就绪连接,不需要轮询事件集合,因此性能大大提高
缺点:仅支持linux平台
函数
epoll_create()
size参数并不起作用,只是给内核一个提示,告诉它事件表需要多大。
epoll_ctl()
epfd:文件句柄,标识事件表
op:操作类型,
EPOLL_CTL_ADD:注册fd上的事件
EPOLL_CTL_MOD:修改fd上注册的事件
EPOLL_CTL_DEL:删除fd上注册的事件
event:指定事件,定义如下
常用events:
EPOLLIN:可读事件
EPOLLOUT:可写事件
EPOLLET:边缘事件
epoll_wait()
服务器端示例
这里使用epoll_data联合体中的fd, ptr的使用将在以后讲解。
ptr的使用链接:epoll_data.ptr
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#define MAX_SIZE 1024
int main(int argc, char *argv[])
{
int sockfd;
int ret;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int client_sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0){
perror("socket error");
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(9000);
server_addr.sin_addr.s_addr = inet_addr("192.168.121.128");
int len = sizeof(struct sockaddr_in);
ret = bind(sockfd, (struct sockaddr*)&server_addr, len);
if (ret == -1){
perror("bind error");
return -1;
}
ret = listen(sockfd, MAX_SIZE);
if (ret < 0){
perror("listen error");
return -1;
}
int epfd, nfds = 0, i = 0;
struct epoll_event ev, evs[MAX_SIZE];
//创建一个epoll监听集合
epfd = epoll_create(MAX_SIZE);
ev.events = EPOLLIN;
ev.data.fd = sockfd;
//将监听socket描述符与定义好的事件添加到epoll监听集合中
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) < 0){
perror("epoll_ctl error");
close(sockfd);
return -1;
}
char buff[1024] = {0};
while (1){
//epoll阻塞等待事件触发,当某个描述符触发事件,则将触发的事件写入到evs中
//并且返回触发事件的描述符个数nfds
nfds = epoll_wait(epfd, evs, MAX_SIZE, 3000);
if (nfds < 0){
//epoll等待出错
perror("epoll_wait error");
continue;
}else if (nfds == 0){
//3000毫秒内没有任何描述符触发事件
printf("timeout\n");
continue;
}
//循环从evs中取出触发的事件
for (i = 0;i<nfds;i++){
//如果evs[i].data.fd == sockfd,代表客户端请求通道有客户端请求到来
//则接受客户端连接请求
if (evs[i].data.fd == sockfd){
client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &len);
if (client_sockfd < 0){
perror("new client error");
continue;
}
printf("new accept----ip:[%s],port:[%d]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
ev.events = EPOLLIN;
ev.data.fd = client_sockfd;
//add event
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, client_sockfd, &ev);
if (ret < 0){
perror("epoll_ctl error");
continue;
}
//EPOLLIN代表可读事件
}else if (evs[i].events & EPOLLIN){
//代表有其他数据传输,则接受数据
memset(buff, 0x00, 1024);
ret = recv(evs[i].data.fd, buff, 1024, 0);
if (ret <= 0){
//ret == 0代表对端断开了tcp连接
//ret < 0 出错
//EINTR被信号打断出错
//EAGAIN描述符没有准备好
if (errno == EAGAIN || errno == EINTR){
continue;
}
//除了上面2个错误之外则从监听集合中删除描述符并关闭socket
epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &ev);
close(evs[i].data.fd);
}
printf("%s--]\n", buff);
}
}
}
close(sockfd);
return 0;
}
客户端示例
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int sock_fd;
struct sockaddr_in ser_addr;
struct sockaddr_in cli_addr;
char buff[1024] = {0};
//调用socket函数创建套接字
sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock_fd < 0){
perror("socket error!");
return -1;
}
//赋值发送地址信息,服务器端地址信息
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(9000);
ser_addr.sin_addr.s_addr = inet_addr("192.168.121.128");
cli_addr.sin_family = AF_INET;
cli_addr.sin_port = htons(8000);
cli_addr.sin_addr.s_addr = inet_addr("192.168.121.128");
int len = sizeof(struct sockaddr_in);
int ret;
//调用 bind 函数绑定一个发送地址信息
ret = bind(sock_fd, (struct sockaddr*)&cli_addr, len);
if (ret < 0){
perror("bind error!!!");
return -1;
}
//调用 connect 函数连接服务器
ret = connect(sock_fd, (struct sockaddr*)&ser_addr, len);
if (ret < 0){
perror("connect error!!!");
return -1;
}
int i = 0;
while (1){
memset(buff, 0x00, 1024);
sprintf(buff, "test-%d", i++);
//连接成功后,调用 send 函数发送数据到服务器端
send(sock_fd, buff, 1024, 0);
sleep(1);
}
//调用 close 函数关闭套接字
close(sock_fd);
return 0;
}