libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit 1acde3cb8eab299a0994f243616de4e8ee028c0a
parent 558d8ae997bba5128830dddf6d35bb97f3fedf6f
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
Date:   Tue, 18 Feb 2014 18:38:41 +0000

add ability to use socketspair instead of pipe, implement W32 pipe/socketspair emulation, implement MHD_SYS_select_
configure.ac: add "--disable-pipes" option

Diffstat:
Mconfigure.ac | 33+++++++++++++++++++++++++++++++++
Msrc/include/microhttpd.h | 3++-
Msrc/include/platform.h | 12++++++++++++
Msrc/microhttpd/daemon.c | 81++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/microhttpd/internal.h | 5+++--
Msrc/platform/platform_interface.h | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/platform/w32functions.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/platform/w32functions.h | 8++++++++
8 files changed, 237 insertions(+), 49 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -167,6 +167,7 @@ netbsd*) AM_CONDITIONAL(HAVE_GNU_LD, false) AM_CONDITIONAL(HAVE_W32, false) LDFLAGS="$LDFLAGS" + os_is_windows=yes ;; *mingw*) AC_DEFINE_UNQUOTED(MINGW,1,[This is a MinGW system]) @@ -180,6 +181,7 @@ netbsd*) AC_SUBST(PLIBC_LIBS) AC_SUBST(PLIBC_LDFLAGS) AC_SUBST(PLIBC_CPPFLAGS) + os_is_windows=yes ;; *openedition*) AC_DEFINE_UNQUOTED(OS390,1,[This is a OS/390 system]) @@ -229,6 +231,37 @@ AC_CHECK_HEADERS([sys/types.h sys/time.h sys/msg.h netdb.h netinet/in.h netinet/ AC_CHECK_HEADERS([search.h], AM_CONDITIONAL(HAVE_TSEARCH, true), AM_CONDITIONAL(HAVE_TSEARCH, false)) +# Check for pipe/socketpair signaling +AC_MSG_CHECKING([[whether to disable pipes signaling]]) +AC_ARG_ENABLE([[pipes]], + [AS_HELP_STRING([[--disable-pipes]], [[disable internal singalling by pipes and use socket pair instead]])], + [], [[enable_pipes=yes]]) +AS_IF([[test "x$os_is_windows" = "xyes"]], [disable_pipes=yes + AC_MSG_RESULT([[yes, forced on W32]])], + [[test "x$enable_pipes" != "xno"]], [disable_pipes=no + AC_MSG_RESULT([[$disable_pipes]])], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #ifdef HAVE_SYS_TYPES_H + #include <sys/types.h> + #endif + #ifdef HAVE_SYS_SOCKET_H + #include <sys/socket.h> + #endif + ]],[[ + int sv[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) return 1 + + ]])], + [disable_pipes=yes + AC_MSG_RESULT([[yes, socketpair in available]])], + [disable_pipes=no + AC_MSG_RESULT([[no, socketpair in not available]])] + ) + ]) +if test "x$disable_pipes" = "xyes"; then + AC_DEFINE([[MHD_DONT_USE_PIPES]], [[1]], [Define to use pair of sockets instead of pipes for signaling]) +fi + # Check for plibc.h from system, if not found, use our own AC_CHECK_HEADERS([plibc.h],our_private_plibc_h=0,our_private_plibc_h=1) AM_CONDITIONAL(USE_PRIVATE_PLIBC_H, test x$our_private_plibc_h = x1) diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -520,7 +520,8 @@ enum MHD_FLAG * use of this option is automatic (as in, you do not even have to * specify it), if #MHD_USE_NO_LISTEN_SOCKET is specified. In * "external" `select()` mode, this option is always simply ignored. - * On W32 a pair of sockets is used instead of a pipe. + * MHD can be build for use a pair of sockets instead of a pipe. + * Pair of sockets is forced on W32. * * You must also use this option if you use internal select mode * or a thread pool in conjunction with #MHD_add_connection. diff --git a/src/include/platform.h b/src/include/platform.h @@ -135,4 +135,16 @@ typedef SOCKET MHD_socket; #define MHD_SOCKET_DEFINED 1 #endif /* MHD_SOCKET_DEFINED */ +/* Force don't use pipes on W32 */ +#if defined(_WIN32) && !defined(MHD_DONT_USE_PIPES) +#define MHD_DONT_USE_PIPES 1 +#endif /* defined(_WIN32) && !defined(MHD_DONT_USE_PIPES) */ + +/* MHD_pipe is type for pipe FDs*/ +#ifndef MHD_DONT_USE_PIPES +typedef int MHD_pipe; +#else /* ! MHD_DONT_USE_PIPES */ +typedef MHD_socket MHD_pipe; +#endif /* ! MHD_DONT_USE_PIPES */ + #endif diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c @@ -728,7 +728,7 @@ MHD_handle_connection (void *data) /* how did we get here!? */ goto exit; } - num_ready = SELECT (max + 1, &rs, &ws, NULL, tvp); + num_ready = MHD_SYS_select_ (max + 1, &rs, &ws, NULL, tvp); if (num_ready < 0) { if (EINTR == MHD_socket_errno_) @@ -1306,8 +1306,8 @@ internal_add_connection (struct MHD_Daemon *daemon, } else if ( (MHD_YES == external_add) && - (-1 != daemon->wpipe[1]) && - (1 != WRITE (daemon->wpipe[1], "n", 1)) ) + (MHD_INVALID_PIPE_ != daemon->wpipe[1]) && + (1 != MHD_pipe_write_ (daemon->wpipe[1], "n", 1)) ) { #if HAVE_MESSAGES MHD_DLOG (daemon, @@ -1477,8 +1477,8 @@ MHD_resume_connection (struct MHD_Connection *connection) MHD_PANIC ("Failed to acquire cleanup mutex\n"); connection->resuming = MHD_YES; daemon->resuming = MHD_YES; - if ( (-1 != daemon->wpipe[1]) && - (1 != WRITE (daemon->wpipe[1], "r", 1)) ) + if ( (MHD_INVALID_PIPE_ != daemon->wpipe[1]) && + (1 != MHD_pipe_write_ (daemon->wpipe[1], "r", 1)) ) { #if HAVE_MESSAGES MHD_DLOG (daemon, @@ -1963,9 +1963,9 @@ MHD_run_from_select (struct MHD_Daemon *daemon, (FD_ISSET (ds, read_fd_set)) ) (void) MHD_accept_connection (daemon); /* drain signaling pipe to avoid spinning select */ - if ( (-1 != daemon->wpipe[0]) && + if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) && (FD_ISSET (daemon->wpipe[0], read_fd_set)) ) - (void) read (daemon->wpipe[0], &tmp, sizeof (tmp)); + (void) MHD_pipe_read_ (daemon->wpipe[0], &tmp, sizeof (tmp)); if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) { @@ -2064,7 +2064,7 @@ MHD_select (struct MHD_Daemon *daemon, FD_SET (daemon->socket_fd, &rs); } } - if (-1 != daemon->wpipe[0]) + if (MHD_INVALID_PIPE_ != daemon->wpipe[0]) { FD_SET (daemon->wpipe[0], &rs); /* update max file descriptor */ @@ -2089,7 +2089,7 @@ MHD_select (struct MHD_Daemon *daemon, } if (MHD_INVALID_SOCKET == max) return MHD_YES; - num_ready = SELECT (max + 1, &rs, &ws, &es, tv); + num_ready = MHD_SYS_select_ (max + 1, &rs, &ws, &es, tv); if (MHD_YES == daemon->shutdown) return MHD_NO; if (num_ready < 0) @@ -2150,7 +2150,7 @@ MHD_poll_all (struct MHD_Daemon *daemon, poll_listen = (int) poll_server; poll_server++; } - if (-1 != daemon->wpipe[0]) + if (MHD_INVALID_PIPE_ != daemon->wpipe[0]) { p[poll_server].fd = daemon->wpipe[0]; p[poll_server].events = POLLIN; @@ -2284,7 +2284,7 @@ MHD_poll_listen_socket (struct MHD_Daemon *daemon, poll_listen = poll_count; poll_count++; } - if (-1 != daemon->wpipe[0]) + if (MHD_INVALID_PIPE_ != daemon->wpipe[0]) { p[poll_count].fd = daemon->wpipe[0]; p[poll_count].events = POLLIN; @@ -2456,10 +2456,10 @@ MHD_epoll (struct MHD_Daemon *daemon, { if (NULL == events[i].data.ptr) continue; /* shutdown signal! */ - if ( (-1 != daemon->wpipe[0]) && + if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) && (daemon->wpipe[0] == events[i].data.fd) ) { - (void) read (daemon->wpipe[0], &tmp, sizeof (tmp)); + (void) MHD_pipe_read_ (daemon->wpipe[0], &tmp, sizeof (tmp)); continue; } if (daemon != events[i].data.ptr) @@ -2692,7 +2692,7 @@ MHD_quiesce_daemon (struct MHD_Daemon *daemon) ret = daemon->socket_fd; if (MHD_INVALID_SOCKET == ret) return MHD_INVALID_SOCKET; - if ( (-1 == daemon->wpipe[1]) && + if ( (MHD_INVALID_PIPE_ == daemon->wpipe[1]) && (0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)) ) { #if HAVE_MESSAGES @@ -3140,7 +3140,7 @@ setup_epoll_to_listen (struct MHD_Daemon *daemon) #endif return MHD_NO; } - if ( (-1 != daemon->wpipe[0]) && + if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) && (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) ) { event.events = EPOLLIN | EPOLLET; @@ -3250,8 +3250,8 @@ MHD_start_daemon_va (unsigned int flags, daemon->pool_increment = MHD_BUF_INC_SIZE; daemon->unescape_callback = &MHD_http_unescape; daemon->connection_timeout = 0; /* no timeout */ - daemon->wpipe[0] = -1; - daemon->wpipe[1] = -1; + daemon->wpipe[0] = MHD_INVALID_PIPE_; + daemon->wpipe[1] = MHD_INVALID_PIPE_; #if HAVE_MESSAGES daemon->custom_error_log = (MHD_LogCallback) &vfprintf; daemon->custom_error_log_cls = stderr; @@ -3263,13 +3263,7 @@ MHD_start_daemon_va (unsigned int flags, #endif if (0 == (flags & (MHD_USE_SELECT_INTERNALLY | MHD_USE_THREAD_PER_CONNECTION))) use_pipe = 0; /* useless if we are using 'external' select */ - if ( (use_pipe) && -#ifdef WINDOWS - (0 != SOCKETPAIR (AF_INET, SOCK_STREAM, IPPROTO_TCP, daemon->wpipe)) -#else - (0 != PIPE (daemon->wpipe)) -#endif - ) + if ( (use_pipe) && (0 != MHD_pipe_ (daemon->wpipe)) ) { #if HAVE_MESSAGES MHD_DLOG (daemon, @@ -3288,9 +3282,9 @@ MHD_start_daemon_va (unsigned int flags, MHD_DLOG (daemon, "file descriptor for control pipe exceeds maximum value\n"); #endif - if (0 != CLOSE (daemon->wpipe[0])) + if (0 != MHD_pipe_close_ (daemon->wpipe[0])) MHD_PANIC ("close failed\n"); - if (0 != CLOSE (daemon->wpipe[1])) + if (0 != MHD_pipe_close_ (daemon->wpipe[1])) MHD_PANIC ("close failed\n"); free (daemon); return NULL; @@ -3705,17 +3699,12 @@ MHD_start_daemon_va (unsigned int flags, d->worker_pool = NULL; if ( (MHD_USE_SUSPEND_RESUME == (flags & MHD_USE_SUSPEND_RESUME)) && -#ifdef WINDOWS - (0 != SOCKETPAIR (AF_INET, SOCK_STREAM, IPPROTO_TCP, d->wpipe)) -#else - (0 != PIPE (d->wpipe)) -#endif - ) + (0 != MHD_pipe_ (d->wpipe)) ) { #if HAVE_MESSAGES MHD_DLOG (daemon, "Failed to create worker control pipe: %s\n", - MHD_strerror_ (errno)); + MHD_pipe_last_strerror_() ); #endif goto thread_failed; } @@ -3728,9 +3717,9 @@ MHD_start_daemon_va (unsigned int flags, MHD_DLOG (daemon, "file descriptor for worker control pipe exceeds maximum value\n"); #endif - if (0 != CLOSE (d->wpipe[0])) + if (0 != MHD_pipe_close_ (d->wpipe[0])) MHD_PANIC ("close failed\n"); - if (0 != CLOSE (d->wpipe[1])) + if (0 != MHD_pipe_close_ (d->wpipe[1])) MHD_PANIC ("close failed\n"); goto thread_failed; } @@ -3906,7 +3895,7 @@ epoll_shutdown (struct MHD_Daemon *daemon) { struct epoll_event event; - if (-1 == daemon->wpipe[1]) + if (MHD_INVALID_PIPE_ == daemon->wpipe[1]) { /* wpipe was required in this mode, how could this happen? */ MHD_PANIC ("Internal error\n"); @@ -3957,9 +3946,9 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) #endif } } - if (-1 != daemon->wpipe[1]) + if (MHD_INVALID_PIPE_ != daemon->wpipe[1]) { - if (1 != WRITE (daemon->wpipe[1], "e", 1)) + if (1 != MHD_pipe_write_ (daemon->wpipe[1], "e", 1)) MHD_PANIC ("failed to signal shutdown via pipe"); } #ifdef HAVE_LISTEN_SHUTDOWN @@ -3990,9 +3979,9 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) /* MHD_USE_NO_LISTEN_SOCKET disables thread pools, hence we need to check */ for (i = 0; i < daemon->worker_pool_size; ++i) { - if (-1 != daemon->worker_pool[i].wpipe[1]) + if (MHD_INVALID_PIPE_ != daemon->worker_pool[i].wpipe[1]) { - if (1 != WRITE (daemon->worker_pool[i].wpipe[1], "e", 1)) + if (1 != MHD_pipe_write_ (daemon->worker_pool[i].wpipe[1], "e", 1)) MHD_PANIC ("failed to signal shutdown via pipe"); } if (0 != (rc = pthread_join (daemon->worker_pool[i].pid, &unused))) @@ -4006,11 +3995,11 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) #endif if ( (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) ) { - if (-1 != daemon->worker_pool[i].wpipe[1]) + if (MHD_INVALID_PIPE_ != daemon->worker_pool[i].wpipe[1]) { - if (0 != CLOSE (daemon->worker_pool[i].wpipe[0])) + if (0 != MHD_pipe_close_ (daemon->worker_pool[i].wpipe[0])) MHD_PANIC ("close failed\n"); - if (0 != CLOSE (daemon->worker_pool[i].wpipe[1])) + if (0 != MHD_pipe_close_ (daemon->worker_pool[i].wpipe[1])) MHD_PANIC ("close failed\n"); } } @@ -4058,11 +4047,11 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) pthread_mutex_destroy (&daemon->per_ip_connection_mutex); pthread_mutex_destroy (&daemon->cleanup_connection_mutex); - if (-1 != daemon->wpipe[1]) + if (MHD_INVALID_PIPE_ != daemon->wpipe[1]) { - if (0 != CLOSE (daemon->wpipe[0])) + if (0 != MHD_pipe_close_ (daemon->wpipe[0])) MHD_PANIC ("close failed\n"); - if (0 != CLOSE (daemon->wpipe[1])) + if (0 != MHD_pipe_close_ (daemon->wpipe[1])) MHD_PANIC ("close failed\n"); } free (daemon); diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h @@ -1102,9 +1102,10 @@ struct MHD_Daemon * Pipe we use to signal shutdown, unless * 'HAVE_LISTEN_SHUTDOWN' is defined AND we have a listen * socket (which we can then 'shutdown' to stop listening). - * On W32 this is a socketpair, not a pipe. + * MHD can be build with usage of socketpair instead of + * pipe (forced on W32). */ - int wpipe[2]; + MHD_pipe wpipe[2]; /** * Are we shutting down? diff --git a/src/platform/platform_interface.h b/src/platform/platform_interface.h @@ -26,6 +26,7 @@ #ifndef MHD_PLATFORM_INTERFACE_H #define MHD_PLATFORM_INTERFACE_H +#include "platform.h" #if defined(_WIN32) && !defined(__CYGWIN__) #include "w32functions.h" #endif @@ -66,4 +67,70 @@ #define MHD_set_socket_errno_(errnum) MHD_W32_set_last_winsock_error_((errnum)) #endif +/* MHD_SYS_select_ is wrapper macro for system select() function */ +#if !defined(MHD_WINSOCK_SOCKETS) +#define MHD_SYS_select_(n,r,w,e,t) select((n),(r),(w),(e),(t)) +#else +#define MHD_SYS_select_(n,r,w,e,t) select((int)0,(r),(w),(e),(t)) +#endif + +/* MHD_pipe_ create pipe (!MHD_DONT_USE_PIPES) / + * create two connected sockets (MHD_DONT_USE_PIPES) */ +#ifndef MHD_DONT_USE_PIPES +#define MHD_pipe_(fdarr) pipe((fdarr)) +#else /* MHD_DONT_USE_PIPES */ +#if !defined(_WIN32) || defined(__CYGWIN__) +#define MHD_pipe_(fdarr) socketpair(AF_LOCAL, SOCK_STREAM, 0, (fdarr)) +#else /* !defined(_WIN32) || defined(__CYGWIN__) */ +#define MHD_pipe_(fdarr) MHD_W32_pair_of_sockets_((fdarr)) +#endif /* !defined(_WIN32) || defined(__CYGWIN__) */ +#endif /* MHD_DONT_USE_PIPES */ + +/* MHD_pipe_errno_ is errno of last function (!MHD_DONT_USE_PIPES) / + * errno of last emulated pipe function (MHD_DONT_USE_PIPES) */ +#ifndef MHD_DONT_USE_PIPES +#define MHD_pipe_errno_ errno +#else +#define MHD_pipe_errno_ MHD_socket_errno_ +#endif + +/* MHD_pipe_last_strerror_ is description string of last errno (!MHD_DONT_USE_PIPES) / + * description string of last pipe error (MHD_DONT_USE_PIPES) */ +#ifndef MHD_DONT_USE_PIPES +#define MHD_pipe_last_strerror_() strerror(errno) +#else +#define MHD_pipe_last_strerror_() MHD_socket_last_strerr_() +#endif + +/* MHD_pipe_write_ write data to real pipe (!MHD_DONT_USE_PIPES) / + * write data to emulated pipe (MHD_DONT_USE_PIPES) */ +#ifndef MHD_DONT_USE_PIPES +#define MHD_pipe_write_(fd, ptr, sz) write((fd), (const void*)(ptr), (sz)) +#else +#define MHD_pipe_write_(fd, ptr, sz) send((fd), (const char*)(ptr), (sz), 0) +#endif + +/* MHD_pipe_read_ read data from real pipe (!MHD_DONT_USE_PIPES) / + * read data from emulated pipe (MHD_DONT_USE_PIPES) */ +#ifndef MHD_DONT_USE_PIPES +#define MHD_pipe_read_(fd, ptr, sz) read((fd), (void*)(ptr), (sz)) +#else +#define MHD_pipe_read_(fd, ptr, sz) recv((fd), (char*)(ptr), (sz), 0) +#endif + +/* MHD_pipe_close_(fd) close any FDs (non-W32) / + * close emulated pipe FDs (W32) */ +#ifndef MHD_DONT_USE_PIPES +#define MHD_pipe_close_(fd) close((fd)) +#else +#define MHD_pipe_close_(fd) MHD_socket_close_((fd)) +#endif + +/* MHD_INVALID_PIPE_ is a value of bad pipe FD */ +#ifndef MHD_DONT_USE_PIPES +#define MHD_INVALID_PIPE_ (-1) +#else +#define MHD_INVALID_PIPE_ MHD_INVALID_SOCKET +#endif + #endif // MHD_PLATFORM_INTERFACE_H diff --git a/src/platform/w32functions.c b/src/platform/w32functions.c @@ -541,3 +541,80 @@ void MHD_W32_set_last_winsock_error_(int errnum) break; } } + +/** + * Create pair of mutually connected TCP/IP sockets on loopback address + * @param sockets_pair array to receive resulted sockets + * @return zero on success, -1 otherwise + */ +int MHD_W32_pair_of_sockets_(SOCKET sockets_pair[2]) +{ + int i; + if (!sockets_pair) + { + errno = EINVAL; + return -1; + } + +#define PAIRMAXTRYIES 800 + for (i = 0; i < PAIRMAXTRYIES; i++) + { + struct sockaddr_in listen_addr; + SOCKET listen_s; + static const int c_addinlen = sizeof(struct sockaddr_in); /* help compiler to optimize */ + int addr_len = c_addinlen; + int opt = 1; + + listen_s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (INVALID_SOCKET == listen_s) + break; /* can't create even single socket */ + + listen_addr.sin_family = AF_INET; + listen_addr.sin_port = htons(0); + listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (0 == bind(listen_s, (struct sockaddr*) &listen_addr, c_addinlen) + && 0 == listen(listen_s, 1) + && 0 == getsockname(listen_s, (struct sockaddr*) &listen_addr, + &addr_len)) + { + SOCKET client_s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (INVALID_SOCKET != client_s) + { + if (0 == ioctlsocket(client_s, FIONBIO, (u_long*) &opt) + && (0 == connect(client_s, (struct sockaddr*) &listen_addr, c_addinlen) + || WSAGetLastError() == WSAEWOULDBLOCK)) + { + struct sockaddr_in accepted_from_addr; + addr_len = c_addinlen; + SOCKET server_s = accept(listen_s, + (struct sockaddr*) &accepted_from_addr, &addr_len); + if (INVALID_SOCKET != server_s) + { + struct sockaddr_in client_addr; + addr_len = c_addinlen; + opt = 0; + if (0 == getsockname(client_s, (struct sockaddr*) &client_addr, &addr_len) + && accepted_from_addr.sin_family == client_addr.sin_family + && accepted_from_addr.sin_port == client_addr.sin_port + && accepted_from_addr.sin_addr.s_addr == client_addr.sin_addr.s_addr + && 0 == ioctlsocket(client_s, FIONBIO, (u_long*) &opt) + && 0 == ioctlsocket(server_s, FIONBIO, (u_long*) &opt)) + { + closesocket(listen_s); + sockets_pair[0] = client_s; + sockets_pair[1] = server_s; + return 0; + } + closesocket(server_s); + } + } + closesocket(client_s); + } + } + closesocket(listen_s); + } + + sockets_pair[0] = INVALID_SOCKET; + sockets_pair[1] = INVALID_SOCKET; + return -1; +} diff --git a/src/platform/w32functions.h b/src/platform/w32functions.h @@ -32,6 +32,7 @@ #include <errno.h> #include <winsock2.h> #include "platform.h" +#include "platform_interface.h" #ifdef __cplusplus extern "C" @@ -170,6 +171,13 @@ const char* MHD_W32_strerror_last_winsock_(void); */ void MHD_W32_set_last_winsock_error_(int errnum); +/** + * Create pair of mutually connected TCP/IP sockets on loopback address + * @param sockets_pair array to receive resulted sockets + * @return zero on success, -1 otherwise + */ +int MHD_W32_pair_of_sockets_(SOCKET sockets_pair[2]); + #ifdef __cplusplus } #endif