Socket Programming in Windows
When you are familiar with network programming in the Unix environment, understanding Windows network programming is easy. This section describes the relationship between the Windows network programming interface and the Unix network programming model, and how Windows socket programming has formed the foundation of the .NET Framework network classes.
Windows Socket Functions
It makes sense that the Windows network programming model is derived from the comparable Unix model. Many features of the Windows operating systems have their roots in Unix systems. Much of Windows network programming was modeled after the Unix Berkeley socket method. It was called, not surprisingly, Windows Sockets, or Winsock for short. The Winsock interface was designed to allow network programmers from the Unix environment to easily port existing network programs, or to create new network programs in the Windows environment without a large learning curve.
The Winsock APIs were implemented as a set of header and library files for developers and DLL files to be used by applications. There are two basic Winsock library versions: the 1.1 version was originally released with Windows 95 workstations and provided basic socket functionality. Later, version 2 was released as an add-on for Windows 95 machines. It added significantly more socket functions and protocols that could be deployed by network programmers. By the time Windows 98 was released, the Winsock library had matured to version 2.2, which is still a part of the current Windows operating system releases.
Note |
The lone exception to this arrangement is the Windows CE platform. At this writing, Windows CE still only supports the Winsock 1.1 libraries. |
The core of the Winsock environment is, of course, the socket. Just as in Unix, all Windows network programs create a socket to establish a link with the underlying network interface on the Windows system. All of the standard socket function calls employed in the Unix world were ported to the Windows system. However, there are a few differences between Unix sockets and Winsock. The following sections describe these differences.
WSAStartup()
To begin a Winsock program, you make a call to the WSAStartup() function. This function informs the operating system which Winsock version the program needs to use. The OS attempts to load the appropriate Winsock library from which the socket functions will operate.
The format of the WSAStartup() function is as follows:
int WSAStartup(WORD wVersion, LPWSDATA lpWSAData)
The first parameter defines the required version for the program. If the program requests version 2.2 of Winsock and only version 1.1 is available, the WSAStartup() function will return an error. However, if the application requests version 1.1 and version 2.2 is loaded, the function will succeed.
When the function succeeds, the lpWSAData parameter points to a structure that will contain information regarding the Winsock library after it’s loaded, such as the actual Winsock version used on the system. This information can then be used to determine the network capabilities of the system the program is running on.
WSACleanup()
A Winsock program must release the Winsock library when it is finished. The WSACleanup() function is used at the end of each Winsock program to indicate that no other Winsock functions will be used, and the Winsock library can be released. The WSACleanup() function does not use any parameters, it just signals the end of the Winsock functions in the program. If any Winsock functions are used after the WSACleanup() function, an error condition will be raised.
Winsock Functions
In between the WSAStartup() and WSACleanup() functions, the Winsock program can behave just like the Unix socket program, using socket(), bind(), connect(), listen(), and accept() calls. In fact, the Winsock interface uses the same structures for addresses (sockaddr_in) and the same values to define protocol families and types (such as the SOCK_STREAM protocol family) as Unix does. The goal of this was to make porting Unix network programs to the Windows environment as easy as possible.
In addition to the standard Unix network functions, the Winsock version 2 interface includes its own set of network functions, all preceded by WSA. These functions extend the functionality of the standard Unix network functions. For example, the WSARecv()function can be used in place of the standard Unix recv() function call. WSARecv() adds two additional parameters to the original function call, allowing for the Windows-specific functionality of creating overlapped I/O and partial datagram notifications. Figure 3.4 shows how the Winsock WSA functions can be used to replace standard Unix functions.
Winsock Non-blocking Socket Functions
Another similarity to the Unix network environment is that Winsock supplies ways to prevent network I/O functions from blocking the program execution. Winsock supports the standard Unix methods of setting a socket to non-blocking mode using the ioctlsocket() function (similar to the Unix fcntl() function) and the select() function to multiplex multiple sockets.
The ioctlsocket() format is as follows:
ioctlsocket(SOCKET s, long cmd, u_long FAR* argp)
The socket to be modified is s, the cmd parameter specifies the operation to make on the socket, and the argp parameter specifies the command parameter.
In addition to these standard socket functions, the Winsock interface offers additional methods of allowing non-blocking network I/O.
WSAAsyncSelect()
One of the features that differentiates Windows from standard Unix programs is the concept of events. Unlike common structured programs that have a set way of executing, Windows programs are usually event driven. Methods are executed in the program in response to events occurring while the program is running—buttons are clicked, menu items are selected, and so on. The standard technique of waiting around for data to occur on network sockets does not fit well in the Windows event model. Event-driven access to network sockets is the answer.
The WSAAsyncSelect() function expands on the standard Unix select() function by allowing Windows to do the work of querying the sockets. A WSAAsyncSelect() method is created that includes the socket to monitor, along with a Windows message value that will be passed to the window when one of the socket events occurs (such as data being available to be read, or the socket being ready to accept written data). The format of the WSAAsyncSelect() function is as follows:
int WSAAsyncSelect(SOCKET s, HWND hWnd,
unsigned int wMsg, long lEvent)
The socket to monitor is defined by the s parameter, and the parent window to receive the event message is defined by hWnd. The actual event to send is defined by the wMsg parameter. The last parameter, lEvent, defines the events to monitor for the socket. You can monitor more than one event for a socket by performing a bitwise OR of the events shown in Table 3.4.
Event |
Description |
---|---|
FD ACCEPT |
A new connection is established with the socket. |
FD ADDRESS LIST CHANGE |
The local address list changed for the socket’s protocol family. |
FD CLOSE |
An existing connection has closed. |
FD CONNECT |
The socket has completed a connection with a remote host. |
FD GROUP QOS |
The socket group’s Quality of Service value has changed. |
FD OOB |
The socket has received out-of-band data. |
FD QOS |
The socket’s Quality Of Service value has changed. |
FD READ |
The socket has data that is ready to be read. |
FD ROUTING INTERFACE CHANGE |
The socket’s routing interface has changed for a specific destination. |
FD WRITE |
The socket is ready for writing data. |
An example of the WSAAsyncSelect() function would look like this:
WSAAsyncSelect(sock, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
In this example, if the socket has data available to be read, or if it detects that the remote host closed the connection, the WM_SOCKET message would be sent to the hwnd window in the wParam of the Window message. It would then be the responsibility of the hwnd window to detect and handle the WM_SOCKET message and perform the appropriate functions depending on which event was triggered. This is almost always handled in a Windows procedure (WindowProc) method for the window using case statements.
WSAEventSelect()
Instead of handling socket notifications using Windows messages, the WSAEventSelect() uses an event object handle. The event object handle is a self-contained method defined in the program that is called when a unique event is triggered. This technique allows you to create separate Windows methods to handle the various socket events.
For this technique to work, a unique event must first be defined using the WSACreateEvent() function. After the event is created, it must be matched to a socket using the WSAEventSelect() function:
WSASelect(SOCKET s, WSAEVENT hEvent, long lNetworkEvents)
As usual, the s parameter defines the socket to monitor, and hEvent defines the created event that will be called when the socket event occurs. Similar to the WSAAsyncSelect() function, the lNetworkEvent parameter is a bitwise combination of all the socket events to monitor. The same event definitions are used for the WSAEventSelect() function as for the WSAAsyncSelect() function. When a socket event occurs, the event method registered by the WSACreateEvent() function is executed.
Overlapped I/O
Possibly one of the greatest features of the Winsock interface is the concept of overlapped I/O. This technique allows a program to post one or more asynchronous I/O requests at a time using a special data structure. The data structure (WSAOVERLAPPED) defines multiple sockets and event objects that are matched together. The events are considered to be overlapping, in that multiple events can be called simultaneously as the sockets receive events.
To use the overlapped technique, a socket must be created with the WSASocket() function call using the overlapped enabled flag (the socket() function does not include this flag). Likewise, all data communication must be done using the WSARecv() and WSASend() functions. These Winsock functions use an overlapped I/O flag to indicate that the data will use the WSAOVERLAPPED data structure.
Although using overlapped I/O can greatly improve performance of the network program, it doesn’t solve all of the possible difficulties. One shortcoming of the overlapped I/O technique is that it can define only 64 events. For large-scale network applications that require hundreds of connections, this technique will not work.
Completion Ports
Another downside to the overlapped I/O technique is that all of the events are processed within a single thread in the program. To allow events to be split among threads, Windows introduced the completion port. A completion port allows the programmer to specify a number of threads for use within a program, and assign events to the individual threads. By combining the overlapped I/O technique with the completion port method, a programmer can handle overlapped socket events using separate program threads. This technique produces really interesting results on systems that contain more than one processor. By creating a separate thread for each processor, multiple sockets can be monitored simultaneously on each processor.