异步I、O之epoll
有时为了处理大量的文件句柄,需要一种机制,在单线程中也可以处理多个socket请求,轮询的机制可以实现但是对系统的负载较高,epoll是linux系统用来处理类似socket这样的文件句柄的读写等操作的一种IO复用机制。服务端维护一个socket列表,使用epoll调用来取出准备好读或写的socket,随后可以对socket句柄进行操作
用到的系统调用epoll_create/epoll_ctl/epoll_wait
用到的数据结构epoll_event/epoll_data_t
查阅manual了解函数用法
一个简单的例子
common.h
//include the header file socket,io,epoll,list,string,unistd,stdlib
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//namespace
using namespace std;
//client list, C++ stl list
list client_list;
//macro statement server_ip,server_port,epollsize,buf_size,Welcome string,server_string
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
#define EPOLL_SIZE 500
#define BUF_SIZE 2048
#define WELCOME_STRING "Welcome to join the chat room, your chat ID is: Client# %d"
#define SERVER_STRING "Client id #%d say >> %s"
#define CAUTION "only one client in the chat room"
//set fd in nonblocking io mode, using fnctl
void setNonBlocking(int fd){
int res = fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | O_NONBLOCK);
if(res != 0){
perror("set fd nonblock failed");
return;
}
}
//add fd to the epoll,with epoll_ctl
void addFdToEpoll(int epollfd, int sockfd, bool enable_et){
struct epoll_event event;
event.events = EPOLLIN;
if(enable_et){
event.events = EPOLLIN | EPOLLET;
}
event.data.fd = sockfd;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &event) != 0){
perror("add fd to epoll failed");
};
setNonBlocking(sockfd);
}
//send broadcast message to the client in clients list, except for which we received the data
void sendBroadcastMessage(int sockfd){
char buf[BUF_SIZE], message[BUF_SIZE];
//initialize the space
bzero(buf, BUF_SIZE);
bzero(message, BUF_SIZE);
int rlen = recv(sockfd, buf, BUF_SIZE, 0);
printf("read data from Client #%d: %s\n", sockfd, buf);
if(rlen == 0){
close(sockfd);
client_list.remove(sockfd);
printf("client #%ld closed, Now, there are %d clients in the chatroom\n",sockfd, client_list.size());
}else{
if(client_list.size() == 1){
int slen = send(sockfd, CAUTION, strlen(CAUTION), 0);
return;
}
sprintf(message, SERVER_STRING, sockfd, buf);
printf("will send:%s\n", message);
list::iterator iter;
for( iter = client_list.begin(); iter != client_list.end(); iter++ ){
puts("sending data...");
if(*iter != sockfd){
send(*iter, message, BUF_SIZE, 0);
}
}
}
}
server.c
#include "common.h"
int main(){
//socket address statement
struct sockaddr_in server_addr;
//initialize the address
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_family = AF_INET;
//create server socket
int listener = socket(AF_INET, SOCK_STREAM, 0);
//bind the server address
if(bind(listener, (struct sockaddr*)&server_addr, sizeof(server_addr)) != 0){
perror("bind failed");
return 1;
}
//listen on the address
if(listen(listener, 5) != 0){
perror("listen failed");
return 2;
}
//create epoll
int epollfd = epoll_create(EPOLL_SIZE);
if(epollfd < 0){
perror("create epollfd failed");
return 3;
}
//add server fd to epoll
addFdToEpoll(epollfd, listener, true);
//epoll_event array
static struct epoll_event events[EPOLL_SIZE];
//main loop
int clientfd;
while(1){
//epoll_wait
puts("waiting clients...");
int event_count = epoll_wait(epollfd,events, EPOLL_SIZE, -1);
printf("epoll event count is %d\n", event_count);
for(int i = 0; i< event_count; i++){
clientfd = events[i].data.fd;
//if fd is serverfd, accept a connection, print information, add new sockfd to epoll
if(clientfd == listener){
struct sockaddr_in client_addr;
socklen_t addrlen = sizeof(struct sockaddr_in);
int newfd = accept(listener, (struct sockaddr*)&client_addr, &addrlen);
if(newfd < 0){
perror("accept new connections failed");
exit(EXIT_FAILURE);
}
printf(
"new connection from %s:%d, client id #%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
newfd
);
addFdToEpoll(epollfd, newfd, true);
client_list.push_back(newfd);
printf("welcome message\n");
char message[BUF_SIZE];
bzero(message, BUF_SIZE);
sprintf(message, WELCOME_STRING, newfd);
send(newfd, message, strlen(message), 0);
}//if not, sendbroadcast message
else{
sendBroadcastMessage(clientfd);
}
}
}
}
client.c
#include"common.h"
int main(){
//server address statement
struct sockaddr_in server_addr;
//initilize server address
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
//create sock
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
perror("create socket failed");
return 1;
}
if(connect(sock, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) != 0){
perror("connect server failed");
return 0;
}
int pipefd[2];
if(pipe(pipefd) != 0){
perror("create pipe failed");
return 2;
}
int epollfd = epoll_create(EPOLL_SIZE);
if(epollfd< 0){
perror("create epollfd failed");
return 3;
}
addFdToEpoll(epollfd, sock, true);
addFdToEpoll(epollfd, pipefd[0], true);
pid_t pid = fork();
if(pid < 0){
perror("create child process failed");
return 4;
}
int client_running = 1;
char message[BUF_SIZE];
char buf[BUF_SIZE];
struct epoll_event events[2];
if(pid == 0){
close(pipefd[0]);
printf("please input your message, and type exit to terminate\n");
while(client_running){
//printf("input>");
bzero(message, BUF_SIZE);
//bzero(buf, BUF_SIZE);
fgets(message, BUF_SIZE, stdin);
if(strncmp(message, "exit", strlen("exit")) == 0){
client_running = 0;
close(sock);
close(pipefd[1]);
return 0;
}else{
write(pipefd[1], message, strlen(message));
}
}
}else{
close(pipefd[1]);
while(client_running){
int event_count = epoll_wait(epollfd, events, 2, -1);
for(int i = 0; i < event_count; i++){
if(events[i].data.fd == pipefd[0]){
bzero(buf, BUF_SIZE);
if(read(pipefd[0], buf, BUF_SIZE) == 0){
client_running = 0;
}else{
printf("\nyour input:%s\n", buf);
send(sock, buf, strlen(buf) - 1, 0);
}
}else{
recv(sock, buf, BUF_SIZE, 0);
printf("\nServer message:%s\n", buf);
}
}
}
}
close(sock);
if(pid){
close(pipefd[0]);
}else{
close(pipefd[1]);
}
return 0;
}