Linux epoll和應用實例

1.高性能網路服務器編程用什麼技術?

Unix/Linux平臺:epoll
Windows平臺:IOCP

2.Linux的epoll函數
epoll用到三個函數:

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
注:#include <sys/epoll.h>

2.1 int epoll_create(int size)

epoll_create() creates an epoll instance.  Since Linux 2.6.8, the size argument is ignored, but must be greater than zero.

On success, these system calls return a nonnegative file descriptor.  On error, -1 is returned, and errno is set to indicate the error.

2.2 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

This system call performs control operations on the epoll instance referred to by the file descriptor epfd.  It requests that the operation op be performed for the target file descriptor(fd).

Valid values for the op argument are:
EPOLL_CTL_ADD
EPOLL_CTL_MOD
EPOLL_CTL_DEL

The event argument describes the object linked to the file descriptor fd.  The struct epoll_event is defined as:

typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
};

The events member is a bit mask composed using the following available event types:
EPOLLIN
EPOLLOUT
EPOLLRDHUP
EPOLLPRI
EPOLLERR
EPOLLHUP
EPOLLET
EPOLLONESHOT
EPOLLWAKEUP

2.3 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

The  epoll_wait()  system  call  waits for events on the epoll() instance referred to by the file descriptor epfd.  The memory area pointed to by events will contain the events that will be available for the caller.  Up to maxevents are returned by epoll_wait(). The maxevents argument must be greater than zero.

The data of each returned structure will contain the same data the user set with an epoll_ctl() (EPOLL_CTL_ADD, EPOLL_CTL_MOD) while the events member will contain the returned event bit field.

3.Linux的epoll實例

不知何故,《UNIX網絡編程》沒有介紹epoll。在網上找到一個epoll實例代碼。其最早期的出處是:

https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/epoll-example.c

注:目前已無法訪問。

下面是代碼:(TCP服務端)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>

#define MAXEVENTS 64

static int make_socket_non_blocking(int sfd)
{
	int flags, s;

	flags = fcntl(sfd, F_GETFL, 0);
	if (flags == -1)
	{
		perror("fcntl");
		return -1;
	}

	flags |= O_NONBLOCK;
	s = fcntl(sfd, F_SETFL, flags);
	if (s == -1)
	{
		perror("fcntl");
		return -1;
	}

	return 0;
}

static int create_and_bind(char *port)
{
	struct addrinfo hints;
	struct addrinfo *result, *rp;
	int s, sfd;

	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
	hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
	hints.ai_flags = AI_PASSIVE;     /* All interfaces */

	s = getaddrinfo(NULL, port, &hints, &result);
	if (s != 0)
	{
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
		return -1;
	}

	for (rp = result; rp != NULL; rp = rp->ai_next)
	{
		sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (sfd == -1)
			continue;

		s = bind(sfd, rp->ai_addr, rp->ai_addrlen);
		if (s == 0)
		{
			/* We managed to bind successfully! */
			break;
		}

		close(sfd);
	}

	if (rp == NULL)
	{
		fprintf(stderr, "Could not bind\n");
		return -1;
	}

	freeaddrinfo(result);

	return sfd;
}

int main(int argc, char *argv[])
{
	int sfd, s;
	int efd;
	struct epoll_event event;
	struct epoll_event *events;

	if (argc != 2)
	{
		fprintf(stderr, "Usage: %s [port]\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	sfd = create_and_bind(argv[1]);
	if (sfd == -1)
		abort();

	s = make_socket_non_blocking(sfd);
	if (s == -1)
		abort();

	s = listen(sfd, SOMAXCONN);
	if (s == -1)
	{
		perror("listen");
		abort();
	}

	efd = epoll_create1(0);
	if (efd == -1)
	{
		perror("epoll_create");
		abort();
	}

	event.data.fd = sfd;
	event.events = EPOLLIN | EPOLLET;
	s = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
	if (s == -1)
	{
		perror("epoll_ctl");
		abort();
	}

	/* Buffer where events are returned */
	events = calloc(MAXEVENTS, sizeof event);

	/* The event loop */
	while (1)
	{
		int n, i;

		n = epoll_wait(efd, events, MAXEVENTS, -1);
		for (i = 0; i < n; i++)
		{
			if ((events[i].events & EPOLLERR) || 
				(events[i].events & EPOLLHUP) || 
				(!(events[i].events & EPOLLIN)))
			{
				/* An error has occured on this fd, or the socket is not
				   ready for reading (why were we notified then?) */
				fprintf(stderr, "epoll error\n");
				close(events[i].data.fd);
				continue;
			}
			else if (sfd == events[i].data.fd)
			{
				/* We have a notification on the listening socket, which
				   means one or more incoming connections. */
				while (1)
				{
					struct sockaddr in_addr;
					socklen_t in_len;
					int infd;
					char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

					in_len = sizeof in_addr;
					infd = accept(sfd, &in_addr, &in_len);
					if (infd == -1)
					{
						if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
						{
							/* We have processed all incoming connections. */
							break;
						}
						else
						{
							perror("accept");
							break;
						}
					}

					s = getnameinfo(&in_addr, in_len,
						hbuf, sizeof hbuf,
						sbuf, sizeof sbuf,
						NI_NUMERICHOST | NI_NUMERICSERV);
					if (s == 0)
					{
						printf("Accepted connection on descriptor %d (host=%s, port=%s)\n", infd, hbuf, sbuf);
					}

					/* Make the incoming socket non-blocking and add it to the list of fds to monitor. */
					s = make_socket_non_blocking(infd);
					if (s == -1)
						abort();

					event.data.fd = infd;
					event.events = EPOLLIN | EPOLLET;
					s = epoll_ctl(efd, EPOLL_CTL_ADD, infd, &event);
					if (s == -1)
					{
						perror("epoll_ctl");
						abort();
					}
				}
				continue;
			}
			else
			{
				/* We have data on the fd waiting to be read. Read and
				   display it. We must read whatever data is available
				   completely, as we are running in edge-triggered mode
				   and won't get a notification again for the same data. */
				int done = 0;

				while (1)
				{
					ssize_t count;
					char buf[512];

					count = read(events[i].data.fd, buf, sizeof buf);
					if (count == -1)
					{
						/* If errno == EAGAIN, that means we have read all data. So go back to the main loop. */
						if (errno != EAGAIN)
						{
							perror("read");
							done = 1;
						}
						break;
					}
					else if (count == 0)
					{
						/* End of file. The remote has closed the connection. */
						done = 1;
						break;
					}

					/* Write the buffer to standard output */
					s = write(1, buf, count);
					if (s == -1)
					{
						perror("write");
						abort();
					}
				}

				if (done)
				{
					printf("Closed connection on descriptor %d\n", events[i].data.fd);

					/* Closing the descriptor will make epoll remove it
					   from the set of descriptors which are monitored. */
					close(events[i].data.fd);
				}
			}
		}
	}

	free(events);

	close(sfd);

	return EXIT_SUCCESS;
}

編譯:
gcc -o epoll epoll.c
運行:
./epoll 8080
備註:假設端口是8080。

如下是epoll使用的流程圖:

4.非常好的參考文章

(1)《【Linux學習】epoll詳解》(鏈接

(2)《IO多路複用漫談(附epoll示例)》(鏈接

(3)《Linux Epoll Module》(鏈接

(4)《Linux epoll詳解》(鏈接

(5)《TCP Echo Server using epoll, an example for Event Driven Server》(鏈接)文中的實例代碼非常接近於實際應用,推薦!

(6)《Blocking I/O, Nonblocking I/O, And Epoll》(鏈接

(7)《select / poll / epoll: practical difference for system architects》(鏈接

(8)《The Implementation of epoll (1)》(鏈接)詳細講述了epoll的實現代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章