epoll简介和一个TCP转发的示例

2017-8-27 Plan C Linux

blog.kurukurumi.com原创,转载请注明出处。

E-mail:hubenchang0515@outlook.com


        epoll是Linux上的一种I/O多路转接的实现,传统的I/O多路转接select和poll采用的是轮询机制,需要等待的文件描述符数量越多,性能就会越低。而epoll的性能与它等待的文件描述符数量无关,在文件描述符数量较多的情况下也能保持较高的性能。


        epoll只需要3个函数就可以使用:


#include <sys/epoll.h>

int epoll_create(int size);
// 创建一个epoll,成功返回epoll自身的文件描述符,失败返回-1

        epoll_create用来创建epoll,参数是epoll监听的文件描述符的个数上限,epoll本身也是一个文件描述符。


#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 设置epoll,成功返回0,失败返回-1

        epoll_ctl用来设置epoll,它的第一个参数是epoll_create返回的epoll文件描述符。

        第二个参数是操作类型,可以取如下值:

EPOLL_CTL_ADD添加一个事件
EPOLL_CTL_MOD修改一个事件
EPOLL_CTL_DEL删除一个事件

        第三个参数是要监听的文件描述符。

        第四个参数是表示事件的结构体的指针,需要在这个结构体里设置监听的文件描述符和事件:

struct epoll_event event;
event.events = EPOLLIN;       // 设置监听的事件为 有数据可读取
event.data.fd = STDIN_FILENO; // 设置监听的文件描述符为 标准输入

        事件的类型可取如下值及其按位或:

EPOLLIN监听的文件描述符有数据可读
EPOLLOUT监听的文件描述符有空间可写
EPOLLRDHUP监听的文件描述符被挂断
EPOLLPRI监听的文件描述符有紧急数据可读(带外数据)
EPOLLERR监听的文件描述符发生错误
EPOLLHUP监听的文件描述符被挂断
EPOLLET采取边沿触发
EPOLLONESHOT监听一次事件,需要通过EPOLL_MOD再次设置事件

  

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
// 成功返回事件个数,失败返回-1

        epoll_wait阻塞等待事件发生,第一个参数是epoll的文件描述符;第二个参数是指向epoll_event结构体的指针,用来返回事件;第三参数是epoll_event结构体的个数,也即最多返回多少个事件;最后一个参数是超时时间,单位是毫秒,为-1时永久阻塞。


        示例代码:

/* Author : Plan C
 * Blog   : http://blog.kurukurumi.com
 * E-mail : hubenchang0515@outlook.com
 * GitHub : https://github.com/hubenchang0515
 */

#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <lualib.h>
#include <lauxlib.h>

/* Length of char[] stored ip address */
#ifndef IP_LENGTH
#	define IP_LENGTH 32
#endif

/* IPv4 address and port to listen */
	char  IPv4_LISTEN [IP_LENGTH];
uint16_t  PORT_LISTEN;

/* IPv4 address and port to send to */
	char  IPv4_TARGET [IP_LENGTH];
uint16_t  PORT_TARGET;

/* MAX of socket */
#ifndef SOCKET_MAX
#	define SOCKET_MAX 1024
#endif
/* sockets to server , index by client socket */
/* sockets to client , index bu server socket */
static int sockets[SOCKET_MAX];


void config();
int init();
int mainloop(int listen);

int main()
{
	config();
	int listen = init();
	if(listen == -1)
	{
		printf("%s\n",strerror(errno));
		return 1;	
	}
	else
	{
		mainloop(listen);
		return 0;
	}
}


