logo
Published on IPv6style (http://www.ipv6style.jp)

IPv6 Programming on Windows Tutorial Part 4

By admin
作成日時 2005-12-12 00:00
Junya Kato
NTT Information Sharing Platform Laboratories



Introduction

In Part 4, I will explain how to handle sockets that use asynchronous I/O.  The TCP echo programs that I previously wrote about, both servers and clients, ran in a console.  For programmers with experience in UNIX programming, I think it was probably easy to understand.  This time, in order to create a program that behaves more like a Windows program, we will build a TCP echo client that is operated via GUI.  I will focus on the processing of window messages using the asynchronous access model, particularly for sockets.

Most of the programs that run on Windows usually have a GUI and use an event-driven model.  Suppose an application consists of a main window and a button, as shown in Figure 1.

Figure 1: Events and Window Procedure Calls
Figure 1: Events and Window Procedure Calls [0]

First, register the window procedure (callback function) for the main window that will be called when an event occurs.  When an event occurs inside the window, the window procedure is called.  We can use some GUI-related operations as examples of these events, e.g. “a button was clicked”, “the mouse pointer entered the window”, “the window was resized”, “the x button was clicked in order to shut down the application”, etc.  When an event occurs, the callback function is called asynchronously during program operation.  When this happens, a window message is sent to the window procedure in order to let it know what kind of event has occurred.  The window procedure checks the content of the window message after receiving it and decides on what action the application will take.  For example, if it receives a message that indicates “a button was clicked”, it will execute the action that was set up for when a button is clicked.


About synchronous sockets

All socket I/O that we covered up until Part 3 was based on an access model called synchronous (blocking) sockets.  Suppose that network and server load are significantly high and that the server connection takes close to 1 minute from start to finish.  This means that with synchronous sockets, you have to wait 1 minute after the completion of process 1 in Figure 2 for process 2 to start.

Figure 2: The Outline of the Process When Blocking Occurs
Figure 2: The Outline of the Process When Blocking Occurs

In a GUI program, this blocking in the socket process causes a big problem.  For example, while socket I/O is happening, it is possible for the GUI to completely freeze since other processes are not running.  For example, consider the following situation.  Suppose it takes 1 minute to connect to the server after inputting a URL in the address bar of a web browser and hitting the Enter key.  If all the GUI operations are frozen during that minute and you cannot click on the cancel button for loading or even resizing and moving the window, the program’s usability as a GUI application is significantly compromised.  So, it is necessary to multiplex the socket I/O and GUI processes to carry out the processes at the same time.


The relationship between asynchronous I/O socket access and window messages

Windows provides a socket access model that uses asynchronous I/O.  If you use this model, even when a socket function is called, blocking does not occur.  The socket function returns immediately after it is called.  In Figure 3, the connect() function, called after Process 1, returns immediately even if it is taking time to connect (in other words, even while it is in the middle of connecting).

Figure 3: The Outline of the Process using Non-Blocking Socket
Figure 3: The Outline of the Process using Non-Blocking Socket

However, if the function returns in the middle of the process, you cannot tell if the connection process succeeded or failed.  So, WinSock sends a window message that contains the result of the connection process to the application asynchronously when the connection() function completes.  By having a window procedure receive and check the message, it is possible to know the result of the connect() function.

Figure 4 shows an example of an asynchronous socket process.  This is an application that begins connecting to the server when the button is clicked.  When the button is clicked, a message indicating that “the button was clicked” is sent to the window procedure (j).  The process to begin connecting that is set up for the button is called (k).  Although the connection to the server is being processed, the connect() function immediately returns and gives control back to the GUI process (l).  Once the connection process completes, an event indicating that the process completed is fired and the window procedure is called once again (m).  As described above, GUI and socket I/O events can be handled in a completely integrated manner, and you can see that the access model using asynchronous I/O is compatible with GUI programs.

Figure 4: An Example of an Asynchronous Socket Process
Figure 4: An Example of an Asynchronous Socket Process [0]

Behavior of each function when using asynchronous sockets

The window messages shown below are generated by socket I/O when an asynchronous socket is used.  You can choose either synchronous or asynchronous depending on the kind of I/O.  For example, you can make it so that the connect() function runs asynchronously and other functions run synchronously and block.

Core design of a TCP echo client using asynchronous I/O

Figure 5: Screenshot of Asynchronous I/O TcpEchoClient
Figure 5: Screenshot of Asynchronous I/O TcpEchoClient

Figure 5 is a screenshot of the TCP echo client that we will create this time.  This application sends a character string written in the String edit box to the TCP echo server and shows the character string that was sent back (echoed back) at the bottom of the screen.  The trigger to begin sending is the SEND button.

Figure 6 shows the rough outline of the program after the SEND button has been clicked up until it shows the echoed back character string on the screen.  To keep things simple, only the connect() and recv() functions run asynchronously.

Figure 6: Rough Outline of an Asynchronous I/O TCP Echo Client
Figure 6: Rough Outline of an Asynchronous I/O TCP Echo Client [0]

The rough outline from the window message generated by the SEND button being clicked up until the communication completes is shown in (1)~(8).

  1. SEND button is clicked
  2. Execute “name resolution”.  Obtain the host name from the Server edit box and move onto executing the connection process based on the result of the name resolution 
  3. Immediately return to the GUI process without waiting for the result of the connection process
  4. When the connection process (connect() function) completes, an event occurs and an applicable process in the window procedure will be called
  5. Execute the send process (send() function). Obtain the character string to send from the String edit box
  6. Data arrives in the receive buffer because it was echoed back from the server. An event indicating that data has arrived occurs and an applicable process in the window procedure will be called
  7. Execute the receive process (recv() function) and extract the character string from the receive buffer
  8. Render the received character string on the screen and returns to the GUI process


Structure of window messages

Now I will explain the structure of window messages generated by socket I/O operations.  Window messages can take two parameters besides the message classification.  Figure 7 shows the parameter structure of window messages generated by socket I/O operations.  When a socket I/O operation occurs, a WM_SOCKET (see endnote 2 [0]) message is generated.

Figure 7: Parameter Structure of Window Messages Generated by Socket I/O Operations
Figure 7: Parameter Structure of Window Messages Generated by Socket I/O Operations [0]

The information on what kind of I/O occurred in which socket and if it was a success or failure will be sent using two parameters (wParam, lParam) at this time.  The data structures for wParam and lParam are different depending on the kind of window message.  When the message is about socket I/O, you can extract the applicable socket using

SOCKET s = (SOCKET)wParam;

and extract the following:

WSAGETSELECTERROR(lParam) macro to get error status 
WSAGETSELECTEVENT(lParam) macro to get message content 


About the source code

Source code of the asynchronous I/O TCP echo client and how to compile it

Listing 1 shows the entire source code of the asynchronous I/O TCP echo client; Figure 8 shows how to compile it.  Since this is a program that uses a GUI, user32.lib and comctl32.lib are linked as import libraries.  The latter, common controls, is used to display the status bar.

Window initialization during application startup

• Lines 85-103.  On Windows, application execution starts with the WinMain() function.  In this function, WinSock initialization (line 94: WSAStartup() function), window class registration (line 98: InitApplication() function), and window creation and display (line 102: InitInstance() function) are performed.

080:/** Entry point of Windows program */
081:/*
082: * Enter the main loop in order to initialize WinSock
083: * and process messages after GUI initialization
084: */
085:int WINAPI WinMain(HINSTANCE hInstance,
086:                   HINSTANCE hPrevInstance,
087:                   LPSTR lpCmdLine,
088:                   int nCmdShow)
089:{
090:    WSADATA wsaData;
091:    MSG msg;
092:
093:    /* Initialize WinSock */
094:    if (WSAStartup(MAKEWORD(2, 2), &wsaData))
095:        return FALSE;
096:
097:    /* Register Window Class */
098:    if (!InitApplication(hInstance)) 
099:        return FALSE;
100:   
101:    /* Create GUI window and display */
102:    if (!InitInstance(hInstance, nCmdShow))
103:        return FALSE;
 .
 .
112:}


• Lines 116-133.  This is the body of the InitApplication() function, which is called from the WinMain() function.  In this function, the window class is registered.  A window class defines the window’s properties; it does things like defining the name of the application and specifying the icon and mouse cursor.  Also, the important point is that the action to take when an event occurs is registered in Windows.  In line 122, the lpfnWndProc pointer is set to the WndProc() function, which is the main body of the window procedure.  Once the registration of the window class is completed using RegisterClass() function in line 132, the WndProc() function will be called asynchronously when an event occurs.

115:/** Process to register the window class */
116:BOOL InitApplication(HANDLE hInstance)
117:{
118:    WNDCLASS wc;
119:
120:    wc.style = CS_HREDRAW | CS_VREDRAW;
121:    /* Register the window procedure */
122:    wc.lpfnWndProc = (WNDPROC)WndProc;
123:    wc.cbClsExtra = 0;
124:    wc.cbWndExtra = 0;
125:    wc.hInstance = hInstance;
126:    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
127:    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
128:    wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
129:    wc.lpszMenuName =  NULL;
130:    wc.lpszClassName = TEXT("Async I/O TcpEchoClient");
131:
132:    return RegisterClass(&wc);
133:}


• Lines137-234.  A window is created using a series of CreateWindow() functions and is displayed.  The GUI shown in Figure 5 is created here.

136:/** Process to create the window and display */
137:BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
138:{
139:    HWND hWnd;
140:
141:    g_hInst = hInstance; // Store the instance in a global variable.
142:
143:    /* Create the parent window */
144:    hWnd = CreateWindow(TEXT("Async I/O TcpEchoClient"), ...);
 .
157:    /* Create the controls within the parent window */
158:    hServerLabel = CreateWindow(TEXT("STATIC"), TEXT("Server"), ...);
166:    hServerEdit = CreateWindowEx(WS_EX_CLIENTEDGE, ...);
176:    hSendButton = CreateWindow(TEXT("BUTTON"), TEXT("SEND"), ...);
185:    hStringLabel = CreateWindow(TEXT("STATIC"), TEXT("String"), ...);
193:    hStringEdit = CreateWindowEx(WS_EX_CLIENTEDGE, ...);
203:    hRecvEdit = CreateWindowEx(WS_EX_CLIENTEDGE, ...);
217:    hStatusLabel = CreateWindowEx(0, ...);
 .
 .
 .
228:    SetFocus(hServerEdit);
229:
230:    ShowWindow(hWnd, nCmdShow);  /* Display the window */
231:    UpdateWindow(hWnd);          /* Update the window for the first time */
232:
233:    return TRUE;
234:}


• Lines 106-109.  The while() loop is an event processing loop.  Once entering this loop, the program will wait for events such as user input and socket I/O.  Events are occurring at all times even when programmers don’t realize it, e.g. the application receives the focus, is iconized, etc.  A default action is called internally to appropriately handle the event.  The actions that the programmer needs to process next are the clicking of the SEND button and socket I/O.  For these actions, the programmer needs to add code within the window procedure to process the action.

085:int WINAPI WinMain(HINSTANCE hInstance,
086:                   HINSTANCE hPrevInstance,
087:                   LPSTR lpCmdLine,
088:                   int nCmdShow)
089:{
 .
 .
 .
105:    /* Process window messages */
106:    while (GetMessage(&msg, NULL, 0, 0)) {
107:        TranslateMessage(&msg);
108:        DispatchMessage(&msg);
109:    }
110:
111:    return (int)msg.wParam;
112:}


Processing the window message that is generated when the SEND button is clicked

• Lines 242-254.  Next, suppose the user clicked on the SEND button.  Suppose also that the server name is written in the Server edit box and a character string to be sent in is written in the String edit box.  When the SEND button is clicked, an event (WM_COMMAND) occurs and the window procedure, WndProc(), is called.  If you check the window message contents (uMsg) in line 251, you will see it is a button click (WM_COMMAND).  So, the process will move forward to the fork in line 252, and the name of the button is specified in lines 253-254.

237:/** Main body of the window procedure */
238:/*
239: * When a button click or socket I/O event occurs,
240: * a window message is sent via the uMsg variable.
241: */
242:LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
243:{
 .
 .
250:    /* Determine the action to take depending on what window message was received */
251:    switch (uMsg) {
252:    case WM_COMMAND:
253:        switch (LOWORD(wParam)) {
254:        case ID_SEND:


•Lines 256-261.  First, handle the GUI right after a button is clicked.  Disable the SEND button so that the button cannot be clicked again while communication is in progress.  Also, display the communication status that indicates “resolving server name” in the status bar at the bottom of the GUI.

255:            /* Disable the SEND button */
256:            EnableWindow(hSendButton, FALSE);
257:
258:            /* Display the “resolving server name” status in the status bar */
259:            SendMessage(hStatusLabel, SB_SETTEXT,
260:                        (WPARAM)(255 | 0),
261:                        (LPARAM)TEXT("Status: resolving servername"));


• Lines 264-269.  Extract the character string containing the server name from the Server edit box and make it possible to reference it using the buf variable.  If the Server edit box is empty, an error is displayed and the program returns to its initial state.

263:            /* Extract the server name from the Server edit box */
264:            SendMessage(hServerEdit, WM_GETTEXT,
265:                        (WPARAM)sizeof(buf), (LPARAM)buf);
266:            if (strlen(buf) == 0) {
267:                NotifyError(hWnd, TEXT("invalid server name or address"));
268:                break;
269:            }


• Lines 272-278.  Resolve the server name (buf).  If it cannot be resolved, an error is displayed and the program returns to its initial state.  If the getaddrinfo() function is successful, the ADDRINFO structure list is set in the g_ai vaiable.

271:            /* Resolve the server name */
272:            memset(&hints, 0, sizeof(hints));
273:            hints.ai_family = AF_UNSPEC;
274:            hints.ai_socktype = SOCK_STREAM;
275:            if (getaddrinfo(buf, "echo", &hints, &g_ai0)) {
276:                NotifyError(hWnd, TEXT("server not found"));
277:                break;
278:            }


Connecting using the connect() function and processing window messages

•Lines 281-283.  If name resolution succeeded, the program moves onto connecting.  First, display the “connecting” status, which indicates the connect() function is running, in the status bar at the bottom of the GUI.

280:            /* Display the “connecting” status in the status bar */
281:            SendMessage(hStatusLabel, SB_SETTEXT,
282:                        (WPARAM)(255 | 0),
283:                        (LPARAM)TEXT("Status: connecting server"));


• Lines 286-293.  Right after the ConnectServer() function is called, immediately leave the window procedure and return to the GUI process using the break statement in line 293.  Since this function leaves the connection process to WinSock and returns immediately, no blocking occurs.  I will discuss the contents of the ConnectServer() function later.

Now, the g_ai variable is treated as the item of the ADDRINFO list that the program is currently attempting to connect to.  For its initial value, set the top item (g_ai0) of the list that was returned by the getaddrinfo() function.  After this, connection attempts will be processed within the ConnectServer() function, starting with the top item of the ADDRINFO list.  The ConnectServer() function returns a BOOL value of either TRUE or FALSE.  When TRUE, it means either that the connect() process hasn’t completed or that there are ADDRINFO structure items left after the current g_ai.  In other words, if TRUE is returned, it is either waiting for the result of the connection process or there are targets left to attempt to connect to, so it is not treated as a fatal error.  On the other hand, if FALSE is returned, there is no ADDRINFO structure item left to attempt to connect to after g_ai.  This happens when no connection could be made, even when the fallback from IPv6 to IPv4 was attempted on all the targets.  When FALSE is returned, it is treated as a fatal error and the program returns to its initial state.

285:            /* Begin connecting */
286:            g_ai = g_ai0;
287:            if (ConnectServer(hWnd) == FALSE) {
288:                NotifyError(hWnd, TEXT("connect error"));
289:                freeaddrinfo(g_ai0);
290:                break;
291:            }
292:
293:            break;


•Lines 433-478.  Now I’ll explain the contents of the ConnectServer() function.  The condition in line 437 is checking if the current ADDRINFO list has reached the end and there are no more items left to process.  In this case, the connection process cannot continue any longer; the function considers it a fatal error and returns FALSE.

423:/** Process to connect to the server */
424:/* Return value
425: *   TRUE:  When connection is successful or when the result is not confirmed
426:            since the connection is still in progress.  In the latter case, 
427:            confirm the result by checking the result of the window message 
428:            (FD_CONNECT) within the window procedure, WndProc.
429:     FALSE: Failed in attempts to connect to all the items in the ADDRINFO list g_ai0.
430:            If FALSE is returned, it means that the fallback from IPv6 to IPv4
431:            completely failed as well.
432: */
433:BOOL ConnectServer(HWND hWnd)
434:{
435:    SOCKET s;
436:    
437:    if (g_ai == NULL)
438:        return FALSE;
 .
 .
 .
478:}


•Lines 440-453.  This for loop is basically the same as the TCP echo client structure that I introduced in Part 2.  Attempting to connect starting from the top of the ADDRINFO list (g_ai0) is basically no different.  In order to use asynchronous I/O, the socket created in lines 448-449 is switched to asynchronous mode.  The WSAAsyncSelect() function specifies which I/O operations within socket s will be configured asynchronous.  In the example in lines 448-449, since WM_SOCKET is specified in the third argument as the message name and FD_CONNECT | FD_READ is specified in the fourth argument, when events 1 and 2 below occur, a window message (WM_SOCKET) is generated.

  1. when the connect() process completes
  2. when data arrives in the receive buffer of the protocol stack

As for WM_SOCKET, the programmer defines it based on the user definable window message (line 47).

046:/** Definition of window messages related to socket I/O */
047:#define WM_SOCKET        (WM_USER+1)
 .
 .
 .
440:    for ( ; g_ai; g_ai = g_ai->ai_next) {
441:
442:        /* Create a socket */
443:        s = socket(g_ai->ai_family, g_ai->ai_socktype, g_ai->ai_protocol);
444:        if (s == INVALID_SOCKET)
445:            continue;
446:
447:        /* Make the socket asynchronous */
448:        if (WSAAsyncSelect(s, hWnd, WM_SOCKET,
449:                           FD_CONNECT | FD_READ) == SOCKET_ERROR) {
450:            closesocket(s);
451:            s = INVALID_SOCKET;
452:            continue;
453:        }

•Lines 456-468.  Processing the connect() function is also an important part of asynchronous I/O.  At a glance, it looks mostly the same as the process for synchronous socket.  However, no blocking occurs in the connect() function; it immediately returns even when it is in the middle of connecting.  Even if the connect() function returns an error (SOCKET_ERROR) as the return value, the program checks the details of the error using the WSAGetLastError() function (see “Differences between WinSock and Berkeley sockets” [0]).  If the error is WSAEWOULDBLOCK, it means that the connect() function is still running in the background.  Since the result of the connection process is unknown at this time, do not treat it as an error.  Return from the ConnectServer() function without doing anything, and then wait for the result of the connect() function (a window message from WinSock (WM_SOCKET) ).  Once the ConnectServer() function returns, the program immediately leaves the window procedure (WndProc() function) as well, so it will immediately return to the event processing loop in lines 106-109.

455:        /* Connection process */
456:        if (connect(s, g_ai->ai_addr, g_ai->ai_addrlen) == SOCKET_ERROR)
457:            /* Even if an error is returned, if the error code is WSAEWOULDBLOCK, */
458:            /* the connection process is still running in the background. */
459:            /* Wait for the window message (FD_CONNECT) to arrive. */
460:            /* If the error message is anything else, stop the process */
461:            /* on this g_ai. */
462:            if (WSAGetLastError() != WSAEWOULDBLOCK) {
463:                closesocket(s);
464:                s = INVALID_SOCKET;
465:                continue;
466:            }
467:
468:        break;


Processing the window message, WM_SOCKET (FD_CONNECT), which sends notification when the connection has been made

When the connect() process is completed, a WM_SOCKET window message is generated.  It takes two parameters and they are represented as the wParam and lParam variables.  We will take a look at how they are processed by the window procedure (WndProc() function).

• Lines 300-317.  Since a socket descriptor is passed into the wParam variable, you can extract the descriptor by casting it to the SOCKET type as shown in line 303.  Also, in lines 309-310, the program determines if the socket I/O operation was a success or failure.  If the value of the WSAGETSELECTERROR(lParam) macro is other than 0, it means the I/O operation failed.  The WSAGETSELECTEVENT(lParam) macro can extract the type of I/O operation.  Normally, when an error occurs in the socket I/O, the process is stopped and the program returns to its initial state.  However, the connect() function continues processing even when an error occurs (line 310).  This is so that we can determine the result of the connection process inside of the fork after line 316 in order to fall back to IPv4 (if it cannot connect with IPv6, then it will try connecting with IPv4).

242:LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
243:{
 .
251:    switch (uMsg) {
 .
300:    case WM_SOCKET:
301:        /** An event related to socket I/O occurred*/
302:
303:        /* Extract the socket descriptor */
304:        s = (SOCKET)wParam;
305:
306:        /* Check the process result of socket I/O operations other than the connect() function */
307:        /* When WSAGETSELECTERROR(lParam) is not 0 (failed), */
308:        /* return to the initial state. */
309:        if (   WSAGETSELECTERROR(lParam) != 0
310:            && WSAGETSELECTEVENT(lParam) != FD_CONNECT) {
311:            closesocket(s);
312:            NotifyError(hWnd, TEXT("socket error"));
313:            break;                       
314:        }
315:
316:        switch (WSAGETSELECTEVENT(lParam)) {
317:        case FD_CONNECT:


•Lines 320-336.  Now I will explain the block that processes the connect() function.  The block between lines 310 and 335 is the process that determines if the result of the connect() function is a success or failure.  If it is determined to be a failure in line 320, the fallback process is performed.  In line 324, close the socket that already failed to connect, then change the target of connection attempts to the next item of the ADDRINFO list in lines 327-333 and call the ConnectServer() function again.  Since the ConnectServer() function does not block here again, leave the window procedure using the break statement in line 335, and again waits for a WM_SOCKET window message (FD_CONNECT type).

318:            /** Received notice that connection process completed. */
319:
320:            if (WSAGETSELECTERROR(lParam) != 0) {
321:                /** Since the connect() function failed, close the socket and reattempt. */
322:
323:                /* Close the socket that could not connect. */
324:                closesocket(s);
325:
326:                /* Reattempt connection and wait for a window message (FD_CONNECT) again. */
327:                g_ai = g_ai->ai_next;
328:                if (ConnectServer(hWnd) == FALSE) {
329:                    closesocket(s);
330:                    NotifyError(hWnd, TEXT("connect error"));
331:                    freeaddrinfo(g_ai0);
332:                    break;
333:                }
334:
335:                break;
336:            }


•Lines 338-342.  If the process reaches line 338, it means that the connect() process was successful and that the program connected to the server.  Since the connection process has already finished, free the ADDRINFO list in line 342.

338:            /** If the process has reached this line, */
339:            /** the connect() function was successful. */
340:
341:            /* Free the ADDRINFO list. */
342:            freeaddrinfo(g_ai0);


Sending a character string to the server

•Lines 345-356.  We now move onto the process of sending a character string to the server.  First, change the status bar status, extract the character string to send from the String edit box and store it in the buf variable.

344:            /* Display “sending” status in the status bar. */
345:            SendMessage(hStatusLabel, SB_SETTEXT,
346:                        (WPARAM)(255 | 0),
347:                        (LPARAM)TEXT("Status: sending string"));
348:
349:            /* Obtain the character string from the String edit box to send to the server. */
350:            g_StringLen = SendMessage(hStringEdit, WM_GETTEXT,
351:                                      (WPARAM)sizeof(buf), (LPARAM)buf);
352:            if (g_StringLen == 0) {
353:                closesocket(s);
354:                NotifyError(hWnd, TEXT("empty string"));
355:                break;
356:            }


• Lines 359-369.  Send a character string using the send() function (to be exact, copy data to the send buffer in the protocol stack).  Since the send process is not performed in asynchronous mode, no window message is generated.  Although this very rarely happens in this sample program, if there is no available space in the send buffer (the server does not accept data due to overload), blocking occurs in the send() function.  Once the program finishes sending the character string stored in the buf variable, it immediately leaves the window procedure (WndProc() function) using the break statement in line 369, returns to the event processing loop in lines 106-109, and waits for the window message.

358:            /* Send the character string obtained from the edit box to the server. */
359:            for (len = 0; len < g_StringLen; ) {
360:                n = send(s, buf + len, g_StringLen - len, 0);
361:                if (n == SOCKET_ERROR) {
362:                    closesocket(s);
363:                    NotifyError(hWnd, TEXT("send Error"));
364:                    break;
365:                }
366:                len += n;
367:            }
368:            
369:            break;


Receiving a character string from the server

Once the sending of the character string to the server is completed, the same character string will be echoed back from the server.  Since GUI processing also needs to be performed at all times, the client program cannot concentrate on waiting for the data to arrive.  Until the data is echoed back from the server, the program continues the event processing loop in lines 106-109 and also watches for things such as GUI actions.

•Lines 251-372.  When a character string is received from the server and the protocol stack puts data in the receive buffer, WinSock generates a window message WM_SOCKET (FD_READ type) that indicates this.  When the window procedure (WndProc() function) receives it, it switches to the process below line 372 through the fork in line 371.

251
:    switch (uMsg) {
300:    case WM_SOCKET:
 .
316:        switch (WSAGETSELECTEVENT(lParam)) {
 .
371:        case FD_READ:
372:            /** Received notification that there is data in the receive buffer. */
 .
 .


• Lines 375-377.  Update the status bar to display the status of receiving characters from the server.

374:            /* Display the “receiving” status in the status bar. */
375:            SendMessage(hStatusLabel, SB_SETTEXT,
376:                        (WPARAM)(255 | 0),
377:                        (LPARAM)TEXT("Status: reciving string"));


• Lines 380-389.  Since data has accumulated in the receive buffer of the protocol stack, retrieve the data by using the recv() function.  What you need to be careful about here is that if there is only 50 bytes of data in the receive buffer when it tries to receive 100 bytes of data, the recv() function reads only 50 bytes.  When the other 50 bytes of data arrives in the receive buffer, a WM_SOCKET window message (FD_READ type) is generated again.  Once the window message is received, it is necessary to retrieve of the rest of the data by calling the recv() function again.  The g_RecvLen variable is a variable that remembers the total length of the character string received from the server; the g_String variable is the length of the character string that is expected to be returned by the server.  If the expected number of characters are not received, the program returns from the window procedure and waits for the WM_SOCKET message (FD_READ type) (lines 388-389).

379:            /* Receive data from the receive buffer. */
380:            n = recv(s, buf + g_RecvLen, g_StringLen - g_RecvLen, 0);
381:            if (n == 0 || n == SOCKET_ERROR) {
382:                closesocket(s);
383:                NotifyError(hWnd, TEXT("recv error"));
384:            }
385:
386:            /* Check if all the data has arrived. */
387:            g_RecvLen += n;
388:            if (g_RecvLen < g_StringLen)
389:                return 0;


• Lines 392-403.  Display the received character string on the screen and end the communication.  This cancels the asynchronous setup and closes the socket (line 397).  At the end, the GUI and the received data length counter (g_RecvLen) are put back to their initial state, and the program returns to the event loop in lines 106-109.

391:            /* Display the received character string on the screen. */
392:            buf[g_RecvLen] = '\0';
393:            SendMessage(hRecvEdit, WM_SETTEXT, (WPARAM)0, (LPARAM)buf);
394:
395:            /* Close the socket and end the communication. */
396:            WSAAsyncSelect(s, hWnd, WM_SOCKET, 0);
397:            closesocket(s);
398:            EnableWindow(hSendButton, TRUE);
399:            SendMessage(hStatusLabel, SB_SETTEXT,
400:                        (WPARAM)(255 | 0), (LPARAM)TEXT(""));
401:            g_RecvLen = 0;
402:
403:            break;


Necessity of making the name resolution process asynchronous

If you ran the sample program, you may have noticed that the GUI freezes for a moment during the name resolution that is performed right after the SEND button is clicked.  The GUI does not permit any operations while the status bar displays “Status: resolving servername”.  This is because the entire application is blocked while the getaddrinfo() function is being called and GUI-related processes cannot be performed.  In the WinSock API, APIs that resolve names without blocking, such as the WSAAsyncGetHostByName() function, are provided by default and it is a rule that any program that uses asynchronous sockets also uses asynchronous API for name resolution.  However, since there is no IPv6-ready API, you need to write your own from scratch using a synchronous name resolution function and threads.  Although I did not explain it this time, I would like to explain how to create asynchronous getaddrinfo()/getnameinfo() functions on another occasion.

Conclusion

In Part 4, I focused on multiplexing models using asynchronous I/O.  Since the code for the GUI part of the TCP echo client that I introduced this time became large, at first glance it may have appeared to be a complicated program.  However, if you look only at the socket I/O, it is highly compatible with a GUI program because the asynchronous I/O access model and window messages generated by the GUI can be handled in an integrated manner.  Also, although asynchronous I/O is specific to Windows, there are not any expressions that are dependent on a specific protocol at all.  It has a structure that can support any protocol, from IPv6 and IPv4 to ones that may emerge in the future, by just adding a fallback process around the connect() function.  In the next article, I will explain how to create a TCP echo server using asynchronous I/O.

Listing 1:tcp-echo-client-asyncio.c [0]
001:/* 
002: * how to compile:
003: * C:???rcl tc-pecho-client-asyncio.c user32.lib comctl32.lib ws2_32.lib
004: */
005:
006:#define STRICT
007:#include ‹winsock2.h›

008:#include ‹ws2tcpip.h›
009:#include ‹windows.h›
010:#include ‹commctrl.h›
011:
012:/** GUI-related definitions */
013:/*
014: * GUI setup
015: *
016: *             +----------------------------------------------+
017: * |? Aync I/O TcpEchoClient ?Q? ×|
018: * +----------------------------------------------+
019: * hServerLabel→Server [________hServerEdit___________] |
020: * hStringLabel→String [________hStringEdit___________][SEND]←hSendButton
021: * |----------------------------------------------|
022: * |[ ]↑|
023: * |[ hRecvEdit ]↓|
024: * +----------------------------------------------+
025: * | hStatusLabel |
026: * +----------------------------------------------+ 027: */ 028: 029:HINSTANCE g_hInst; /* Current interface*/ 030: 031:/* Window handles for GUI components */ 032:HWND hServerLabel; /* Server label*/ 033:HWND hServerEdit; /* Edit box to enter server name */ 034:HWND hStringLabel; /* String label */ 035:HWND hStringEdit; /* Edit box to enter the character string that will be sent to the server */ 036:HWND hSendButton; /* Send button to send the character string */ 037:HWND hRecvEdit; /* Edit box that displays the received character string from the server */ 038:HWND hStatusLabel; /* Status bar that displays the client operation status */ 039: 040:#define ID_EDIT_SERVER 1000 041:#define ID_SEND 1001 042:#define ID_EDIT_VIEW 1002 043:#define ID_STATUS 1003 044:#define ID_STATIC 1004 045: 046:/** Definition of window messages related to socket I/O */ 047:#define WM_SOCKET (WM_USER+1) 048: 049: 050:/** Store the result of name resolution */ 051:LPADDRINFO g_ai0; /* Store the top of the list returned from the getaddrinfo() function */ 052:LPADDRINFO g_ai; /* Indicates the ADDRINFO item that is currently in the process of connecting */ 053:/* 054: * 1 2 3 055: * g_ai0 --?r ADDRINFO0 --?r ADDRINFO --?r ADDRINFO --?r NULL 056: * ^ ^ 057: * | ......... | 058: * g_ai -------------------+ - - - - - - + 059: * When the second one fails, point to the third one 060: * 061: * g_ai is the ADDRINFO item that is currently attempting to connect 062: * The list item moves towards NULL each time the connection is attempted 063: * Once a connection is made, the rest of the ADDRINFO items are not referenced 064: */ 065: 066: 067:/** Send buffer counters for send()/recv() 068:int g_StringLen; /* The length of the character string that will be sent to, and received from, the server */ 069:int g_RecvLen = 0; /* The length of the character string already received from the server */ 070: 071: 072:/** Declaration of function prototype */ 073:BOOL InitApplication(HANDLE hInstance); 074:BOOL InitInstance(HINSTANCE hInstance, int nCmdShow); 075:LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 076:BOOL ConnectServer(HWND hWnd); 077:void NotifyError(HWND hWnd, TCHAR *errmsg); 078: 079: 080:/** Entry point of Windows program */ 081:/* 082: * Enter the main loop in order to initialize WinSock 083: * and process messages after GUI initialization 084: */ 085:int WINAPI WinMain(HINSTANCE hInstance, 086: HINSTANCE hPrevInstance, 087: LPSTR lpCmdLine, 088: int nCmdShow) 089:{ 090: WSADATA wsaData; 091: MSG msg; 092: 093: /* Initialize WinSock */ 094: if (WSAStartup(MAKEWORD(2, 2), &wsaData)) 095: return FALSE; 096: 097: /* Register Window Class */ 098: if (!InitApplication(hInstance)) 099: return FALSE; 100: 101: /* Create GUI window and display */ 102: if (!InitInstance(hInstance, nCmdShow)) 103: return FALSE; 104: 105: /* Process window messages */ 106: while (GetMessage(&msg, NULL, 0, 0)) { 107: TranslateMessage(&msg); 108: DispatchMessage(&msg); 109: } 110: 111: return (int)msg.wParam; 112:} 113: 114: 115:/** Process to register the window class */ 116:BOOL InitApplication(HANDLE hInstance) 117:{ 118: WNDCLASS wc; 119: 120: wc.style = CS_HREDRAW | CS_VREDRAW; 121: /* Register the window procedure */ 122: wc.lpfnWndProc = (WNDPROC)WndProc; 123: wc.cbClsExtra = 0; 124: wc.cbWndExtra = 0; 125: wc.hInstance = hInstance; 126: wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); 127: wc.hCursor = LoadCursor(NULL, IDC_ARROW); 128: wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW; 129: wc.lpszMenuName = NULL; 130: wc.lpszClassName = TEXT("Async I/O TcpEchoClient"); 131: 132: return RegisterClass(&wc); 133:} 134: 135: 136:/** Process to create the window and display */ 137:BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) 138:{ 139: HWND hWnd; 140: 141: g_hInst = hInstance; // Store the instance in a global variable. 142: 143: /* Create the parent window */ 144: hWnd = CreateWindow(TEXT("Async I/O TcpEchoClient"), 145: TEXT("Async I/O TcpEchoClient"), 146: WS_OVERLAPPEDWINDOW | WS_VISIBLE, 147: CW_USEDEFAULT, 0, 148: 320, 160, 149: NULL, 150: NULL, 151: g_hInst, 152: NULL); 153: 154: if (!hWnd) 155: return FALSE; 156: 157: /* Create the controls within the parent window */ 158: hServerLabel = CreateWindow(TEXT("STATIC"), TEXT("Server"), 159: WS_CHILD | WS_VISIBLE | SS_CENTER, 160: 5, 5, 161: 60, 25, 162: hWnd, 163: (HMENU)ID_STATIC, 164: g_hInst, 165: NULL); 166: hServerEdit = CreateWindowEx(WS_EX_CLIENTEDGE, 167: TEXT("EDIT"), NULL, 168: WS_CHILD | WS_VISIBLE | WS_TABSTOP | 169: ES_AUTOHSCROLL, 170: 65, 5, 171: 200, 25, 172: hWnd, 173: (HMENU)ID_EDIT_SERVER, 174: g_hInst, 175: NULL); 176: hSendButton = CreateWindow(TEXT("BUTTON"), TEXT("SEND"), 177: WS_CHILD | WS_VISIBLE | WS_TABSTOP | 178: BS_PUSHBUTTON, 179: 265, 30, 180: 50, 25, 181: hWnd, 182: (HMENU)ID_SEND, 183: g_hInst, 184: NULL); 185: hStringLabel = CreateWindow(TEXT("STATIC"), TEXT("String"), 186: WS_CHILD | WS_VISIBLE | SS_CENTER, 187: 5, 30, 188: 60, 25, 189: hWnd, 190: (HMENU)ID_STATIC, 191: g_hInst, 192: NULL); 193: hStringEdit = CreateWindowEx(WS_EX_CLIENTEDGE, 194: TEXT("EDIT"), NULL, 195: WS_CHILD | WS_VISIBLE | WS_TABSTOP | 196: ES_AUTOHSCROLL, 197: 65, 30, 198: 200, 25, 199: hWnd, 200: (HMENU)ID_EDIT_SERVER, 201: g_hInst, 202: NULL); 203: hRecvEdit = CreateWindowEx(WS_EX_CLIENTEDGE, 204: TEXT("EDIT"), NULL, 205: WS_CHILD | WS_VISIBLE | WS_VSCROLL | 206: WS_TABSTOP | ES_NOHIDESEL | 207: ES_MULTILINE | ES_LEFT | ES_READONLY, 208: 0, 55, 209: 315, 50, 210: hWnd, 211: (HMENU)ID_EDIT_VIEW, 212: g_hInst, 213: NULL); 214: 215: /* Create a status bar (requires common controls) */ 216: InitCommonControls(); 217: hStatusLabel = CreateWindowEx(0, 218: STATUSCLASSNAME, NULL, 219: WS_CHILD | SBARS_SIZEGRIP | CCS_BOTTOM | 220: WS_VISIBLE, 221: 0, 105, 222: 60, 25, 223: hWnd, 224: (HMENU)ID_STATUS, 225: g_hInst, 226: NULL); 227: 228: SetFocus(hServerEdit); 229: 230: ShowWindow(hWnd, nCmdShow); /* Display the window */ 231: UpdateWindow(hWnd); /* Update the window for the first time */ 232: 233: return TRUE; 234:} 235: 236: 237:/** Main body of the window procedure */ 238:/* 239: * When a button click or socket I/O event occurs, 240: * a window message is sent via the uMsg variable. 241: */ 242:LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 243:{ 244: char buf[BUFSIZ]; 245: ADDRINFO hints; 246: SOCKET s; 247: int len; 248: int n; 249: 250: /* Determine the action to take depending on what window message was received */ 251: switch (uMsg) { 252: case WM_COMMAND: 253: switch (LOWORD(wParam)) { 254: case ID_SEND: 255: /* Disable the SEND button */ 256: EnableWindow(hSendButton, FALSE); 257: 258: /* Display the “resolving server name” status in the status bar */ 259: SendMessage(hStatusLabel, SB_SETTEXT, 260: (WPARAM)(255 | 0), 261: (LPARAM)TEXT("Status: resolving servername")); 262: 263: /* Extract the server name from the Server edit box */ 264: SendMessage(hServerEdit, WM_GETTEXT, 265: (WPARAM)sizeof(buf), (LPARAM)buf); 266: if (strlen(buf) == 0) { 267: NotifyError(hWnd, TEXT("invalid server name or address")); 268: break; 269: } 270: 271: /* Resolve the server name */ 272: memset(&hints, 0, sizeof(hints)); 273: hints.ai_family = AF_UNSPEC; 274: hints.ai_socktype = SOCK_STREAM; 275: if (getaddrinfo(buf, "echo", &hints, &g_ai0)) { 276: NotifyError(hWnd, TEXT("server not found")); 277: break; 278: } 279: 280: /* Display the “connecting” status in the status bar */ 281: SendMessage(hStatusLabel, SB_SETTEXT, 282: (WPARAM)(255 | 0), 283: (LPARAM)TEXT("Status: connecting server")); 284: 285: /* Begin connecting */ 286: g_ai = g_ai0; 287: if (ConnectServer(hWnd) == FALSE) { 288: NotifyError(hWnd, TEXT("connect error")); 289: freeaddrinfo(g_ai0); 290: break; 291: } 292: 293: break; 294: 295: default: 296: break; 297: } 298: break; 299: 300: case WM_SOCKET: 301: /** An event related to socket I/O occurred*/ 302: 303: /* Extract the socket descriptor */ 304: s = (SOCKET)wParam; 305: 306: /* Check the process result of socket I/O operations other than the connect() function */ 307: /* When WSAGETSELECTERROR(lParam) is not 0 (failed), */ 308: /* return to the initial state. */ 309: if ( WSAGETSELECTERROR(lParam) != 0 310: && WSAGETSELECTEVENT(lParam) != FD_CONNECT) { 311: closesocket(s); 312: NotifyError(hWnd, TEXT("socket error")); 313: break; 314: } 315: 316: switch (WSAGETSELECTEVENT(lParam)) { 317: case FD_CONNECT: 318: /** Received notice that connection process completed. */ 319: 320: if (WSAGETSELECTERROR(lParam) != 0) { 321: /** Since the connect() function failed, close the socket and reattempt. */ 322: 323: /* Close the socket that could not connect. */ 324: closesocket(s); 325: 326: /* Reattempt connection and wait for a window message (FD_CONNECT) again. */ 327: g_ai = g_ai-?rai_next; 328: if (ConnectServer(hWnd) == FALSE) { 329: closesocket(s); 330: NotifyError(hWnd, TEXT("connect error")); 331: freeaddrinfo(g_ai0); 332: break; 333: } 334: 335: break; 336: } 337: 338: /** If the process has reached this line, */ 339: /** the connect() function was successful. */ 340: 341: /* Free the ADDRINFO list. */ 342: freeaddrinfo(g_ai0); 343: 344: /* Display “sending” status in the status bar. */ 345: SendMessage(hStatusLabel, SB_SETTEXT, 346: (WPARAM)(255 | 0), 347: (LPARAM)TEXT("Status: sending string")); 348: 349: /* Obtain the character string from the String edit box to send to the server. */ 350: g_StringLen = SendMessage(hStringEdit, WM_GETTEXT, 351: (WPARAM)sizeof(buf), (LPARAM)buf); 352: if (g_StringLen == 0) { 353: closesocket(s); 354: NotifyError(hWnd, TEXT("empty string")); 355: break; 356: } 357: 358: /* Send the character string obtained from the edit box to the server. */ 359: for (len = 0; len ?q g_StringLen; ) { 360: n = send(s, buf + len, g_StringLen - len, 0); 361: if (n == SOCKET_ERROR) { 362: closesocket(s); 363: NotifyError(hWnd, TEXT("send Error")); 364: break; 365: } 366: len += n; 367: } 368: 369: break; 370: 371: case FD_READ: 372: /** Received notification that there is data in the receive buffer. */ 373: 374: /* Display the “receiving” status in the status bar. */ 375: SendMessage(hStatusLabel, SB_SETTEXT, 376: (WPARAM)(255 | 0), 377: (LPARAM)TEXT("Status: reciving string")); 378: 379: /* Receive data from the receive buffer. */ 380: n = recv(s, buf + g_RecvLen, g_StringLen - g_RecvLen, 0); 381: if (n == 0 || n == SOCKET_ERROR) { 382: closesocket(s); 383: NotifyError(hWnd, TEXT("recv error")); 384: } 385: 386: /* Check if all the data has arrived. */ 387: g_RecvLen += n; 388: if (g_RecvLen ?q g_StringLen) 389: return 0; 390: 391: /* Display the received character string on the screen. */ 392: buf[g_RecvLen] = '??0'; 393: SendMessage(hRecvEdit, WM_SETTEXT, (WPARAM)0, (LPARAM)buf); 394: 395: /* Close the socket and end the communication. */ 396: WSAAsyncSelect(s, hWnd, WM_SOCKET, 0); 397: closesocket(s); 398: EnableWindow(hSendButton, TRUE); 399: SendMessage(hStatusLabel, SB_SETTEXT, 400: (WPARAM)(255 | 0), (LPARAM)TEXT("")); 401: g_RecvLen = 0; 402: 403: break; 404: 405: default: 406: break; 407: } 408: 409: break; 410: 411: case WM_DESTROY: 412: PostQuitMessage(0); 413: break; 414: 415: default: 416: return DefWindowProc(hWnd, uMsg, wParam, lParam); 417: } 418: 419: return 0; 420:} 421: 422: 423:/** Process to connect to the server */ 424:/* Return value 425: * TRUE: When connection is successful or when the result is not confirmed 426: since the connection is still in progress. In the latter case, 427: confirm the result by checking the result of the window message 428: (FD_CONNECT) within the window procedure, WndProc. 429: FALSE: Failed in attempts to connect to all the items in the ADDRINFO list g_ai0. 430: If FALSE is returned, it means that the fallback from IPv6 to IPv4 431: completely failed as well. 432: */ 433:BOOL ConnectServer(HWND hWnd) 434:{ 435: SOCKET s; 436: 437: if (g_ai == NULL) 438: return FALSE; 439: 440: for ( ; g_ai; g_ai = g_ai-?rai_next) { 441: 442: /* Create a socket */ 443: s = socket(g_ai-?rai_family, g_ai-?rai_socktype, g_ai-?rai_protocol); 444: if (s == INVALID_SOCKET) 445: continue; 446: 447: /* Make the socket asynchronous */ 448: if (WSAAsyncSelect(s, hWnd, WM_SOCKET, 449: FD_CONNECT | FD_READ) == SOCKET_ERROR) { 450: closesocket(s); 451: s = INVALID_SOCKET; 452: continue; 453: } 454: 455: /* Connection process */ 456: if (connect(s, g_ai-?rai_addr, g_ai-?rai_addrlen) == SOCKET_ERROR) 457: /* Even if an error is returned, if the error code is WSAEWOULDBLOCK, */ 458: /* the connection process is still running in the background. */ 459: /* Wait for the window message (FD_CONNECT) to arrive. */ 460: /* If the error message is anything else, stop the process */ 461: /* on this g_ai. */ 462: if (WSAGetLastError() != WSAEWOULDBLOCK) { 463: closesocket(s); 464: s = INVALID_SOCKET; 465: continue; 466: } 467: 468: break; 469: } 470: 471: if (s == INVALID_SOCKET) { 472: /* Connection attempts for all the items in the ADDRINFO list, g_ai0, failed */ 473: /* There are no other targets to attempt to connect to */ 474: return FALSE; 475: } 476: 477: return TRUE; 478:} 479: 480: 481:/** Function to display a message box and bring the GUI back to its initial state when an error occurs */ 482:void NotifyError(HWND hWnd, TCHAR *errmsg) 483:{ 484: /* Display an error in the status bar */ 485: SendMessage(hStatusLabel, SB_SETTEXT, 486: (WPARAM)(255 | 0), (LPARAM)TEXT("Status: error!")); 487: 488: /* Display a message box that notifies the user of the error */ 489: MessageBox(hWnd, errmsg, TEXT("Error!"), MB_OK | MB_ICONERROR); 490: 491: /* Bring the SEND button back to its initial state */ 492: EnableWindow(hSendButton, TRUE); 493: 494: /* Bring the Recv edit box back to its initial state */ 495: SendMessage(hRecvEdit, WM_SETTEXT, (WPARAM)0, (LPARAM)TEXT("")); 496: 497: /* Bring the status bar back to its initial state */ 498: SendMessage(hStatusLabel, SB_SETTEXT, (WPARAM)(255 | 0), (LPARAM)TEXT("")); 499: 500: /* Reset the number of already received characters */ 501: g_RecvLen = 0; 502:}

Figure 8: How to compile tcp-echo-client-asyncio.c
C:\>cl tcp-echo-client-asyncio.c user32 comctl32.lib ws2_32.lib
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.

tcp-echo-client-asyncio.c
Microsoft (R) Incremental Linker Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:tcp-echo-client-asyncio.exe
tcp-echo-client-asyncio.obj
user32.lib
comctl32.lib
ws2_32.lib


Differences between WinSock and Berkeley sockets

On many UNIX systems, when a system call fails, the value number that shows the cause of the error gets stored in the global variable errno.  The errno variables are used not only for file I/O, but also for referencing error causes for socket operations.

    ------------------------------------------------------------
    /* How to check for errors on Berkeley sockets (UNIX) */
    if ((s = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
        if (errno == EPROTONOSUPPORT)
            fprintf(stderr, "IPv6 is unsupported protocol\n");
        exit(1);
    }
    ------------------------------------------------------------

The WinSock API, on the other hand, cannot reference errno variables to get the socket operation result. Instead of referencing errno variables, it uses the WSAGetLastError() function.

    ------------------------------------------------------------
    /* How to check for errors on WinSock */
    if ((s = socket(AF_INET6, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        if (WSAGetLastError() == WSAEPROTONOSUPPORT)
            fprintf(stderr, "IPv6 is unsupported protocol\n");
        exit(1);
    }
    ------------------------------------------------------------

As for setting a value to the errno variable, use the WSASetLastError() function.

    ------------------------------------------------------------
    errno = EPROTONOSUPPORT;              /* for Berkeley sockets */
    WSASetLastError(WSAEPROTONOSUPPORT);  /* for WinSock API */
    ------------------------------------------------------------

Endnote:

1.  The send process itself is performed using the OS protocol stack, not by the application.

2.  The user defines the name of the message that gets generated for socket I/O operations.  We define it as WM_SOCKET in this sample program.




この記事のトラックバックURL

http://www.ipv6style.jp/trackback/631

Source URL:
http://www.ipv6style.jp/en/apps/20051212/index.shtml