Linux IO多路复用之epoll网络编程攻略
什么是IO多路复用
IO多路复用是一种异步I/O模型,允许单个进程同时监控多个文件描述符,当某个文件描述符发生IO事件时,可以及时地通知进程进行处理,提高系统的资源利用率和IO效率。
在Linux中,IO多路复用主要有三种实现方法:select、poll和epoll。其中,epoll是Linux2.6内核中引入的新机制,性能更优,使用范围更广。
epoll的工作原理
epoll的工作原理与select和poll类似,都是通过监听文件描述符集合来实现IO多路复用。但是,epoll具有以下几个优点:
- 基于事件驱动,只有在IO事件发生时才会通知进程,而不是像select和poll那样需要轮询。
- 支持边缘触发和水平触发两种模式,能够更精细地控制IO事件的处理。
- 支持基于文件描述符的事件注册方式,不需要每次都要重复注册和复制文件描述符集合。
epoll的使用步骤
使用epoll实现IO多路复用需要完成以下几个步骤:
- 调用epoll_create创建一个epoll对象。
- 定义一个事件结构体epoll_event,用于存储每个事件的详细信息。
- 调用epoll_ctl向epoll对象中添加、删除或修改文件描述符的事件。
- 调用epoll_wait等待IO事件的发生。
- 处理IO事件。
epoll示例
下面给出两个epoll的示例,一个是TCP服务器程序示例,另一个是UDP客户端和服务器程序示例。
TCP服务器程序示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define MAX_EVENTS 1024
#define PORT 8888
int main() {
int listenfd, connfd, epollfd, nfds, i;
char buf[1024];
struct sockaddr_in servaddr, cliaddr;
socklen_t clilen = sizeof(cliaddr);
struct epoll_event ev, events[MAX_EVENTS];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("socket error");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind error");
return -1;
}
if(listen(listenfd, 5) < 0) {
perror("listen error");
return -1;
}
epollfd = epoll_create(MAX_EVENTS);
if (epollfd < 0) {
perror("epoll_create error");
return -1;
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listenfd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev) < 0) {
perror("epoll_ctl error");
return -1;
}
while (1) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds < 0) {
perror("epoll_wait error");
continue;
}
for (i = 0; i < nfds; i++) {
if (events[i].data.fd == listenfd) {
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
if (connfd < 0) {
perror("accept error");
continue;
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = connfd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev) < 0) {
perror("epoll_ctl error");
return -1;
}
} else if (events[i].events & EPOLLIN) {
connfd = events[i].data.fd;
int n = read(connfd, buf, sizeof(buf));
if (n < 0) {
perror("read error");
epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, &ev);
close(connfd);
continue;
} else if (n == 0) {
epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, &ev);
close(connfd);
continue;
}
write(STDOUT_FILENO, buf, n);
ev.events = EPOLLOUT | EPOLLET;
ev.data.fd = connfd;
if (epoll_ctl(epollfd, EPOLL_CTL_MOD, connfd, &ev) < 0) {
perror("epoll_ctl error");
return -1;
}
} else if (events[i].events & EPOLLOUT) {
connfd = events[i].data.fd;
sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: 14\r\n\r\nHello, World!\n");
int n = write(connfd, buf, strlen(buf));
if (n < 0) {
perror("write error");
epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, &ev);
close(connfd);
continue;
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = connfd;
if (epoll_ctl(epollfd, EPOLL_CTL_MOD, connfd, &ev) < 0) {
perror("epoll_ctl error");
return -1;
}
}
}
}
return 0;
}
UDP客户端和服务器程序示例
// UDP客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define MAX_EVENTS 1024
#define SERVER_ADDR "127.0.0.1"
#define SERVER_PORT 8888
int main() {
int sockfd, res, epollfd, nfds, i;
char buf[1024];
struct sockaddr_in servaddr;
struct epoll_event ev, events[MAX_EVENTS];
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket error");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
servaddr.sin_port = htons(SERVER_PORT);
epollfd = epoll_create(MAX_EVENTS);
if (epollfd < 0) {
perror("epoll_create error");
return -1;
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = sockfd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) < 0) {
perror("epoll_ctl error");
return -1;
}
while (1) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds < 0) {
perror("epoll_wait error");
continue;
}
for (i = 0; i < nfds; i++) {
if (events[i].data.fd == sockfd) {
fgets(buf, sizeof(buf), stdin);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
} else if (events[i].events & EPOLLIN) {
res = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
buf[res] = '\0';
printf("%s", buf);
}
}
}
return 0;
}
// UDP服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define BUF_LEN 1024
#define SERVER_PORT 8888
#define MAX_EVENTS 1024
int main() {
int sockfd, res, epollfd, nfds, i;
char buf[BUF_LEN];
struct sockaddr_in servaddr, cliaddr;
struct epoll_event ev, events[MAX_EVENTS];
socklen_t clilen = sizeof(cliaddr);
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket error");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERVER_PORT);
if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind error");
return -1;
}
epollfd = epoll_create(MAX_EVENTS);
if (epollfd < 0) {
perror("epoll_create error");
return -1;
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = sockfd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) < 0) {
perror("epoll_ctl error");
return -1;
}
while (1) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds < 0) {
perror("epoll_wait error");
continue;
}
for (i = 0; i < nfds; i++) {
if (events[i].events & EPOLLIN) {
res = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&cliaddr, &clilen);
buf[res] = '\0';
printf("Received from client %s:%d: %s", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port, buf);
sendto(sockfd, buf, res, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
}
}
}
return 0;
}
以上就是使用epoll实现IO多路复用的完整攻略以及两个示例的演示代码。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Linux IO多路复用之epoll网络编程 - Python技术站