/* read config from .lua */
void config()
{
	lua_State* L = luaL_newstate();
	if(luaL_dofile(L,"config.lua") != 0)
	{
		lua_error(L); // this invoke will abort
	}
	
	lua_getglobal(L,"IPv4_LISTEN");
	strncpy(IPv4_LISTEN,luaL_checkstring(L,-1),IP_LENGTH);
	
	lua_getglobal(L,"PORT_LISTEN");
	PORT_LISTEN = luaL_checkinteger(L,-1);
	
	lua_getglobal(L,"IPv4_TARGET");
	strncpy(IPv4_TARGET,luaL_checkstring(L,-1),IP_LENGTH);
	
	lua_getglobal(L,"PORT_TARGET");
	PORT_TARGET = luaL_checkinteger(L,-1);
	
	printf("Listen  %s:%u\n", IPv4_LISTEN, PORT_LISTEN);
	printf("Server  %s:%u\n", IPv4_TARGET, PORT_TARGET);
	
	lua_close(L);
}


/* bind and listen */
int init()
{
	/* create socket to listen */
	int fd = socket(AF_INET,SOCK_STREAM,0);
	if(fd == -1)
	{
		return -1;
	}
	
	/* bind port and listen */
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(IPv4_LISTEN);
	addr.sin_port = htons(PORT_LISTEN);
	if(bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1)
	{
		return -1;
	}
	if(listen(fd,10) == -1)
	{
		return -1;
	}
	
	return fd;
}


/* main loop */
int mainloop(int listen)
{
	/* init epoll */
	int epollfd = epoll_create(20);
	struct epoll_event event;
	event.events = EPOLLIN;
	event.data.fd = listen;
	epoll_ctl(epollfd, EPOLL_CTL_ADD, listen, &event);
	
	/* loop */
	while(epoll_wait(epollfd, &event, 1,-1) > 0)
	{
		int fd = event.data.fd;
		printf("%d is ready\n", fd);
		/* the new connect from client */
		if(fd == listen)
		{
			struct sockaddr_in addr;
			socklen_t len = sizeof(addr);
			int client = accept(listen, (struct sockaddr*)&addr, &len);
			/* connect to server */
			int server = socket(AF_INET,SOCK_STREAM,0);
			if(server == -1)
			{
				return -1;
			}
			
			addr.sin_family = AF_INET;
			addr.sin_addr.s_addr = inet_addr(IPv4_TARGET);
			addr.sin_port = htons(PORT_TARGET);
			if(connect(server, (struct sockaddr*)&addr, sizeof(addr)) == -1)
			{
				return -1;
			}
			
			/* store socket of client and server */
			sockets[client] = server;
			sockets[server] = client;
			
			/* add to epoll */
			event.events = EPOLLIN;
			event.data.fd = client;
			epoll_ctl(epollfd, EPOLL_CTL_ADD, client, &event);
			event.events = EPOLLIN;
			event.data.fd = server;
			epoll_ctl(epollfd, EPOLL_CTL_ADD, server, &event);	
		}
		else // from client or server
		{
			char buffer[1024];
			ssize_t len;
			do
			{
				len = read(fd, buffer, 1024);
				write(STDOUT_FILENO, buffer, len);
				write(sockets[fd], buffer, len);
			}while(len == 1024);
			
			if(len == 0) // socket is closed by other
			{
				event.events = EPOLLIN;
				event.data.fd = fd;
				epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &event);
				
				event.events = EPOLLIN;
				event.data.fd = sockets[fd];
				epoll_ctl(epollfd, EPOLL_CTL_DEL, sockets[fd], &event);
				
				close(fd);
				close(sockets[fd]);
			}
		}
	}
	
	return 0;
}

        编译:

gcc main.c -llua -lm

        这段示例代码进行TCP的转发,需要读取config.lua作为配置,假如config.lua如下,则浏览器采用http访问localhost时会显示本人学校教务处的网站。

-- 监听的ip地址和端口号
IPv4_LISTEN = '0.0.0.0'
PORT_LISTEN = 80

-- 转发的ip地址和断口号
IPv4_TARGET = '202.114.242.231'
PORT_TARGET = 8036

发表评论:

Powered by emlog
鄂ICP备16003833号