本文实例为大家分享了epoll多路复用一个实例程序的具体代码,供大家参考,具体内容如下
1、实例程序描述编写一个echo server程序,功能是客户端向服务端发送消息,服务端接收到消息后输出,并原样返回给客户端,客户端接收到服务端的应答消息并打印输出。
2、公共接口函数部分2.1、common.h 源文件
/**
**描述:公共头文件
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
typedef struct epoll_event EPOLL_EVENT_T;
#define BUF_SIZE 2048
#define LISTENQ 10
#define FD_SEZE 1000
#define MAX_EVENTS 100
#define ERROR_SOCKET_SELECT -1
#define ERROR_SOCKET_TIMEOUT -2
#define ERROR_SOCKET_READ -3
#define ERROR_SOCKET_WRITE -4
#define ERROR_SOCKET_CLOSE -5
#define ERROR_SOCKET_CREATE -6
#define ERROR_SOCKET_BIND -7
#define ERROR_SOCKET_LISTEN -8
#define ERROR_SOCKET_CONNECT -9
#define ERROR_EPOLL_CREATE -10
#define ERROR_EPOLL_CTL_ADD -11
#define ERROR_EPOLL_CTL_DEL -12
#define ERROR_EPOLL_CTL_MOD -13
#define ERROR_ARGUMENT -999
int add_epoll_event(int epollfd, int fd, int events);
int del_epoll_event(int epoll, int fd, int events);
int mod_epoll_event(int epoll, int fd, int events);
int make_socket_nonblock(int sock_fd);
int listen_socket(char *ip, int port, int nonblock);
int connect_socket(char *ip, int port,int nonblock);
2.2、common.c 源文件
/**
**描述:公共函数
*/
#include "common.h"
int add_epoll_event(int epollfd, int fd, int events)
{
EPOLL_EVENT_T ev;
ev.events = events;
ev.data.fd = fd;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1){
perror("epoll_ctl_add");
return ERROR_EPOLL_CTL_ADD;
}
}
int del_epoll_event(int epollfd, int fd, int events)
{
EPOLL_EVENT_T ev;
ev.events = events;
ev.data.fd = fd;
if(epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev) == -1){
perror("epoll_ctl_del");
return ERROR_EPOLL_CTL_ADD;
}
}
int mod_epoll_event(int epollfd, int fd, int events)
{
EPOLL_EVENT_T ev;
ev.events = events;
ev.data.fd = fd;
if(epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev) == -1){
perror("epoll_ctl_mod");
return ERROR_EPOLL_CTL_MOD;
}
}
//设置socket为非阻塞模式函数
int make_socket_nonblock(int sock_fd)
{
int flags;
if((flags = fcntl(sock_fd, F_GETFL, NULL)) < 0){
printf("get socket fd flags error:%d %s", errno, strerror(errno));
return -1;
}
if(fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK) == -1){
printf("set socket non-block error:%d %s", errno, strerror(errno));
return -1;
}
return 0;
}
int listen_socket(char *ip, int port, int nonblock)
{
int opt=1, sockfd;
struct sockaddr_in svr_addr;
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
close(sockfd);
return ERROR_SOCKET_CREATE;
}
if(nonblock) //设置socket为非阻塞模式
make_socket_nonblock(sockfd);
//SO_REUSEADDR是让端口释放后立即就可以被再次使用
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
memset(&svr_addr, 0, sizeof(struct sockaddr_in));
svr_addr.sin_family = AF_INET;
if(ip == NULL)
svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
else
svr_addr.sin_addr.s_addr = inet_addr(ip);
svr_addr.sin_port = htons(port);
if(bind(sockfd, (struct sockaddr*)&svr_addr, sizeof(struct sockaddr)) == -1){
close(sockfd);
return ERROR_SOCKET_BIND;
}
if(listen(sockfd, LISTENQ) == -1){
close(sockfd);
return ERROR_SOCKET_LISTEN;
}
return sockfd;
}
int connect_socket(char *ip, int port,int nonblock)
{
int sockfd;
struct sockaddr_in svr_addr;
if(ip==NULL || strlen(ip)==0 || port<=0)
return ERROR_ARGUMENT;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0){
perror("socket");
return ERROR_SOCKET_CREATE;
}
memset(&svr_addr, 0, sizeof(svr_addr));
svr_addr.sin_family = AF_INET;
svr_addr.sin_addr.s_addr = inet_addr(ip);
svr_addr.sin_port = htons(port);
if(connect(sockfd, (struct sockaddr*)&svr_addr, sizeof(svr_addr)) == -1){
perror("connect");
close(sockfd);
return ERROR_SOCKET_CONNECT;
}
if(nonblock)
make_socket_nonblock(sockfd);
return sockfd;
}
3、服务端源文件 epoll_server.c
/**
**程序描述:编写一个echo server程序,功能是客户端向服务器发送信息,服务器端接收数据后输出并原样返回给客户端,客户端接收到消息并输出到终端。
*/
#include "common.h"
void do_epoll(int listen_fd);
void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int listen_fd, char *buf);
void on_accept(int epollfd, int listen_fd);
void on_read(int epollfd, int fd, char *buf);
void on_write(int epollfd, int fd, char *buf);
int main(int argc, int *argv[])
{
char svr_ip[32]={0};
int svr_port;
int listen_fd, nonblock=0;
if(argc < 3){
printf("ERROR: too few command-line arguments\n");
printf("Usage: %s <svr_ip> <svr_port>\n", argv[0]);
return -1;
}
strncpy(svr_ip, argv[1], strlen(argv[1]));
svr_port = atoi(argv[2]);
listen_fd = listen_socket(svr_ip, svr_port, nonblock);
if(listen_fd < 0){
printf("listen socket error:%d %s\n", errno, strerror(errno));
return -2;
}
printf("epoll_svr listen on[%s:%d] succ\n", svr_ip, svr_port);
do_epoll(listen_fd);
printf("exit epoll_svr succ\n");
return 0;
}
void do_epoll(int listen_fd)
{
EPOLL_EVENT_T events[MAX_EVENTS];
int epollfd, conn_fd, nfds;
char buf[BUF_SIZE]={0};
//创建一个epoll文件描述符
epollfd = epoll_create(FD_SEZE);
if(epollfd == -1){
perror("epoll_create");
close(listen_fd);
return;
}
//添加监听描述符的读事件
add_epoll_event(epollfd, listen_fd, EPOLLIN);
for(;;){
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if(nfds == -1){
perror("epoll_wait");
close(listen_fd);
break;
}
handle_epoll_events(epollfd, events, nfds, listen_fd, buf);
}
close(epollfd);
}
void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int sockfd, char *buf)
{
int i=0;
int fd;
//printf("nfds = %d\n", nfds);
for(i=0; i < nfds; i++){
fd = events[i].data.fd;
//根据文件描述符类型和事件类型进行相应处理
if(fd == sockfd && (events[i].events & EPOLLIN))
on_accept(epollfd, sockfd);
else if(events[i].events & EPOLLIN)
on_read(epollfd, fd, buf);
else if(events[i].events & EPOLLOUT)
on_write(epollfd, fd, buf);
}
}
void on_accept(int epollfd, int listen_fd)
{
int conn_fd;
struct sockaddr_in cli_addr;
socklen_t cli_addr_len=sizeof(struct sockaddr);
conn_fd = accept(listen_fd, (struct sockaddr*)&cli_addr, &cli_addr_len);
if(conn_fd == -1){
perror("accept");
}
else{
printf("accept a new client:[%s:%d], conn_fd=%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), conn_fd);
//make_socket_nonblock(conn_fd);
add_epoll_event(epollfd, conn_fd, EPOLLIN); //添加一个客户端连接描述符的读事件
}
}
void on_read(int epollfd, int fd, char *buf)
{
int nread;
nread = read(fd, buf, BUF_SIZE);
if(nread == -1){
perror("read");
close(fd);
del_epoll_event(epollfd, fd, EPOLLIN);
}
else if(nread == 0){
printf("ERROR:client close\n");
close(fd);
del_epoll_event(epollfd, fd, EPOLLIN);
}
else{
printf("recv req msg from client succ, fd=%d, msg:%s", fd, buf);
//修改描述符对应的事件,由读改为写
mod_epoll_event(epollfd, fd, EPOLLOUT);
}
}
void on_write(int epollfd, int fd, char *buf)
{
int nwrite;
nwrite=write(fd, buf, strlen(buf));
if(nwrite == -1){
perror("write");
close(fd);
del_epoll_event(epollfd, fd, EPOLLOUT);
}
else{
printf("send resp msg to client succ, fd=%d, msg:%s\n", fd, buf);
mod_epoll_event(epollfd, fd, EPOLLIN); //将描述符对应的事件,由写改为读
}
memset(buf, 0, sizeof(buf));
}
4、客户端源文件 epoll_client.c
/**
**程序描述:回射程序echo客户端。客户端也要使用epoll实现对路复用,控制STDIN_FILENO、STDOUT_FILENO和sockfd这三个描述符对应的事件。
STDIN_FILENO:标准输入描述符,只有一个读事件
STDOUT_FILENO:标准输出描述符,只有一个写事件
sockfd: 有两个事件,一个是读事件,即读取从服务端发送来的数据;另一个是写事件,即发送数据给服务端。需要注意的是,在同一时刻,它只能有一个事件
*/
#include "common.h"
void do_epoll(int sockfd);
void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int sockfd, char *buf);
void on_read(int epollfd, int fd, int sockfd, char *buf);
void on_write(int epollfd, int fd, int sockfd, char *buf);
int main(int argc, char *argv[])
{
char svr_ip[32]={0};
int svr_port;
int conn_fd, nonblock=0;
if(argc < 3){
printf("ERROR: too few command-line arguments\n");
printf("Usage: %s <svr_ip> <svr_port>\n", argv[0]);
return -1;
}
strncpy(svr_ip, argv[1], strlen(argv[1]));
svr_port = atoi(argv[2]);
conn_fd=connect_socket(svr_ip, svr_port, nonblock);
if(conn_fd < 0){
printf("connect to server failed!\n", conn_fd);
return -2;
}
printf("connect to server succ, sockfd=%d\n", conn_fd);
do_epoll(conn_fd);
printf("exit epoll_cli succ\n");
return 0;
}
void do_epoll(int sockfd)
{
int epollfd;
EPOLL_EVENT_T events[MAX_EVENTS];
int nfds;
char buf[BUF_SIZE]={0};
epollfd = epoll_create(FD_SEZE);
add_epoll_event(epollfd, STDIN_FILENO, EPOLLIN); //注册标准输入读事件
for(;;){
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if(nfds == -1){
perror("epoll_wait");
close(sockfd);
break;
}
handle_epoll_events(epollfd, events, nfds, sockfd, buf);
}
close(epollfd);
}
void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int sockfd, char *buf)
{
int i, fd;
//printf("nfds = %d\n", nfds);
for(i=0; i<nfds; i++){
fd = events[i].data.fd;
//根据文件描述符类型和事件类型进行相应处理
if(events[i].events & EPOLLIN)
on_read(epollfd, fd, sockfd, buf);
else if(events[i].events & EPOLLOUT)
on_write(epollfd, fd, sockfd, buf);
}
}
void on_read(int epollfd, int fd, int sockfd, char *buf)
{
int nread;
nread = read(fd, buf, BUF_SIZE);
if(nread < 0){
perror("read error");
close(fd);
}
else if(nread == 0){
printf("epoll_svr close.\n");
close(fd);
exit(-1);
}
else{
if(fd == STDIN_FILENO){ //标准输入描述符的读事件
printf("stdin buf=%s", buf);
add_epoll_event(epollfd, sockfd, EPOLLOUT); //注册sockfd的写事件
}
else{//sockfd描述符的读事件
del_epoll_event(epollfd, sockfd, EPOLLIN); //删除当前sockfd描述符的读事件
add_epoll_event(epollfd, STDOUT_FILENO, EPOLLOUT); //注册标准输出写事件
}
}
}
void on_write(int epollfd, int fd, int sockfd, char *buf)
{
int nwrite;
nwrite = write(fd, buf, strlen(buf));
if(nwrite < 0){
perror("write error");
close(fd);
}
else{
if(fd == STDOUT_FILENO) //标准输出描述符的写事件
{
printf("recv resp_msg from svr, msg=%s\n", buf);
del_epoll_event(epollfd, fd, EPOLLOUT); //删除输出描述符的写事件
}
else //sockfd描述符的写事件
{
printf("send req_msg to svr succ, msg=%s", buf);
mod_epoll_event(epollfd, fd, EPOLLIN); //修改sockfd描述符为读事件
}
}
memset(buf, 0, BUF_SIZE);
}
5、Makefile文件
#第1种方式
all: epoll_server epoll_client
epoll_server: epoll_server.o common.o
$(LINK)
epoll_client: epoll_client.o common.o
$(LINK)
%.o: %.c
$(COMPILE)
#compile & link
CFLAGS += -g
COMPILE=gcc -c -g -std=gnu99 -o $@ $<
LINK=gcc -g -o $@ $^
clean:
rm -rf *.o epoll_server epoll_client
6、总结分析
6.1 客户端程序分析
1、对于客户端程序而言,我们监听3个文件描述符,分别是连接服务端的sockfd,标准输入描述符STDIN_FILENO 以及 标准输出描述符STDOUT_FILENO。在 do_poll函数中,我们首先注册了标准输入描述符的读事件(EPOLLIN),然后在for循环中,循环调用epoll_wait系统调用,handle_epoll_events是整个epoll事件表的handler函数。当我们向终端输入数据完毕的时候,就会触发标准输入描述符的读事件,从而调用读事件处理函数on_read,在on_read函数中,注册了sockfd的写事件。
2、在do_poll的for循环中,继续调用epoll_wait 和 handle_epoll_events,发现sockfd的写事件已就绪,转向调用写事件处理函数on_write。在on_write函数中,发送消息给服务端,然后修改sockfd描述符的读事件就绪,准备接收服务端发来的应答消息。
3、在do_poll的for循环中,继续调用epoll_wait 和 handle_epoll_events,发现sockfd的读事件已就绪,转向调用写事件处理函数on_write。在on_read函数中,删除当前sockfd描述符的读事件并注册标准输出描述符的写事件。之所以要删除掉sockfd的读事件,是避免其一直处于就绪状态。
4、在do_poll的for循环中,继续调用epoll_wait 和 handle_epoll_events,发现标准输出描述符的写事件已就绪,转向调用写事件处理函数on_write。在on_write函数中,输出服务端发来的应答消息并删除标准输出描述符的写事件。之所以要删除掉标准输出描述符的写事件,还是为了避免其一直处于就绪状态,具体表现就是不停地在终端打印信息。
6.2 服务端程序分析
1、对于服务端程序而言,我们监听2个文件描述符,分别是接受多个客户端连接请求的listen_fd和处理单个客户端的数据读写的conn_fd。
2、在main函数中,首先创建了监听连接请求的listen_fd文件描述符。然后在do_poll函数中,首先注册了listen_fd描述符的读事件(EPOLLIN)。在for循环中,循环调用epoll_wait系统调用和epoll事件handler函数handle_epoll_events。
3、当有客户端发起连接请求时,会触发listen_fd描述符的读事件,转向执行listen_fd的读事件处理函数on_accept。在on_accept函数中,客户端与服务端成功建立连接,并返回一个conn_fd文件描述符,然后注册这个描述符的读事件。
4、在do_poll的for循环中,继续调用epoll_wait 和 handle_epoll_events,当客户端有数据发送给服务端时,触发conn_fd的读事件,转向执行on_read函数。在on_read函数中,接收客户端发来的消息并在终端输出,然后修改conn_fd描述符为写事件就绪。如果接收到的数据大小为0,则说明连接已经断开,则关闭conn_fd描述符并删除其读事件。
5、在do_poll的for循环中,继续调用epoll_wait 和 handle_epoll_events,发现conn_fd的写事件就绪,转向执行on_write函数。在on_write函数中,发送应答消息给客户端,并修改conn_fd描述符的读事件就绪。
题外话本epoll实例程序,本人已经在CentOS 7.6系统下测试通过了。