Essentially, six types of socket I/O models are available that allow Winsock applications to manage I/O: blocking, select, WSAAsyncSelect, WSAEventSelect,overlapped, and completion port. This section explains the features of each I/O model and outlines how to use it to develop an application that can manage one or more socket requests. On the companion CD, you will find sample applications for each I/O model demonstrating how to develop a simple TCP echo server using the principles described in each model.
Note that technically speaking, there could be a straight non-blocking I/O model—that is, an application that places all sockets into non-blocking mode withioctlsocket. However, this soon becomes unmanageable because the application will spend most of its time cycling through socket handles and I/O operations until they succeed.
The blocking Model :
Most Winsock programmers begin with the blocking model because it is the easiest and most straightforward model. The Winsock samples in Chapter 1 use this model. As we have mentioned, applications following this model typically use one or two threads per socket connection for handling I/O. Each thread will then issue blocking operations, such as send and recv.
The advantage to the blocking model is its simplicity. For very simple applications and rapid prototyping, this model is very useful. The disadvantage is that it does not scale up to many connections as the creation of more threads consumes valuable system resources.
The select Model :
The select model is another I/O model widely available in Winsock. We call it the select model because it centers on using the select function to manage I/O. The design of this model originated on UNIX-based computers featuring Berkeley socket implementations. The select model was incorporated into Winsock 1.1 to allow applications that want to avoid blocking on socket calls the capability to manage multiple sockets in an organized manner. Because Winsock 1.1 is backward-compatible with Berkeley socket implementations, a Berkeley socket application that uses the select function should technically be able to run without modification.
The select function can be used to determine if there is data on a socket and if a socket can be written to. The reason for having this function is to prevent your application from blocking on an I/O bound call such as send or recv when a socket is in a blocking mode and to prevent the WSAEWOULDBLOCK error when a socket is in a non-blocking mode. The select function blocks for I/O operations until the conditions specified as parameters are met. The function prototype for select is as follows :
- int select(
- int nfds,
- fd_set FAR * readfds,
- fd_set FAR * writefds,
- fd_set FAR * exceptfds,
- const struct timeval FAR * timeout
- );
The writefds set identifies sockets in which one of the following is true:
Finally, the exceptfds set identifies sockets in which one of the following is true:
For example, when you want to test a socket for readability, you must add it to the readfds set and wait for the select function to complete. When the selectcall completes, you have to determine if your socket is still part of the readfds set. If so, the socket is readable—you can begin to retrieve data from it. Any two of the three parameters (readfds, writefds, exceptfds) can be null values (at least one must not be null), and any non-null set must contain at least one socket handle; otherwise, the select function won't have anything to wait for. The final parameter, timeout, is a pointer to a timeval structure that determines how long the select function will wait for I/O to complete. If timeout is a null pointer, select will block indefinitely until at least one descriptor meets the specified criteria. The timeval structure is defined as :
- struct timeval
- {
- long tv_sec;
- long tv_usec;
- };
Before you can begin to use select to monitor sockets, your application has to set up either one or all of the read, write, and exception fd_set structures by assigning socket handles to a set. When you assign a socket to one of the sets, you are asking select to let you know if the I/O activities just described have occurred on a socket. Winsock provides the following set of macros to manipulate and check the fd_set sets for I/O activity :
For example, if you want to find out when it is safe to read data from a socket without blocking, simply assign your socket to the fd_read set using the FD_SET macro and then call select. To test whether your socket is still part of the fd_read set, use the FD_ISSET macro. The following five steps describe the basic flow of an application that uses select with one or more socket handles :
When select returns, it modifies each of the fd_set structures by removing the socket handles that do not have pending I/O operations. This is why you should use the FD_ISSET macro as in step 4 to determine if a particular socket is part of a set. The following code sample outlines the basic steps needed to set up the select model for a single socket. Adding more sockets to this application simply involves maintaining a list or an array of additional sockets :
- SOCKET s;
- fd_set fdread;
- int ret;
- // Create a socket, and accept a connection
- // Manage I/O on the socket
- while(TRUE)
- {
- // Always clear the read set before calling
- // select()
- FD_ZERO(&fdread);
- // Add socket s to the read set
- FD_SET(s, &fdread);
- if ((ret = select(0, &fdread, NULL, NULL, NULL))
- == SOCKET_ERROR)
- {
- // Error condition
- }
- if (ret > 0)
- {
- // For this simple case, select() should return
- // the value 1. An application dealing with
- // more than one socket could get a value
- // greater than 1. At this point, your
- // application should check to see whether the
- // socket is part of a set.
- if (FD_ISSET(s, &fdread))
- {
- // A read event has occurred on socket s
- }
- }
- }
The WSAAsyncSelect Model :
Winsock provides a useful asynchronous I/O model that allows an application to receive Windows message–based notification of network events on a socket. This is accomplished by calling the WSAAsyncSelect function after creating a socket. Before we continue, however, we need to make one subtle distinction. The WSAAsyncSelect and WSAEventSelect models provide asynchronous notification of the capability to read or write data. It does not provide asynchronous data transfer like the overlapped and completion port models.
This model originally existed in Winsock 1.1 implementations to help application programmers cope with the cooperative multitasking message-based environment of 16-bit Windows platforms, such as Windows for Workgroups. Applications can still benefit from this model, especially if they manage window messages in a standard Windows procedure, usually referred to as a winproc. This model is also used by the Microsoft Foundation Class (MFC) CSocketobject.
- Message Notification
To use the WSAAsyncSelect model, your application must first create a window using the CreateWindow function and supply a window procedure (winproc) support function for it. You can also use a dialog box with a dialog procedure instead of a window because dialog boxes are windows. For our purposes, we will demonstrate this model using a simple window with a supporting window procedure. Once you have set up the window infrastructure, you can begin creating sockets and turning on window message notification by calling the WSAAsyncSelect function, which is defined as :
- int WSAAsyncSelect(
- SOCKET s,
- HWND hWnd,
- unsigned int wMsg,
- long lEvent
- );
- WSAAsyncSelect(s, hwnd, WM_SOCKET,
- FD_CONNECT │ FD_READ │ FD_WRITE │ FD_CLOSE);
When your application calls WSAAsyncSelect on a socket, the socket mode is automatically changed from blocking to the non-blocking mode that we described previously. As a result, if a Winsock I/O call such as WSARecv is called and has to wait for data, it will fail with error WSAEWOULDBLOCK. To avoid this error, applications should rely on the user-defined window message specified in the wMsg parameter of WSAAsyncSelect to indicate when network event types occur on the socket.
After your application successfully calls WSAAsyncSelect on a socket, the application begins to receive network event notification as Windows messages in the window procedure associated with the hWnd parameter window handle. A window procedure is normally defined as :
- LRESULT CALLBACK WindowProc(
- HWND hWnd,
- UINT uMsg,
- WPARAM wParam,
- LPARAM lParam
- );
When network event messages arrive at a window procedure, the application should first check the lParam high-word bits to determine whether a network error has occurred on the socket. There is a special macro, WSAGETSELECTERROR, that returns the value of the high-word bits error information. After the application has verified that no error occurred on the socket, the application should determine which network event type caused the Windows message to fire by reading the low-word bits of lParam. Another special macro, WSAGETSELECTEVENT, returns the value of the low-word portion of lParam.
The following example demonstrates how to manage window messages when using the WSAAsyncSelect I/O model. The code highlights the steps needed to develop a basic server application and removes the programming details of developing a fully featured Windows application :
- #define WM_SOCKET WM_USER + 1
- #include
- #include
- int WINAPI WinMain(HINSTANCE hInstance,
- HINSTANCE hPrevInstance, LPSTR lpCmdLine,
- int nCmdShow)
- {
- WSADATA wsd;
- SOCKET Listen;
- SOCKADDR_IN InternetAddr;
- HWND Window;
- // Create a window and assign the ServerWinProc
- // below to it
- Window = CreateWindow();
- // Start Winsock and create a socket
- WSAStartup(MAKEWORD(2,2), &wsd);
- Listen = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
- // Bind the socket to port 5150
- // and begin listening for connections
- InternetAddr.sin_family = AF_INET;
- InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
- InternetAddr.sin_port = htons(5150);
- bind(Listen, (PSOCKADDR) &InternetAddr,
- sizeof(InternetAddr));
- // Set up window message notification on
- // the new socket using the WM_SOCKET define
- // above
- WSAAsyncSelect(Listen, Window, WM_SOCKET,
- FD_ACCEPT │ FD_CLOSE);
- listen(Listen, 5);
- // Translate and dispatch window messages
- // until the application terminates
- while (1) {
- // ...
- }
- }
- BOOL CALLBACK ServerWinProc(HWND hDlg,UINT wMsg,
- WPARAM wParam, LPARAM lParam)
- {
- SOCKET Accept;
- switch(wMsg)
- {
- case WM_PAINT:
- // Process window paint messages
- break;
- case WM_SOCKET:
- // Determine whether an error occurred on the
- // socket by using the WSAGETSELECTERROR() macro
- if (WSAGETSELECTERROR(lParam))
- {
- // Display the error and close the socket
- closesocket( (SOCKET) wParam);
- break;
- }
- // Determine what event occurred on the
- // socket
- switch(WSAGETSELECTEVENT(lParam))
- {
- case FD_ACCEPT:
- // Accept an incoming connection
- Accept = accept(wParam, NULL, NULL);
- // Prepare accepted socket for read,
- // write, and close notification
- WSAAsyncSelect(Accept, hDlg, WM_SOCKET,
- FD_READ │ FD_WRITE │ FD_CLOSE);
- break;
- case FD_READ:
- // Receive data from the socket in
- // wParam
- break;
- case FD_WRITE:
- // The socket in wParam is ready
- // for sending data
- break;
- case FD_CLOSE:
- // The connection is now closed
- closesocket( (SOCKET)wParam);
- break;
- }
- break;
- }
- return TRUE;
- }
Therefore, an application should assume that sends are always possible on a socket starting from the first FD_WRITE message and lasting until a send,WSASend, sendto, or WSASendTo returns the socket error WSAEWOULDBLOCK. After such failure, another FD_WRITE message notifies the application that sends are once again possible.
The WSAAsyncSelect model offers many advantages; foremost is the capability to handle many connections simultaneously without much overhead, unlike the select model's requirement of setting up the fd_set structures. The disadvantages are having to use a window if your application requires no windows (such as a service or console application). Also, having a single window procedure to service all the events on thousands of socket handles can become a performance bottleneck (meaning this model doesn't scale very well).
補充說明 :
* [ NP in MS ] Part2 : Socket I/O Models 之二 - WSAEventSelect Model
* [ NP in MS ] Part2 : Socket I/O Models 之三 - Overlapped Model
沒有留言:
張貼留言