Last active
July 31, 2017 12:03
-
-
Save mback2k/4478231 to your computer and use it in GitHub Desktop.
select_ws example program
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mingw32-gcc select_ws.c -Wl,-lws2_32 -g -pedantic -Wall -W -Wpointer-arith -Wwrite-strings -Wunused -Wshadow -Winline -Wnested-externs -Wmissing-declarations -Wmissing-prototypes -Wno-long-long -Wfloat-equal -Wno-multichar -Wsign-compare -Wundef -Wno-format-nonliteral -Wendif-labels -Wstrict-prototypes -Wdeclaration-after-statement -Wstrict-aliasing=3 -Wcast-align -Wtype-limits -Wold-style-declaration -Wmissing-parameter-type -Wempty-body -Wclobbered -Wignored-qualifiers -Wconversion -Wno-sign-conversion -Wvla -Wno-pedantic-ms-format -Wno-system-headers -o select_ws.exe |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <unistd.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <errno.h> | |
#include <winsock2.h> | |
#include <windows.h> | |
#include <malloc.h> | |
#include <fcntl.h> | |
#define SET_SOCKERRNO(x) (WSASetLastError((int)(x))) | |
#define curlx_sitosk(x) ((curl_socket_t)(x)) | |
#define curlx_sktosi(x) ((int)(x)) | |
typedef SOCKET curl_socket_t; | |
/* | |
* WinSock select() does not support standard file descriptors, | |
* it can only check SOCKETs. The following function is an attempt | |
* to re-create a select() function with support for other handle types. | |
* | |
* select() function with support for WINSOCK2 sockets and all | |
* other handle types supported by WaitForMultipleObjectsEx() as | |
* well as disk files, anonymous and names pipes, and character input. | |
* | |
* http://msdn.microsoft.com/en-us/library/windows/desktop/ms687028.aspx | |
* http://msdn.microsoft.com/en-us/library/windows/desktop/ms741572.aspx | |
*/ | |
struct select_ws_wait_data { | |
HANDLE handle; /* actual handle to wait for during select */ | |
HANDLE event; /* internal event to abort waiting thread */ | |
}; | |
static DWORD WINAPI select_ws_wait_thread(LPVOID lpParameter) | |
{ | |
struct select_ws_wait_data *data; | |
HANDLE handle, handles[2]; | |
INPUT_RECORD inputrecord; | |
LARGE_INTEGER size, pos; | |
DWORD type, length; | |
/* retrieve handles from internal structure */ | |
data = (struct select_ws_wait_data *) lpParameter; | |
if(data) { | |
handle = data->handle; | |
handles[0] = data->event; | |
handles[1] = handle; | |
free(data); | |
} | |
else | |
return -1; | |
/* retrieve the type of file to wait on */ | |
type = GetFileType(handle); | |
switch(type) { | |
case FILE_TYPE_DISK: | |
/* The handle represents a file on disk, this means: | |
* - WaitForMultipleObjectsEx will always be signalled for it. | |
* - comparison of current position in file and total size of | |
* the file can be used to check if we reached the end yet. | |
* | |
* Approach: Loop till either the internal event is signalled | |
* or if the end of the file has already been reached. | |
*/ | |
while(WaitForMultipleObjectsEx(2, handles, FALSE, INFINITE, FALSE) | |
== WAIT_OBJECT_0 + 1) { | |
/* get total size of file */ | |
size.QuadPart = 0; | |
if(GetFileSizeEx(handle, &size)) { | |
/* get the current position within the file */ | |
pos.QuadPart = 0; | |
if(SetFilePointerEx(handle, pos, &pos, FILE_CURRENT)) { | |
/* compare position with size, abort if not equal */ | |
if(size.QuadPart == pos.QuadPart) { | |
/* sleep and continue waiting */ | |
SleepEx(100, FALSE); | |
continue; | |
} | |
} | |
} | |
/* there is some data available, stop waiting */ | |
break; | |
} | |
break; | |
case FILE_TYPE_CHAR: | |
/* The handle represents a character input, this means: | |
* - WaitForMultipleObjectsEx will be signalled on any kind of input, | |
* including mouse and window size events we do not care about. | |
* | |
* Approach: Loop till either the internal event is signalled | |
* or we get signalled for an actual key-event. | |
*/ | |
while(WaitForMultipleObjectsEx(2, handles, FALSE, INFINITE, FALSE) | |
== WAIT_OBJECT_0 + 1) { | |
/* check if this is an actual console handle */ | |
if(GetConsoleMode(handle, &length)) { | |
/* retrieve an event from the console buffer */ | |
length = 0; | |
if(PeekConsoleInput(handle, &inputrecord, 1, &length)) { | |
/* check if the event is not an actual key-event */ | |
if(length == 1 && inputrecord.EventType != KEY_EVENT) { | |
/* purge the non-key-event and continue waiting */ | |
ReadConsoleInput(handle, &inputrecord, 1, &length); | |
continue; | |
} | |
} | |
} | |
/* there is some data available, stop waiting */ | |
break; | |
} | |
break; | |
case FILE_TYPE_PIPE: | |
/* The handle represents an anonymous or named pipe, this means: | |
* - WaitForMultipleObjectsEx will always be signalled for it. | |
* - peek into the pipe and retrieve the amount of data available. | |
* | |
* Approach: Loop till either the internal event is signalled | |
* or there is data in the pipe available for reading. | |
*/ | |
while(WaitForMultipleObjectsEx(2, handles, FALSE, INFINITE, FALSE) | |
== WAIT_OBJECT_0 + 1) { | |
/* peek into the pipe and retrieve the amount of data available */ | |
if(PeekNamedPipe(handle, NULL, 0, NULL, &length, NULL)) { | |
/* if there is no data available, sleep and continue waiting */ | |
if(length == 0) { | |
SleepEx(100, FALSE); | |
continue; | |
} | |
} | |
else { | |
/* if the pipe has been closed, sleep and continue waiting */ | |
if(GetLastError() == ERROR_BROKEN_PIPE) { | |
SleepEx(100, FALSE); | |
continue; | |
} | |
} | |
/* there is some data available, stop waiting */ | |
break; | |
} | |
break; | |
default: | |
/* The handle has an unknown type, try to wait on it */ | |
WaitForMultipleObjectsEx(2, handles, FALSE, INFINITE, FALSE); | |
break; | |
} | |
return 0; | |
} | |
static HANDLE select_ws_wait(HANDLE handle, HANDLE event) | |
{ | |
struct select_ws_wait_data *data; | |
HANDLE thread = NULL; | |
/* allocate internal waiting data structure */ | |
data = malloc(sizeof(struct select_ws_wait_data)); | |
if(data) { | |
data->handle = handle; | |
data->event = event; | |
/* launch waiting thread */ | |
thread = CreateThread(NULL, 0, | |
&select_ws_wait_thread, | |
data, 0, NULL); | |
/* free data if thread failed to launch */ | |
if(!thread) { | |
free(data); | |
} | |
} | |
return thread; | |
} | |
static int select_ws(int nfds, fd_set *readfds, fd_set *writefds, | |
fd_set *exceptfds, struct timeval *timeout) | |
{ | |
DWORD milliseconds, wait, idx; | |
WSAEVENT wsaevent, *wsaevents; | |
WSANETWORKEVENTS wsanetevents; | |
HANDLE handle, *handles, *threads; | |
curl_socket_t sock, *fdarr, *wsasocks; | |
long networkevents; | |
int error, fds; | |
HANDLE waitevent = NULL; | |
DWORD nfd = 0, thd = 0, wsa = 0; | |
int ret = 0; | |
/* check if the input value is valid */ | |
if(nfds < 0) { | |
errno = EINVAL; | |
return -1; | |
} | |
/* check if we got descriptors, sleep in case we got none */ | |
if(!nfds) { | |
Sleep((timeout->tv_sec * 1000) + (timeout->tv_usec / 1000)); | |
return 0; | |
} | |
/* create internal event to signal waiting threads */ | |
waitevent = CreateEvent(NULL, TRUE, FALSE, NULL); | |
if(!waitevent) { | |
errno = ENOMEM; | |
return -1; | |
} | |
/* allocate internal array for the original input handles */ | |
fdarr = malloc(nfds * sizeof(curl_socket_t)); | |
if(fdarr == NULL) { | |
errno = ENOMEM; | |
return -1; | |
} | |
/* allocate internal array for the internal event handles */ | |
handles = malloc(nfds * sizeof(HANDLE)); | |
if(handles == NULL) { | |
free(fdarr); | |
errno = ENOMEM; | |
return -1; | |
} | |
/* allocate internal array for the internal threads handles */ | |
threads = malloc(nfds * sizeof(HANDLE)); | |
if(threads == NULL) { | |
free(handles); | |
free(fdarr); | |
errno = ENOMEM; | |
return -1; | |
} | |
/* allocate internal array for the internal socket handles */ | |
wsasocks = malloc(nfds * sizeof(curl_socket_t)); | |
if(wsasocks == NULL) { | |
free(threads); | |
free(handles); | |
free(fdarr); | |
errno = ENOMEM; | |
return -1; | |
} | |
/* allocate internal array for the internal WINSOCK2 events */ | |
wsaevents = malloc(nfds * sizeof(WSAEVENT)); | |
if(wsaevents == NULL) { | |
free(threads); | |
free(wsasocks); | |
free(handles); | |
free(fdarr); | |
errno = ENOMEM; | |
return -1; | |
} | |
/* loop over the handles in the input descriptor sets */ | |
for(fds = 0; fds < nfds; fds++) { | |
networkevents = 0; | |
handles[nfd] = 0; | |
if(FD_ISSET(fds, readfds)) | |
networkevents |= FD_READ|FD_ACCEPT|FD_CLOSE; | |
if(FD_ISSET(fds, writefds)) | |
networkevents |= FD_WRITE|FD_CONNECT; | |
if(FD_ISSET(fds, exceptfds)) | |
networkevents |= FD_OOB|FD_CLOSE; | |
/* only wait for events for which we actually care */ | |
if(networkevents) { | |
fdarr[nfd] = curlx_sitosk(fds); | |
if(fds == fileno(stdin)) { | |
handle = GetStdHandle(STD_INPUT_HANDLE); | |
handle = select_ws_wait(handle, waitevent); | |
handles[nfd] = handle; | |
threads[thd] = handle; | |
thd++; | |
} | |
else if(fds == fileno(stdout)) { | |
handles[nfd] = GetStdHandle(STD_OUTPUT_HANDLE); | |
} | |
else if(fds == fileno(stderr)) { | |
handles[nfd] = GetStdHandle(STD_ERROR_HANDLE); | |
} | |
else { | |
wsaevent = WSACreateEvent(); | |
if(wsaevent != WSA_INVALID_EVENT) { | |
error = WSAEventSelect(fds, wsaevent, networkevents); | |
if(error != SOCKET_ERROR) { | |
handle = (HANDLE) wsaevent; | |
handles[nfd] = handle; | |
wsasocks[wsa] = curlx_sitosk(fds); | |
wsaevents[wsa] = wsaevent; | |
wsa++; | |
} | |
else { | |
WSACloseEvent(wsaevent); | |
handle = (HANDLE) curlx_sitosk(fds); | |
handle = select_ws_wait(handle, waitevent); | |
handles[nfd] = handle; | |
threads[thd] = handle; | |
thd++; | |
} | |
} | |
} | |
nfd++; | |
} | |
} | |
/* convert struct timeval to milliseconds */ | |
if(timeout) { | |
milliseconds = ((timeout->tv_sec * 1000) + (timeout->tv_usec / 1000)); | |
} | |
else { | |
milliseconds = INFINITE; | |
} | |
/* wait for one of the internal handles to trigger */ | |
wait = WaitForMultipleObjectsEx(nfd, handles, FALSE, milliseconds, FALSE); | |
/* signal the event handle for the waiting threads */ | |
SetEvent(waitevent); | |
/* loop over the internal handles returned in the descriptors */ | |
for(idx = 0; idx < nfd; idx++) { | |
handle = handles[idx]; | |
sock = fdarr[idx]; | |
fds = curlx_sktosi(sock); | |
/* check if the current internal handle was triggered */ | |
if(wait != WAIT_FAILED && (wait - WAIT_OBJECT_0) <= idx && | |
WaitForSingleObjectEx(handle, 0, FALSE) == WAIT_OBJECT_0) { | |
/* first handle stdin, stdout and stderr */ | |
if(fds == fileno(stdin)) { | |
/* stdin is never ready for write or exceptional */ | |
FD_CLR(sock, writefds); | |
FD_CLR(sock, exceptfds); | |
} | |
else if(fds == fileno(stdout) || fds == fileno(stderr)) { | |
/* stdout and stderr are never ready for read or exceptional */ | |
FD_CLR(sock, readfds); | |
FD_CLR(sock, exceptfds); | |
} | |
else { | |
/* try to handle the event with the WINSOCK2 functions */ | |
wsanetevents.lNetworkEvents = 0; | |
error = WSAEnumNetworkEvents(fds, handle, &wsanetevents); | |
if(error != SOCKET_ERROR) { | |
/* remove from descriptor set if not ready for read/accept/close */ | |
if(!(wsanetevents.lNetworkEvents & (FD_READ|FD_ACCEPT|FD_CLOSE))) | |
FD_CLR(sock, readfds); | |
/* remove from descriptor set if not ready for write/connect */ | |
if(!(wsanetevents.lNetworkEvents & (FD_WRITE|FD_CONNECT))) | |
FD_CLR(sock, writefds); | |
/* HACK: | |
* use exceptfds together with readfds to signal | |
* that the connection was closed by the client. | |
* | |
* Reason: FD_CLOSE is only signaled once, sometimes | |
* at the same time as FD_READ with data being available. | |
* This means that recv/sread is not reliable to detect | |
* that the connection is closed. | |
*/ | |
/* remove from descriptor set if not exceptional */ | |
if(!(wsanetevents.lNetworkEvents & (FD_OOB|FD_CLOSE))) | |
FD_CLR(sock, exceptfds); | |
} | |
} | |
/* check if the event has not been filtered using specific tests */ | |
if(FD_ISSET(sock, readfds) || FD_ISSET(sock, writefds) || | |
FD_ISSET(sock, exceptfds)) { | |
ret++; | |
} | |
} | |
else { | |
/* remove from all descriptor sets since this handle did not trigger */ | |
FD_CLR(sock, readfds); | |
FD_CLR(sock, writefds); | |
FD_CLR(sock, exceptfds); | |
} | |
} | |
for(idx = 0; idx < wsa; idx++) { | |
WSAEventSelect(wsasocks[idx], NULL, 0); | |
WSACloseEvent(wsaevents[idx]); | |
} | |
for(idx = 0; idx < thd; idx++) { | |
WaitForSingleObject(threads[thd], INFINITE); | |
CloseHandle(threads[thd]); | |
} | |
CloseHandle(waitevent); | |
free(wsaevents); | |
free(wsasocks); | |
free(threads); | |
free(handles); | |
free(fdarr); | |
return ret; | |
} | |
int main(void) | |
{ | |
WORD wVersionRequested; | |
WSADATA wsaData; | |
SOCKET sock[4]; | |
struct sockaddr_in sockaddr[4]; | |
fd_set readfds; | |
fd_set writefds; | |
fd_set exceptfds; | |
SOCKET maxfd = 0; | |
int selfd = 0; | |
void *buffer = malloc(1024); | |
ssize_t nread; | |
setmode(fileno(stdin), O_BINARY); | |
wVersionRequested = MAKEWORD(2, 2); | |
WSAStartup(wVersionRequested, &wsaData); | |
sock[0] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | |
sockaddr[0].sin_family = AF_INET; | |
sockaddr[0].sin_addr.s_addr = inet_addr("8.8.8.8"); | |
sockaddr[0].sin_port = htons(53); | |
connect(sock[0], (struct sockaddr *) &sockaddr[0], sizeof(sockaddr[0])); | |
sock[1] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | |
sockaddr[1].sin_family = AF_INET; | |
sockaddr[1].sin_addr.s_addr = inet_addr("8.8.4.4"); | |
sockaddr[1].sin_port = htons(53); | |
connect(sock[1], (struct sockaddr *) &sockaddr[1], sizeof(sockaddr[1])); | |
sock[2] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | |
sockaddr[2].sin_family = AF_INET; | |
sockaddr[2].sin_addr.s_addr = inet_addr("127.0.0.1"); | |
sockaddr[2].sin_port = htons(1337); | |
printf("bind = %d\n", bind(sock[2], (struct sockaddr *) &sockaddr[2], sizeof(sockaddr[2]))); | |
printf("listen = %d\n", listen(sock[2], 5)); | |
sock[3] = INVALID_SOCKET; | |
while(1) { | |
FD_ZERO(&readfds); | |
FD_ZERO(&writefds); | |
FD_ZERO(&exceptfds); | |
FD_SET(sock[0], &readfds); | |
FD_SET(sock[0], &exceptfds); | |
maxfd = maxfd > sock[0] ? maxfd : sock[0]; | |
FD_SET(sock[1], &readfds); | |
FD_SET(sock[1], &exceptfds); | |
maxfd = maxfd > sock[1] ? maxfd : sock[1]; | |
FD_SET(sock[2], &readfds); | |
FD_SET(sock[2], &exceptfds); | |
maxfd = maxfd > sock[2] ? maxfd : sock[2]; | |
if(sock[3] != INVALID_SOCKET) { | |
FD_SET(sock[3], &readfds); | |
FD_SET(sock[3], &exceptfds); | |
maxfd = maxfd > sock[3] ? maxfd : sock[3]; | |
} | |
FD_SET((SOCKET)fileno(stdin), &readfds); | |
maxfd = maxfd > (SOCKET)fileno(stdin) ? maxfd : (SOCKET)fileno(stdin); | |
printf("maxfd = %d\n", maxfd); | |
selfd = select_ws(maxfd + 1, &readfds, &writefds, &exceptfds, NULL); | |
printf("selfd = %d\n", selfd); | |
if(FD_ISSET(sock[0], &readfds)) { | |
printf("read sock[0]\n"); | |
nread = recv(sock[0], buffer, 1024, 0); | |
printf("read sock[0] = %d\n", nread); | |
} | |
if(FD_ISSET(sock[0], &exceptfds)) { | |
printf("exception sock[0]\n"); | |
} | |
if(FD_ISSET(sock[1], &readfds)) { | |
printf("read sock[1]\n"); | |
nread = recv(sock[1], buffer, 1024, 0); | |
printf("read sock[1] = %d\n", nread); | |
} | |
if(FD_ISSET(sock[1], &exceptfds)) { | |
printf("exception sock[1]\n"); | |
} | |
if(FD_ISSET(sock[2], &readfds)) { | |
if(sock[3] != INVALID_SOCKET) | |
closesocket(sock[3]); | |
printf("accept sock[2] = %d\n", sock[2]); | |
nread = sizeof(sockaddr[3]); | |
printf("WSAGetLastError = %d\n", WSAGetLastError()); | |
sock[3] = accept(sock[2], (struct sockaddr *) &sockaddr[3], &nread); | |
printf("WSAGetLastError = %d\n", WSAGetLastError()); | |
printf("accept sock[2] = %d\n", sock[3]); | |
} | |
if(FD_ISSET(sock[2], &exceptfds)) { | |
printf("exception sock[2]\n"); | |
} | |
if(sock[3] != INVALID_SOCKET) { | |
if(FD_ISSET(sock[3], &readfds)) { | |
printf("read sock[3]\n"); | |
nread = recv(sock[3], buffer, 1024, 0); | |
printf("read sock[3] = %d\n", nread); | |
} | |
if(FD_ISSET(sock[3], &exceptfds)) { | |
printf("exception sock[3]\n"); | |
} | |
} | |
if(FD_ISSET(fileno(stdin), &readfds)) { | |
printf("read fileno(stdin)\n"); | |
nread = read(fileno(stdin), buffer, 1024); | |
printf("read fileno(stdin) = %d\n", nread); | |
} | |
} | |
WSACleanup(); | |
free(buffer); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment