From c6f05662899417ad22c4fa3217e6c2ba2d27b5a3 Mon Sep 17 00:00:00 2001 From: Florian Wilkemeyer Date: Sun, 10 Jan 2016 10:56:22 +0100 Subject: Added epoll as event-dispatcher on linux (instead of select()) Note: It will be enabled by default if configure detects that the system has support for epoll To enforce usage of select run configure with --enable-epoll=no --- conf/packet.conf | 13 ++++ configure | 62 ++++++++++++++++++- configure.ac | 39 ++++++++++++ src/common/socket.c | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 278 insertions(+), 6 deletions(-) diff --git a/conf/packet.conf b/conf/packet.conf index 3a7ec2c85..1780d1b4c 100644 --- a/conf/packet.conf +++ b/conf/packet.conf @@ -10,6 +10,19 @@ debug: no // How long can a socket stall before closing the connection (in seconds)? stall_time: 60 +// Linux/Epoll: Maxmimum Events per cycle +// Default Value: +// (Maxmimum Supported Connections)/2 +// NOTE: this controls the maximum collected socket-events per-cycle (call to epoll_wait()) +// for example settings this to 32 will allow up to 32 events (incomming data/new connections +// per server-cycle. +// NOTE: Recommended Settings is at least half the maxmimum supported connections +// Settings this to a lower value, may cause lags/delays +// Depending on available CPU Time +// NOTE: This Setting is only available on Linux when build using EPoll as event dispatcher! +// +//epoll_maxevents: 1024 + // Maximum allowed size for clients packets in bytes (default: 65535). // Default Values: // 24576 (Clients < 20131223) diff --git a/configure b/configure index 053dc0df2..eb928cad1 100755 --- a/configure +++ b/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac 1a78266. +# From configure.ac 7f3fd6d. # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.69. # @@ -692,6 +692,7 @@ enable_option_checking enable_manager enable_packetver enable_packetver_re +enable_epoll with_key1 with_key2 with_key3 @@ -1342,6 +1343,7 @@ Optional Features: --enable-packetver=ARG Sets the PACKETVER define. (see src/common/mmo.h) --enable-packetver-re Sets or unsets the PACKETVER_RE define - see src/common/mmo.h (currently disabled by default) + --enable-epoll use epoll(4) on Linux --enable-debug[=ARG] Compiles extra debug code. (yes by default) (available options: yes, no, gdb) --enable-buildbot[=ARG] (available options: yes, no) @@ -3546,6 +3548,52 @@ fi +# +# EPOLL (Linux Only) +# +# Check whether --enable-epoll was given. +if test "${enable_epoll+set}" = set; then : + enableval=$enable_epoll; enable_epoll=$enableval +else + enable_epoll=auto +fi + +if test x$enable_epoll = xno; then + have_linux_epoll=no +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Linux epoll(4)" >&5 +$as_echo_n "checking for Linux epoll(4)... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #ifndef __linux__ + #error This is not Linux + #endif + #include + +int +main () +{ +epoll_create1 (EPOLL_CLOEXEC); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + have_linux_epoll=yes +else + have_linux_epoll=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $have_linux_epoll" >&5 +$as_echo "$have_linux_epoll" >&6; } +fi +if test x$enable_epoll,$have_linux_epoll = xyes,no; then + as_fn_error $? "epoll support explicitly enabled but not available" "$LINENO" 5 +fi + + # # Obfuscation keys # @@ -8133,6 +8181,18 @@ case $enable_packetver_re in ;; esac +# +# Epoll +# +case $have_linux_epoll in + "yes") + CPPFLAGS="$CPPFLAGS -DSOCKET_EPOLL" + ;; + "no") + # default value + ;; +esac + # # Obfuscation keys # diff --git a/configure.ac b/configure.ac index 7b7590813..0e1c6b8bf 100644 --- a/configure.ac +++ b/configure.ac @@ -142,6 +142,33 @@ AC_ARG_ENABLE( ) +# +# Epoll +# +AC_ARG_ENABLE([epoll], + [AS_HELP_STRING([--enable-epoll],[use epoll(4) on Linux])], + [enable_epoll=$enableval], [enable_epoll=auto]) +if test x$enable_epoll = xno; then + have_linux_epoll=no +else + AC_MSG_CHECKING([for Linux epoll(4)]) + AC_LINK_IFELSE([AC_LANG_PROGRAM( + [ + #ifndef __linux__ + #error This is not Linux + #endif + #include + ], + [epoll_create1 (EPOLL_CLOEXEC);])], + [have_linux_epoll=yes], + [have_linux_epoll=no]) + AC_MSG_RESULT([$have_linux_epoll]) +fi +if test x$enable_epoll,$have_linux_epoll = xyes,no; then + AC_MSG_ERROR([epoll support explicitly enabled but not available]) +fi + + # # Obfuscation keys # @@ -1138,6 +1165,18 @@ case $enable_packetver_re in ;; esac +# +# Epoll +# +case $have_linux_epoll in + "yes") + CPPFLAGS="$CPPFLAGS -DSOCKET_EPOLL" + ;; + "no") + # default value + ;; +esac + # # Obfuscation keys # diff --git a/src/common/socket.c b/src/common/socket.c index 60759d0bb..b6e73bce5 100644 --- a/src/common/socket.c +++ b/src/common/socket.c @@ -38,6 +38,10 @@ #include #include +#ifdef SOCKET_EPOLL +#include +#endif + #ifdef WIN32 # include "common/winapi.h" #else @@ -251,8 +255,19 @@ char* sErr(int code) #define MSG_NOSIGNAL 0 #endif +#ifndef SOCKET_EPOLL +// Select based Event Dispatcher: fd_set readfds; +#else +// Epoll based Event Dispatcher: +static int epoll_maxevents = (FD_SETSIZE / 2); +static int epfd = SOCKET_ERROR; +static struct epoll_event epevent; +static struct epoll_event *epevents = NULL; + +#endif + // Maximum packet size in bytes, which the client is able to handle. // Larger packets cause a buffer overflow and stack corruption. #if PACKETVER >= 20131223 @@ -536,9 +551,25 @@ int connect_client(int listen_fd) { } #endif - if( sockt->fd_max <= fd ) sockt->fd_max = fd + 1; +#ifndef SOCKET_EPOLL + // Select Based Event Dispatcher sFD_SET(fd,&readfds); +#else + // Epoll based Event Dispatcher + epevent.data.fd = fd; + epevent.events = EPOLLIN; + + if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &epevent) == SOCKET_ERROR){ + ShowError("connect_client: New Socket #%d failed to add to epoll event dispatcher: %s\n", fd, error_msg()); + sClose(fd); + return -1; + } + +#endif + + if( sockt->fd_max <= fd ) sockt->fd_max = fd + 1; + create_session(fd, recv_to_fifo, send_from_fifo, default_func_parse); sockt->session[fd]->client_addr = ntohl(client_address.sin_addr.s_addr); @@ -586,8 +617,26 @@ int make_listen_bind(uint32 ip, uint16 port) exit(EXIT_FAILURE); } + +#ifndef SOCKET_EPOLL + // Select Based Event Dispatcher + sFD_SET(fd,&readfds); + +#else + // Epoll based Event Dispatcher + epevent.data.fd = fd; + epevent.events = EPOLLIN; + + if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &epevent) == SOCKET_ERROR){ + ShowError("make_listen_bind: failed to add listener socket #%d to epoll event dispatcher: %s\n", fd, error_msg()); + sClose(fd); + exit(EXIT_FAILURE); + } + +#endif + if(sockt->fd_max <= fd) sockt->fd_max = fd + 1; - sFD_SET(fd, &readfds); + create_session(fd, connect_client, null_send, null_parse); sockt->session[fd]->client_addr = 0; // just listens @@ -637,9 +686,26 @@ int make_connection(uint32 ip, uint16 port, struct hSockOpt *opt) { //Now the socket can be made non-blocking. [Skotlex] sockt->set_nonblocking(fd, 1); - if (sockt->fd_max <= fd) sockt->fd_max = fd + 1; + +#ifndef SOCKET_EPOLL + // Select Based Event Dispatcher sFD_SET(fd,&readfds); +#else + // Epoll based Event Dispatcher + epevent.data.fd = fd; + epevent.events = EPOLLIN; + + if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &epevent) == SOCKET_ERROR){ + ShowError("make_connection: failed to add socket #%d to epoll event dispatcher: %s\n", fd, error_msg()); + sClose(fd); + return -1; + } + +#endif + + if(sockt->fd_max <= fd) sockt->fd_max = fd + 1; + create_session(fd, recv_to_fifo, send_from_fifo, default_func_parse); sockt->session[fd]->client_addr = ntohl(remote_address.sin_addr.s_addr); @@ -813,8 +879,10 @@ int wfifoset(int fd, size_t len) int do_sockets(int next) { +#ifndef SOCKET_EPOLL fd_set rfd; struct timeval timeout; +#endif int ret,i; // PRESEND Timers are executed before do_sendrecv and can send packets and/or set sessions to eof. @@ -832,6 +900,9 @@ int do_sockets(int next) } #endif +#ifndef SOCKET_EPOLL + // Select based Event Dispatcher: + // can timeout until the next tick timeout.tv_sec = next/1000; timeout.tv_usec = next%1000*1000; @@ -848,6 +919,20 @@ int do_sockets(int next) } return 0; // interrupted by a signal, just loop and try again } +#else + // Epoll based Event Dispatcher + + ret = epoll_wait(epfd, epevents, epoll_maxevents, next); + if(ret == SOCKET_ERROR) + { + if( sErrno != S_EINTR ) + { + ShowFatalError("do_sockets: epoll_wait() failed, %s!\n", error_msg()); + exit(EXIT_FAILURE); + } + return 0; // interrupted by a signal, just loop and try again + } +#endif sockt->last_tick = time(NULL); @@ -859,6 +944,32 @@ int do_sockets(int next) if( sockt->session[fd] ) sockt->session[fd]->func_recv(fd); } +#elif defined(SOCKET_EPOLL) + // epoll based selection + + for( i = 0; i < ret; i++ ) + { + struct epoll_event *it = &epevents[i]; + struct socket_data *sock = sockt->session[ it->data.fd ]; + + if(!sock) + continue; + + if ((it->events & EPOLLERR) || + (it->events & EPOLLHUP) || + (!(it->events & EPOLLIN))) + { + // Got Error on this connection + sockt->eof( it->data.fd ); + + } else if (it->events & EPOLLIN) { + // data wainting + sock->func_recv( it->data.fd ); + + } + + } + #else // otherwise assume that the fd_set is a bit-array and enumerate it in a standard way for( i = 1; ret && i < sockt->fd_max; ++i ) @@ -1184,7 +1295,15 @@ int socket_config_read(const char* cfgName) sockt->stall_time = atoi(w2); if( sockt->stall_time < 3 ) sockt->stall_time = 3;/* a minimum is required to refrain it from killing itself */ + } +#ifdef SOCKET_EPOLL + else if(!strcmpi(w1, "epoll_maxevents")) { + epoll_maxevents = atoi(w2); + if(epoll_maxevents < 16){ + epoll_maxevents = 16; // minimum that seems to be useful + } } +#endif #ifndef MINICORE else if (!strcmpi(w1, "enable_ip_rules")) { ip_rules = config_switch(w2); @@ -1255,6 +1374,18 @@ void socket_final(void) VECTOR_CLEAR(sockt->lan_subnets); VECTOR_CLEAR(sockt->allowed_ips); VECTOR_CLEAR(sockt->trusted_ips); + +#ifdef SOCKET_EPOLL + if(epfd != SOCKET_ERROR){ + close(epfd); + epfd = SOCKET_ERROR; + } + if(epevents != NULL){ + aFree(epevents); + epevents = NULL; + } +#endif + } /// Closes a socket. @@ -1264,7 +1395,17 @@ void socket_close(int fd) return;// invalid sockt->flush(fd); // Try to send what's left (although it might not succeed since it's a nonblocking socket) + +#ifndef SOCKET_EPOLL + // Select based Event Dispatcher sFD_CLR(fd, &readfds);// this needs to be done before closing the socket +#else + // Epoll based Event Dispatcher + epevent.data.fd = fd; + epevent.events = EPOLLIN; + epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &epevent); // removing the socket from epoll when it's being closed is not required but recommended +#endif + sShutdown(fd, SHUT_RDWR); // Disallow further reads/writes sClose(fd); // We don't really care if these closing functions return an error, we are just shutting down and not reusing this socket. if (sockt->session[fd]) delete_session(fd); @@ -1406,15 +1547,34 @@ void socket_init(void) // Get initial local ips sockt->naddr_ = sockt->getips(sockt->addr_,16); + socket_config_read(SOCKET_CONF_FILENAME); + +#ifndef SOCKET_EPOLL + // Select based Event Dispatcher: sFD_ZERO(&readfds); + ShowInfo("Server uses '" CL_WHITE "select" CL_RESET "' as event dispatcher\n"); + +#else + // Epoll based Event Dispatcher: + epfd = epoll_create(FD_SETSIZE); // 2.6.8 or newer ignores the expected socket amount argument + if(epfd == SOCKET_ERROR){ + ShowError("Failed to Create Epoll Event Dispatcher: %s\n", error_msg()); + exit(EXIT_FAILURE); + } + + memset(&epevent, 0x00, sizeof(struct epoll_event)); + epevents = aCalloc(epoll_maxevents, sizeof(struct epoll_event)); + + ShowInfo("Server uses '" CL_WHITE "epoll" CL_RESET "' with up to " CL_WHITE "%d" CL_RESET " events per cycle as event dispatcher\n", epoll_maxevents); + +#endif + #if defined(SEND_SHORTLIST) memset(send_shortlist_set, 0, sizeof(send_shortlist_set)); #endif CREATE(sockt->session, struct socket_data *, FD_SETSIZE); - socket_config_read(SOCKET_CONF_FILENAME); - // initialize last send-receive tick sockt->last_tick = time(NULL); -- cgit v1.2.3-60-g2f50