The Windows Sockets API is based on the BSD Sockets API, so many of the calls are very similar. There are some minor differences in setup. The most obvious differences are the requirements for header files. Listing 6.31 shows the steps necessary to include the networking sockets functions. The Windows header automatically includes the 1.1 ver-sion of the Windows socket library. The #define WIN32_LEAN_AND_MEAN avoids the inclusion of this header and allows the application to include the 2.0 version of the Windows socket library.
The Microsoft compiler also allows the source to contain a directive indicating which libraries are to be linked into the executable. In this case, the library is ws2_32.lib; this is more convenient than having to specify it on the command line. We also allocate a global variable that will be used to store the handle of an event object. The event object will be used to ensure that the server thread is ready before the client thread sends any data.
Listing 6.31 Including the Header Files for Windows Sockets
#pragma comment( lib, "ws2_32.lib" )
Listing 6.32 shows the code for the main thread. The first action that the main thread needs to take is to start the Windows sockets library with a call into WSAStartup(); this takes two parameters. The first parameter is the version number of the library that the application requires; version 2.2 is current. The second parameter is the address of a WSADATA structure where the description of the sockets implementation will be stored.
Listing 6.32 The Main Thread Is Responsible for Starting Both the Client and Server Threads
int _tmain( int argc, _TCHAR* argv )
HANDLE serverthread, clientthread;
WSAStartup( MAKEWORD(2,2), &wsaData );
hEvent = CreateEvent( 0, true, 0, 0 );
serverthread = (HANDLE)_beginthreadex( 0, 0, &server, 0, 0, 0 );
clientthread = (HANDLE)_beginthreadex( 0, 0, &client, 0, 0, 0 ); WaitForSingleObject( clientthread, INFINITE );
CloseHandle( clientthread );
CloseHandle( hEvent ); getchar();
As previously mentioned, the code uses an event object to ensure that the server thread starts up before the client thread sends a request. The event is created through a call to CreateEvent(). The event is created unsignaled so that it can be signaled by the server thread, and this signaling will enable the client thread to progress. The event is set up to require a manual reset so that once it has been signaled, it remains in that state. This ensures that any later client threads will not block on the event.
The main thread then starts the server thread and the client thread using calls to _beginthreadex(). It waits until the client thread completes before exiting. The final action of the main thread is to call WSACleanup() to close down the sockets library.
Listing 6.33 shows the code for the client thread. The client thread is going to send data to the server and then print the response from the server. The client thread first opens up a socket and then waits for the event object to become signaled, indicating that the server is ready, before continuing.
Listing 6.33 Code for the Client Thread
unsigned int __stdcall client( void * data )
SOCKET ConnectSocket = socket( AF_INET, SOCK_STREAM, 0 );
WaitForSingleObject( hEvent, INFINITE );
struct sockaddr_in server; ZeroMemory( &server, sizeof(server) ); server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr( "127.0.0.1" ); server.sin_port = 7780;
connect( ConnectSocket, (struct sockaddr*)&server, sizeof(server) );
printf( "Sending 'abcd' to server\n" );
ZeroMemory( buffer, sizeof(buffer) );
strncpy_s( buffer, 1024, "abcd", 5 );
send( ConnectSocket, buffer, strlen(buffer)+1, 0 );
ZeroMemory( buffer, sizeof(buffer) );
recv( ConnectSocket, buffer, 1024, 0 );
printf( "Got '%s' from server\n", buffer );
printf( "Close client\n" );
shutdown( ConnectSocket, SD_BOTH );
closesocket( ConnectSocket );
The code uses the socket to connect to port 7780 on the localhost (localhost is defined as the IP address 127.0.0.1). Once connected, it sends the string "abcd" to the server and then waits to receive a string back from the server. Once it receives the returned string, it shuts down the connection and then closes the socket.
Listing 6.34 shows the code for the server thread. The server thread does not actually handle the response to any client thread. It exists only to accept incoming connections and to pass the details of this connection onto a newly created thread that will handle the response.
Listing 6.34 Code for the Server Thread
unsigned int __stdcall server( void * data )
SOCKET ServerSocket = socket( AF_INET, SOCK_STREAM, 0 );
struct sockaddr_in server; ZeroMemory( &server, sizeof(server) ); server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = 7780;
bind( ServerSocket,(struct sockaddr*)&server, sizeof(server) ); listen( ServerSocket, SOMAXCONN );
while ( (newsocket = accept( ServerSocket, 0, 0) )!=INVALID_SOCKET )
newthread=(HANDLE)_beginthread( &handleecho, 0, (void*)newsocket);
printf( "Close server\n" );
shutdown( ServerSocket, SD_BOTH );
closesocket( ServerSocket );
Listing 6.35 shows the code for the thread that will actually respond to the client. This thread will loop around, receiving data from the client thread and sending the same data back to the client thread until it receives a return value that indicates the socket has closed or some other error condition. At that point, the thread will shut down and close the socket.
Listing 6.35 Code for the Echo Thread
void handleecho( void * data )
ZeroMemory( buffer, sizeof(buffer) );
while ( (count = recv( socket, buffer, 1023, 0) ) >0 )
printf( "Received %s from client\n", buffer ); int ret = send( socket, buffer, count, 0 );
printf( "Close echo thread\n" );
shutdown( socket, SD_BOTH );
closesocket( socket );
The first activity of the server thread is to open a socket. It then binds this socket to accept any connections to port 7780. The socket also needs to be placed into the listen state; the value SOMAXCONN contains the maximum number of connections that will be queued for acceptance. Once these steps have been completed, the server thread signals the event, which then enables the client thread to attempt to connect.
The main thread then waits in a loop accepting connections until it receives a con-nection identified as INVALID_SOCKET. This will happen when the Windows socket library is shut down and is how the server thread will exit cleanly when the other thread exits.
Every time the server thread accepts a connection, a new thread is created, and the identification of this new connection is passed into the newly created thread. It is impor-tant to notice that the call to create the thread that will actually handle the work is _beginthread(). The _beginthread() call will create a new thread that does not leave resources that need to be cleaned up with a call to CloseHandle() when it exits. In contrast, the client and server threads were created by the master thread with a call to _beginthreadex(), which means that they will have resources assigned to them until a call to CloseHandle() is made.
When the loop finally receives an INVALID_SOCKET, the server thread shuts down and then closes the socket.
The code for Windows is sufficiently similar to that for Unix-like operating systems that it is possible to convert between the two. Although the example program is rela-tively simple, it illustrates the key steps necessary for communication between two threads, two processes, or two systems.