Last active
February 14, 2022 19:50
-
-
Save brokenprogrammer/7ef31af950fcdd4063faaa1212a97ee3 to your computer and use it in GitHub Desktop.
Reference implementation for Event based Overlapped (Asynchronous) I/O TCP Echo Server.
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
| #define Win32OutputWSAErrorCode printf | |
| #define Win32OutputErrorCode printf | |
| DWORD WINAPI | |
| _Win32NetworkListeningThreadProc(LPVOID lpParameter) | |
| { | |
| win32_mprof_net_state *State = (win32_mprof_net_state *)lpParameter; | |
| win32_mprof_sock_connection *SocketConnection; | |
| DWORD BytesTransferred; | |
| DWORD Flags; | |
| while(TRUE) | |
| { | |
| // NOTE(Oskar): Quit flag has been set so we exit and cleanup. | |
| if (State->EchoThreadQuit) | |
| { | |
| break; | |
| } | |
| DWORD WSAWaitResult = WSAWaitForMultipleEvents(State->NumberOfEvents, State->EventArray, FALSE, WSA_INFINITE, FALSE); | |
| if (WSAWaitResult == WSA_WAIT_FAILED) | |
| { | |
| Win32OutputWSAErrorCode("WSAWait Failed."); | |
| return 0; | |
| } | |
| // NOTE(Oskar): Ignore the case if its Zero because that is a new connection handled | |
| // by other thread. | |
| DWORD EventIndex = WSAWaitResult - WSA_WAIT_EVENT_0; | |
| if ((EventIndex) == 0) | |
| { | |
| WSAResetEvent(State->EventArray[0]); | |
| continue; | |
| } | |
| SocketConnection = State->Connections[EventIndex]; | |
| WSAResetEvent(State->EventArray[EventIndex]); | |
| BOOL OverlappedResult = WSAGetOverlappedResult(SocketConnection->Socket, &(SocketConnection->Overlapped), &BytesTransferred, FALSE, &Flags); | |
| if (OverlappedResult == FALSE || BytesTransferred == 0) | |
| { | |
| printf("Closing socket %d\n", (int)SocketConnection->Socket); | |
| if (closesocket(SocketConnection->Socket) == SOCKET_ERROR) | |
| { | |
| Win32OutputWSAErrorCode("Call to closesocket Failed."); | |
| } | |
| GlobalFree(SocketConnection); | |
| WSACloseEvent(State->EventArray[EventIndex]); | |
| // NOTE(Oskar): Move event and socket handles to the back of their respective arrays. | |
| EnterCriticalSection(&State->CriticalSection); | |
| { | |
| if (EventIndex + 1 != State->NumberOfEvents) | |
| { | |
| for (DWORD Index = EventIndex; Index < State->NumberOfEvents; Index++) | |
| { | |
| State->EventArray[Index] = State->EventArray[Index + 1]; | |
| State->Connections[Index] = State->Connections[Index + 1]; | |
| } | |
| } | |
| State->NumberOfEvents--; | |
| } | |
| LeaveCriticalSection(&State->CriticalSection); | |
| continue; | |
| } | |
| // NOTE(Oskar): Here we check what the last queued operation was. | |
| // NOTE(Oskar): Everything bellow here is basically the echo functionality that you can replace. | |
| if (SocketConnection->Operation == MPROF_SOCK_QUEUED_OP_READ) | |
| { | |
| SocketConnection->BytesReceived = BytesTransferred; | |
| SocketConnection->BytesSent = 0; | |
| } | |
| else if (SocketConnection->Operation == MPROF_SOCK_QUEUED_OP_SEND) | |
| { | |
| SocketConnection->BytesReceived = 0; | |
| SocketConnection->BytesSent += BytesTransferred; | |
| } | |
| // NOTE(Oskar): Send with WSASend. WSASend doesn't guarantee sending everything in one go | |
| // so we continue sending untill all bytes are sent. | |
| if (SocketConnection->BytesReceived > SocketConnection->BytesSent) | |
| { | |
| SocketConnection->Operation = MPROF_SOCK_QUEUED_OP_SEND; | |
| ZeroMemory(&(SocketConnection->Overlapped), sizeof(WSAOVERLAPPED)); | |
| SocketConnection->Overlapped.hEvent = State->EventArray[EventIndex]; | |
| SocketConnection->DataBuf.buf = SocketConnection->Buffer + SocketConnection->BytesSent; | |
| SocketConnection->DataBuf.len = SocketConnection->BytesReceived - SocketConnection->BytesSent; | |
| if (WSASend(SocketConnection->Socket, &(SocketConnection->DataBuf), 1, NULL, 0, &(SocketConnection->Overlapped), NULL) == SOCKET_ERROR) | |
| { | |
| if (WSAGetLastError() != ERROR_IO_PENDING) | |
| { | |
| Win32OutputWSAErrorCode("WSASend Failed."); | |
| return 0; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| // NOTE(Oskar): No more byte to send so we continue with WSARecv. | |
| SocketConnection->Operation = MPROF_SOCK_QUEUED_OP_READ; | |
| Flags = 0; | |
| ZeroMemory(&(SocketConnection->Overlapped), sizeof(WSAOVERLAPPED)); | |
| SocketConnection->Overlapped.hEvent = State->EventArray[EventIndex]; | |
| SocketConnection->DataBuf.len = MPROF_SOCK_BUFFER_SIZE; | |
| SocketConnection->DataBuf.buf = SocketConnection->Buffer; | |
| if (WSARecv(SocketConnection->Socket, &(SocketConnection->DataBuf), 1, NULL, &Flags, &(SocketConnection->Overlapped), NULL) == SOCKET_ERROR) | |
| { | |
| if (WSAGetLastError() != ERROR_IO_PENDING) | |
| { | |
| Win32OutputWSAErrorCode("WSARecv Failed."); | |
| return 0; | |
| } | |
| } | |
| } | |
| } | |
| // NOTE(Oskar): Cleanup | |
| for (int Index = 0; Index < State->NumberOfEvents; ++Index) | |
| { | |
| win32_mprof_sock_connection *Connection = State->Connections[Index]; | |
| printf("Closing socket %d\n", (int)SocketConnection->Socket); | |
| if (Connection->Socket != INVALID_SOCKET) | |
| { | |
| closesocket(SocketConnection->Socket); | |
| } | |
| GlobalFree(SocketConnection); | |
| WSACloseEvent(State->EventArray[Index]); | |
| } | |
| return 0; | |
| } | |
| static int | |
| Win32InitializeWinsock() | |
| { | |
| WSADATA WSAData; | |
| if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0) | |
| { | |
| Win32OutputWSAErrorCode("WSAStartup Failed"); | |
| WSACleanup(); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static win32_mprof_net_state | |
| Win32InitializeNetworking() | |
| { | |
| win32_mprof_net_state Result = {0}; | |
| Result.Initialized = 0; | |
| Result.NumberOfEvents = 0; | |
| InitializeCriticalSection(&Result.CriticalSection); | |
| struct addrinfo Hints; | |
| ZeroMemory(&Hints, sizeof(Hints)); | |
| Hints.ai_family = AF_INET; | |
| Hints.ai_socktype = SOCK_STREAM; | |
| Hints.ai_protocol = IPPROTO_TCP; | |
| Hints.ai_flags = AI_PASSIVE; | |
| if (getaddrinfo(NULL, MPROF_SOCK_DEFAULT_PORT, &Hints, &Result.AddressInfo) != 0) | |
| { | |
| Win32OutputWSAErrorCode("getaddrinfo Failed"); | |
| WSACleanup(); | |
| return Result; | |
| } | |
| // NOTE(Oskar): Initialize socket object | |
| Result.ListenSocket = WSASocket(Result.AddressInfo->ai_family, | |
| Result.AddressInfo->ai_socktype, | |
| Result.AddressInfo->ai_protocol, | |
| NULL, 0, WSA_FLAG_OVERLAPPED); | |
| if (Result.ListenSocket == INVALID_SOCKET) | |
| { | |
| Win32OutputWSAErrorCode("WSASocket Failed, unable to create listening socket."); | |
| WSACleanup(); | |
| return Result; | |
| } | |
| // NOTE(Oskar): Bind socket to a network address. | |
| if (bind(Result.ListenSocket, Result.AddressInfo->ai_addr, (int)Result.AddressInfo->ai_addrlen) == SOCKET_ERROR) | |
| { | |
| Win32OutputWSAErrorCode("Failed to bind listening socket."); | |
| WSACleanup(); | |
| return Result; | |
| } | |
| // NOTE(Oskar): Listen for incoming requests. | |
| if (listen(Result.ListenSocket, MPROF_SOCK_DEFAULT_BACKLOG) == SOCKET_ERROR) | |
| { | |
| Win32OutputWSAErrorCode("Failed to initiate listening on socket."); | |
| WSACleanup(); | |
| return Result; | |
| } | |
| Result.Initialized = 1; | |
| return Result; | |
| } | |
| static void | |
| Win32CleanupNetworking(win32_mprof_net_state *State) | |
| { | |
| // NOTE(Oskar): Set quit flag to allow thread to clean up after itself. | |
| State->EchoThreadQuit = 1; | |
| if (State->ListenSocket != INVALID_SOCKET) | |
| { | |
| closesocket(State->ListenSocket); | |
| } | |
| if (State->LastAcceptSocket != INVALID_SOCKET) | |
| { | |
| closesocket(State->LastAcceptSocket); | |
| } | |
| if (State->AddressInfo != NULL) | |
| { | |
| freeaddrinfo(State->AddressInfo); | |
| } | |
| WSACleanup(); | |
| } | |
| static void | |
| _Win32SocketConnectionInitialize(win32_mprof_sock_connection *Target, SOCKET LastAcceptSocket) | |
| { | |
| Target->Socket = LastAcceptSocket; | |
| ZeroMemory(&(Target->Overlapped), sizeof(OVERLAPPED)); | |
| Target->Operation = MPROF_SOCK_QUEUED_OP_READ; | |
| Target->BytesSent = 0; | |
| Target->BytesReceived = 0; | |
| Target->DataBuf.len = MPROF_SOCK_BUFFER_SIZE; | |
| Target->DataBuf.buf = Target->Buffer; | |
| } | |
| static int | |
| Win32NetworkListen(win32_mprof_net_state *State) | |
| { | |
| if ((State->LastAcceptSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) | |
| { | |
| Win32OutputWSAErrorCode("Failed to create Accepting socket."); | |
| WSACleanup(); | |
| return -1; | |
| } | |
| if ((State->EventArray[0] = WSACreateEvent()) == WSA_INVALID_EVENT) | |
| { | |
| Win32OutputWSAErrorCode("Failed to create new event object."); | |
| return -1; | |
| } | |
| State->EchoThreadHandle = CreateThread(NULL, 0, _Win32NetworkListeningThreadProc, State, 0, &State->EchoThreadId); | |
| if (State->EchoThreadHandle == NULL) | |
| { | |
| Win32OutputErrorCode("Failed to create thread."); | |
| return -1; | |
| } | |
| // NOTE(Oskar): Loop to accept new incoming connections. Upon a new connection we allocate a new | |
| // socket connection that is then available for our listening thread. | |
| State->NumberOfEvents = 1; | |
| while (TRUE) | |
| { | |
| if ((State->LastAcceptSocket = accept(State->ListenSocket, NULL, NULL)) == INVALID_SOCKET) | |
| { | |
| Win32OutputWSAErrorCode("Failed to accept incoming connection."); | |
| return -1; | |
| } | |
| EnterCriticalSection(&State->CriticalSection); | |
| { | |
| if ((State->Connections[State->NumberOfEvents] = (win32_mprof_sock_connection *) GlobalAlloc(GPTR, sizeof(win32_mprof_sock_connection))) == NULL) | |
| { | |
| Win32OutputErrorCode("Call to global alloc failed."); | |
| return -1; | |
| } | |
| win32_mprof_sock_connection *TargetConnection = State->Connections[State->NumberOfEvents]; | |
| _Win32SocketConnectionInitialize(TargetConnection, State->LastAcceptSocket); | |
| if ((TargetConnection->Overlapped.hEvent = State->EventArray[State->NumberOfEvents] = WSACreateEvent()) == WSA_INVALID_EVENT) | |
| { | |
| Win32OutputWSAErrorCode("Failed to create new event object."); | |
| return -1; | |
| } | |
| // Post a WSARecv() request to to begin receiving data on the socket | |
| DWORD Flags; | |
| DWORD RecvBytes; | |
| if (WSARecv(TargetConnection->Socket, | |
| &(TargetConnection->DataBuf), 1, &RecvBytes, &Flags, &(TargetConnection->Overlapped), NULL) == SOCKET_ERROR) | |
| { | |
| if (WSAGetLastError() != ERROR_IO_PENDING) | |
| { | |
| Win32OutputWSAErrorCode("Failed to recieve data from socket."); | |
| return -1; | |
| } | |
| } | |
| State->NumberOfEvents++; | |
| } | |
| LeaveCriticalSection(&State->CriticalSection); | |
| // Signal the first event in the event array to tell the worker thread to | |
| // service an additional event in the event array | |
| if (WSASetEvent(State->EventArray[0]) == FALSE) | |
| { | |
| Win32OutputWSAErrorCode("Failed to set event object as signaled."); | |
| return -1; | |
| } | |
| } | |
| return 0; | |
| } |
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
| #define MPROF_SOCK_DEFAULT_PORT "27015" | |
| #define MPROF_SOCK_BUFFER_SIZE 4096 | |
| #define MPROF_SOCK_MAX_THREAD 4 | |
| #define MPROF_SOCK_MAX_IO_REQUESTS 10 | |
| #define MPROF_SOCK_DEFAULT_BACKLOG 5 | |
| typedef enum _win32_mprof_sock_queued_op | |
| { | |
| MPROF_SOCK_QUEUED_OP_READ = 0x1, | |
| MPROF_SOCK_QUEUED_OP_SEND = 0x2, | |
| } win32_mprof_sock_queued_op; | |
| typedef struct _win32_mprof_sock_connection | |
| { | |
| CHAR Buffer[MPROF_SOCK_BUFFER_SIZE]; | |
| WSABUF DataBuf; | |
| SOCKET Socket; | |
| WSAOVERLAPPED Overlapped; | |
| win32_mprof_sock_queued_op Operation; | |
| DWORD BytesSent; | |
| DWORD BytesReceived; | |
| } win32_mprof_sock_connection; | |
| typedef struct _win32_mprof_net_state | |
| { | |
| CRITICAL_SECTION CriticalSection; | |
| struct addrinfo *AddressInfo; | |
| SOCKET ListenSocket; | |
| SOCKET LastAcceptSocket; | |
| HANDLE EchoThreadHandle; | |
| DWORD EchoThreadId; | |
| BOOL EchoThreadQuit; | |
| DWORD NumberOfEvents; | |
| WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS]; | |
| win32_mprof_sock_connection *Connections[WSA_MAXIMUM_WAIT_EVENTS]; | |
| BOOL Initialized; | |
| } win32_mprof_net_state; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment