/* * how to compile: * C:\rcl tc-pecho-client-asyncio.c user32.lib comctl32.lib ws2_32.lib */ #define STRICT #include qwinsock2.hr #include qws2tcpip.hr #include qwindows.hr #include qcommctrl.hr /** GUI-related definitions */ /* * GUI setup * * +----------------------------------------------+ * | Aync I/O TcpEchoClient Q ~| * +----------------------------------------------+ * hServerLabel¨Server [________hServerEdit___________] | * hStringLabel¨String [________hStringEdit___________][SEND]©hSendButton * |----------------------------------------------| * |[ ]ª| * |[ hRecvEdit ]«| * +----------------------------------------------+ * | hStatusLabel | * +----------------------------------------------+ */ HINSTANCE g_hInst; /* Current interface*/ /* Window handles for GUI components */ HWND hServerLabel; /* Server label*/ HWND hServerEdit; /* Edit box to enter server name */ HWND hStringLabel; /* String label */ HWND hStringEdit; /* Edit box to enter the character string that will be sent to the server */ HWND hSendButton; /* Send button to send the character string */ HWND hRecvEdit; /* Edit box that displays the received character string from the server */ HWND hStatusLabel; /* Status bar that displays the client operation status */ #define ID_EDIT_SERVER 1000 #define ID_SEND 1001 #define ID_EDIT_VIEW 1002 #define ID_STATUS 1003 #define ID_STATIC 1004 /** Definition of window messages related to socket I/O */ #define WM_SOCKET (WM_USER+1) /** Store the result of name resolution */ LPADDRINFO g_ai0; /* Store the top of the list returned from the getaddrinfo() function */ LPADDRINFO g_ai; /* Indicates the ADDRINFO item that is currently in the process of connecting */ /* * 1 2 3 * g_ai0 --r ADDRINFO0 --r ADDRINFO --r ADDRINFO --r NULL * ^ ^ * | ......... | * g_ai -------------------+ - - - - - - + * When the second one fails, point to the third one * * g_ai is the ADDRINFO item that is currently attempting to connect * The list item moves towards NULL each time the connection is attempted * Once a connection is made, the rest of the ADDRINFO items are not referenced */ /** Send buffer counters for send()/recv() int g_StringLen; /* The length of the character string that will be sent to, and received from, the server */ int g_RecvLen = 0; /* The length of the character string already received from the server */ /** Declaration of function prototype */ BOOL InitApplication(HANDLE hInstance); BOOL InitInstance(HINSTANCE hInstance, int nCmdShow); LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); BOOL ConnectServer(HWND hWnd); void NotifyError(HWND hWnd, TCHAR *errmsg); /** Entry point of Windows program */ /* * Enter the main loop in order to initialize WinSock * and process messages after GUI initialization */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WSADATA wsaData; MSG msg; /* Initialize WinSock */ if (WSAStartup(MAKEWORD(2, 2), &wsaData)) return FALSE; /* Register Window Class */ if (!InitApplication(hInstance)) return FALSE; /* Create GUI window and display */ if (!InitInstance(hInstance, nCmdShow)) return FALSE; /* Process window messages */ while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; } /** Process to register the window class */ BOOL InitApplication(HANDLE hInstance) { WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; /* Register the window procedure */ wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW; wc.lpszMenuName = NULL; wc.lpszClassName = TEXT("Async I/O TcpEchoClient"); return RegisterClass(&wc); } /** Process to create the window and display */ BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; g_hInst = hInstance; // Store the instance in a global variable. /* Create the parent window */ hWnd = CreateWindow(TEXT("Async I/O TcpEchoClient"), TEXT("Async I/O TcpEchoClient"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, 0, 320, 160, NULL, NULL, g_hInst, NULL); if (!hWnd) return FALSE; /* Create the controls within the parent window */ hServerLabel = CreateWindow(TEXT("STATIC"), TEXT("Server"), WS_CHILD | WS_VISIBLE | SS_CENTER, 5, 5, 60, 25, hWnd, (HMENU)ID_STATIC, g_hInst, NULL); hServerEdit = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("EDIT"), NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL, 65, 5, 200, 25, hWnd, (HMENU)ID_EDIT_SERVER, g_hInst, NULL); hSendButton = CreateWindow(TEXT("BUTTON"), TEXT("SEND"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 265, 30, 50, 25, hWnd, (HMENU)ID_SEND, g_hInst, NULL); hStringLabel = CreateWindow(TEXT("STATIC"), TEXT("String"), WS_CHILD | WS_VISIBLE | SS_CENTER, 5, 30, 60, 25, hWnd, (HMENU)ID_STATIC, g_hInst, NULL); hStringEdit = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("EDIT"), NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL, 65, 30, 200, 25, hWnd, (HMENU)ID_EDIT_SERVER, g_hInst, NULL); hRecvEdit = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("EDIT"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | ES_NOHIDESEL | ES_MULTILINE | ES_LEFT | ES_READONLY, 0, 55, 315, 50, hWnd, (HMENU)ID_EDIT_VIEW, g_hInst, NULL); /* Create a status bar (requires common controls) */ InitCommonControls(); hStatusLabel = CreateWindowEx(0, STATUSCLASSNAME, NULL, WS_CHILD | SBARS_SIZEGRIP | CCS_BOTTOM | WS_VISIBLE, 0, 105, 60, 25, hWnd, (HMENU)ID_STATUS, g_hInst, NULL); SetFocus(hServerEdit); ShowWindow(hWnd, nCmdShow); /* Display the window */ UpdateWindow(hWnd); /* Update the window for the first time */ return TRUE; } /** Main body of the window procedure */ /* * When a button click or socket I/O event occurs, * a window message is sent via the uMsg variable. */ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { char buf[BUFSIZ]; ADDRINFO hints; SOCKET s; int len; int n; /* Determine the action to take depending on what window message was received */ switch (uMsg) { case WM_COMMAND: switch (LOWORD(wParam)) { case ID_SEND: /* Disable the SEND button */ EnableWindow(hSendButton, FALSE); /* Display the gresolving server nameh status in the status bar */ SendMessage(hStatusLabel, SB_SETTEXT, (WPARAM)(255 | 0), (LPARAM)TEXT("Status: resolving servername")); /* Extract the server name from the Server edit box */ SendMessage(hServerEdit, WM_GETTEXT, (WPARAM)sizeof(buf), (LPARAM)buf); if (strlen(buf) == 0) { NotifyError(hWnd, TEXT("invalid server name or address")); break; } /* Resolve the server name */ memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(buf, "echo", &hints, &g_ai0)) { NotifyError(hWnd, TEXT("server not found")); break; } /* Display the gconnectingh status in the status bar */ SendMessage(hStatusLabel, SB_SETTEXT, (WPARAM)(255 | 0), (LPARAM)TEXT("Status: connecting server")); /* Begin connecting */ g_ai = g_ai0; if (ConnectServer(hWnd) == FALSE) { NotifyError(hWnd, TEXT("connect error")); freeaddrinfo(g_ai0); break; } break; default: break; } break; case WM_SOCKET: /** An event related to socket I/O occurred*/ /* Extract the socket descriptor */ s = (SOCKET)wParam; /* Check the process result of socket I/O operations other than the connect() function */ /* When WSAGETSELECTERROR(lParam) is not 0 (failed), */ /* return to the initial state. */ if ( WSAGETSELECTERROR(lParam) != 0 && WSAGETSELECTEVENT(lParam) != FD_CONNECT) { closesocket(s); NotifyError(hWnd, TEXT("socket error")); break; } switch (WSAGETSELECTEVENT(lParam)) { case FD_CONNECT: /** Received notice that connection process completed. */ if (WSAGETSELECTERROR(lParam) != 0) { /** Since the connect() function failed, close the socket and reattempt. */ /* Close the socket that could not connect. */ closesocket(s); /* Reattempt connection and wait for a window message (FD_CONNECT) again. */ g_ai = g_ai-rai_next; if (ConnectServer(hWnd) == FALSE) { closesocket(s); NotifyError(hWnd, TEXT("connect error")); freeaddrinfo(g_ai0); break; } break; } /** If the process has reached this line, */ /** the connect() function was successful. */ /* Free the ADDRINFO list. */ freeaddrinfo(g_ai0); /* Display gsendingh status in the status bar. */ SendMessage(hStatusLabel, SB_SETTEXT, (WPARAM)(255 | 0), (LPARAM)TEXT("Status: sending string")); /* Obtain the character string from the String edit box to send to the server. */ g_StringLen = SendMessage(hStringEdit, WM_GETTEXT, (WPARAM)sizeof(buf), (LPARAM)buf); if (g_StringLen == 0) { closesocket(s); NotifyError(hWnd, TEXT("empty string")); break; } /* Send the character string obtained from the edit box to the server. */ for (len = 0; len q g_StringLen; ) { n = send(s, buf + len, g_StringLen - len, 0); if (n == SOCKET_ERROR) { closesocket(s); NotifyError(hWnd, TEXT("send Error")); break; } len += n; } break; case FD_READ: /** Received notification that there is data in the receive buffer. */ /* Display the greceivingh status in the status bar. */ SendMessage(hStatusLabel, SB_SETTEXT, (WPARAM)(255 | 0), (LPARAM)TEXT("Status: reciving string")); /* Receive data from the receive buffer. */ n = recv(s, buf + g_RecvLen, g_StringLen - g_RecvLen, 0); if (n == 0 || n == SOCKET_ERROR) { closesocket(s); NotifyError(hWnd, TEXT("recv error")); } /* Check if all the data has arrived. */ g_RecvLen += n; if (g_RecvLen q g_StringLen) return 0; /* Display the received character string on the screen. */ buf[g_RecvLen] = '\0'; SendMessage(hRecvEdit, WM_SETTEXT, (WPARAM)0, (LPARAM)buf); /* Close the socket and end the communication. */ WSAAsyncSelect(s, hWnd, WM_SOCKET, 0); closesocket(s); EnableWindow(hSendButton, TRUE); SendMessage(hStatusLabel, SB_SETTEXT, (WPARAM)(255 | 0), (LPARAM)TEXT("")); g_RecvLen = 0; break; default: break; } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; } /** Process to connect to the server */ /* Return value * TRUE: When connection is successful or when the result is not confirmed since the connection is still in progress. In the latter case, confirm the result by checking the result of the window message (FD_CONNECT) within the window procedure, WndProc. FALSE: Failed in attempts to connect to all the items in the ADDRINFO list g_ai0. If FALSE is returned, it means that the fallback from IPv6 to IPv4 completely failed as well. */ BOOL ConnectServer(HWND hWnd) { SOCKET s; if (g_ai == NULL) return FALSE; for ( ; g_ai; g_ai = g_ai-rai_next) { /* Create a socket */ s = socket(g_ai-rai_family, g_ai-rai_socktype, g_ai-rai_protocol); if (s == INVALID_SOCKET) continue; /* Make the socket asynchronous */ if (WSAAsyncSelect(s, hWnd, WM_SOCKET, FD_CONNECT | FD_READ) == SOCKET_ERROR) { closesocket(s); s = INVALID_SOCKET; continue; } /* Connection process */ if (connect(s, g_ai-rai_addr, g_ai-rai_addrlen) == SOCKET_ERROR) /* Even if an error is returned, if the error code is WSAEWOULDBLOCK, */ /* the connection process is still running in the background. */ /* Wait for the window message (FD_CONNECT) to arrive. */ /* If the error message is anything else, stop the process */ /* on this g_ai. */ if (WSAGetLastError() != WSAEWOULDBLOCK) { closesocket(s); s = INVALID_SOCKET; continue; } break; } if (s == INVALID_SOCKET) { /* Connection attempts for all the items in the ADDRINFO list, g_ai0, failed */ /* There are no other targets to attempt to connect to */ return FALSE; } return TRUE; } /** Function to display a message box and bring the GUI back to its initial state when an error occurs */ void NotifyError(HWND hWnd, TCHAR *errmsg) { /* Display an error in the status bar */ SendMessage(hStatusLabel, SB_SETTEXT, (WPARAM)(255 | 0), (LPARAM)TEXT("Status: error!")); /* Display a message box that notifies the user of the error */ MessageBox(hWnd, errmsg, TEXT("Error!"), MB_OK | MB_ICONERROR); /* Bring the SEND button back to its initial state */ EnableWindow(hSendButton, TRUE); /* Bring the Recv edit box back to its initial state */ SendMessage(hRecvEdit, WM_SETTEXT, (WPARAM)0, (LPARAM)TEXT("")); /* Bring the status bar back to its initial state */ SendMessage(hStatusLabel, SB_SETTEXT, (WPARAM)(255 | 0), (LPARAM)TEXT("")); /* Reset the number of already received characters */ g_RecvLen = 0; }