DESKTOP

Network Programming with Windows Sockets : A Socket-Based Server with New Features

10/10/2010 3:14:41 PM

  • Rather than creating a fixed-size thread pool, we now create server threads on demand. Every time the server accepts a client connection, it creates a server worker thread, and the thread terminates when the client quits.

  • The server creates a separate accept thread so that the main thread can poll the global shutdown flag while the accept call is blocked. While it is possible to specify nonblocking sockets, threads provide a convenient and uniform solution. It’s worth noting that a lot of the extended Winsock functionality is designed to support asynchronous operation, and Windows threads allow you to use the much simpler and more standard synchronous socket functionality.

  • The thread management is improved, at the cost of some complexity, so that the state of each thread is maintained.

  • This server also supports in-process servers by loading a DLL during initialization. The DLL name is a command line option, and the server thread first tries to locate an entry point in the DLL. If successful, the server thread calls the DLL entry point; otherwise, the server creates a process, as in serverNP.  The DLL needs to be trusted, however, because any unhandled exception could crash or corrupt the server, as would changes to the environment.

Program 1. serverSK: Socket-Based Server with In-Process Servers
/* Chapter 12. Client/server. SERVER PROGRAM. SOCKET VERSION. */
/* Execute the command in the request and return a response. */
/* Commands will be executed in process if a shared library */
/* entry point can be located, and out of process otherwise. */
/* ADDITIONAL FEATURE: argv [1] can be name of a DLL supporting */
/* in-process servers. */

#include "Everything.h"
#include "ClientServer.h"/* Defines the request and response records.
*/

struct sockaddr_in srvSAddr;/* Server's Socket address structure */
struct sockaddr_in connectSAddr;/* Connected socket */
WSADATA WSStartData; /* Socket library data structure */

enum SERVER_THREAD_STATE {SERVER_SLOT_FREE, SERVER_THREAD_STOPPED,
SERVER_THREAD_RUNNING, SERVER_SLOT_INVALID};

typedef struct SERVER_ARG_TAG { /* Server thread arguments */
CRITICAL_SECTION threadCs;
DWORDnumber;
SOCKET sock;
enum SERVER_THREAD_STATE thState;
HANDLE hSrvThread;
HINSTANCE hDll; /* Shared library handle */
} SERVER_ARG;

static BOOL ReceiveRequestMessage (REQUEST *pRequest, SOCKET);
static BOOL SendResponseMessage (RESPONSE *pResponse, SOCKET);
static DWORD WINAPI Server (PVOID);
static DWORD WINAPI AcceptThread (PVOID);
static BOOL WINAPI Handler (DWORD);

volatile static int shutFlag = 0;
static SOCKET SrvSock = INVALID_SOCKET, connectSock = INVALID_SOCKET;

int main (int argc, LPCTSTR argv [])
{
/* Server listening and connected sockets. */
DWORD iThread, tStatus;
SERVER_ARG sArgs[MAX_CLIENTS];
HANDLE hAcceptThread = NULL;
HINSTANCE hDll = NULL;
/* Console control handler to permit server shutdown */
SetConsoleCtrlHandler (Handler, TRUE);

/* Initialize the WS library. Ver 2.0 */
WSAStartup (MAKEWORD (2, 0), &WSStartData);

/* Open command library DLL if it is specified on command line */
if (argc > 1) hDll = LoadLibrary (argv[1]);

/* Initialize thread arg array */
for (iThread = 0; iThread < MAX_CLIENTS; iThread++) {
InitializeCriticalSection (&sArgs[iThread].threadCs);
sArgs[iThread].number = iThread;
sArgs[iThread].thState = SERVER_SLOT_FREE;
sArgs[iThread].sock = 0;
sArgs[iThread].hDll = hDll;
sArgs[iThread].hSrvThread = NULL;
}
/* Follow standard server socket/bind/listen/accept sequence */
SrvSock = socket(PF_INET, SOCK_STREAM, 0);

/* Prepare the socket address structure for binding the
server socket to port number "reserved" for this service.
Accept requests from any client machine. */

srvSAddr.sin_family = AF_INET;
srvSAddr.sin_addr.s_addr = htonl(INADDR_ANY);
srvSAddr.sin_port = htons(SERVER_PORT);
bind (SrvSock, (struct sockaddr *)&srvSAddr, sizeof(srvSAddr));
listen (SrvSock, MAX_CLIENTS);

/* Main thread becomes listening/connecting/monitoring thread */
/* Find an empty slot in the server thread arg array */
while (!shutFlag) {
iThread = 0;
while (!shutFlag) {
/* Continuously poll thread State of all server slots */
EnterCriticalSection(&sArgs[iThread].threadCs);
__try {
if (sArgs[iThread].thState ==
SERVER_THREAD_STOPPED) {
/* stopped, either normally or a shutdown request */
/* Wait for it to stop, and free the slot */
WaitForSingleObject(sArgs[iThread].hSrvThread,
INFINITE);
CloseHandle (sArgs[iThread].hSrvThread);
sArgs[iThread].hSrvThread = NULL;
sArgs[iThread].thState = SERVER_SLOT_FREE;
}
/* Free slot or shut down. Use slot for new connection */
if (sArgs[iThread].thState == SERVER_SLOT_FREE
||shutFlag) break;
}
__finally {
LeaveCriticalSection(&sArgs[iThread].threadCs);
}

iThread = (iThread++) % MAX_CLIENTS;
if (iThread == 0) Sleep(50); /* Break the polling loop */
/* An alternative: use an event to signal a free slot */
}
if (shutFlag) break;
/* sArgs[iThread] == SERVER_SLOT_FREE */
/* Wait for a connection on this socket */
/* Use a separate accept thread to poll the shutFlag flag */
hAcceptThread = (HANDLE)_beginthreadex (NULL, 0, AcceptThread,
&sArgs[iThread], 0, NULL);
while (!shutFlag) {
tStatus = WaitForSingleObject (hAcceptThread, CS_TIMEOUT);
if (tStatus == WAIT_OBJECT_0) {
/* sArgs[iThread] == SERVER_THREAD_RUNNING */
if (!shutFlag) {
CloseHandle (hAcceptThread);
hAcceptThread = NULL;
}
break;
}
}
} /* OUTER while (!shutFlag) */

/* shutFlag == TRUE */
_tprintf(_T("Shutdown in process. Wait for server threads\n"));
/* Wait for any active server threads to terminate */
/* Try continuously as some threads may be long running. */

while (TRUE) {
int nRunningThreads = 0;
for (iThread = 0; iThread < MAX_CLIENTS; iThread++) {
EnterCriticalSection(&sArgs[iThread].threadCs);
__try {
if (
sArgs[iThread].thState == SERVER_THREAD_RUNNING ||
sArgs[iThread].thState == SERVER_THREAD_STOPPED) {
if (WaitForSingleObject
(sArgs[iThread].hSrvThread, 10000) ==
WAIT_OBJECT_0) {
CloseHandle (sArgs[iThread].hSrvThread);
sArgs[iThread].hSrvThread = NULL;
sArgs[iThread].thState = SERVER_SLOT_INVALID;
} else
if (WaitForSingleObject
(sArgs[iThread].hSrvThread, 10000) ==
WAIT_TIMEOUT) {
nRunningThreads++;
} else {
_tprintf(_T("Error waiting: slot %d\n"), iThread);
}
}
}
__finally { LeaveCriticalSection(&sArgs[iThread].threadCs);}
}
if (nRunningThreads == 0) break;
}

if (hDll != NULL) FreeLibrary (hDll);

/* Redundant shutdown */
shutdown (SrvSock, SD_BOTH);
closesocket (SrvSock);
WSACleanup();
if (hAcceptThread != NULL)
WaitForSingleObject(hAcceptThread, INFINITE));
return 0;
}

static DWORD WINAPI AcceptThread (PVOID pArg)
{
LONG addrLen;
SERVER_ARG * pThArg = (SERVER_ARG *)pArg;

addrLen = sizeof(connectSAddr);
pThArg->sock =
accept (SrvSock, (struct sockaddr *)&connectSAddr, &addrLen);

/* A new connection. Create a server thread */
EnterCriticalSection(&(pThArg->threadCs));
__try {
pThArg->hSrvThread = (HANDLE)_beginthreadex (NULL, 0, Server,
pThArg, 0, NULL);
pThArg->thState = SERVER_THREAD_RUNNING;
}
__finally { LeaveCriticalSection(&(pThArg->threadCs)); }
return 0;
}

BOOL WINAPI Handler (DWORD CtrlEvent)
{
/* Shutdown the program */
_tprintf (_T("In console control handler\n"));
InterlockedIncrement (&shutFlag);
return TRUE;
}


In-process servers could have been included in serverNP if desired. The biggest advantage of in-process servers is that no context switch to a different process is required, potentially improving performance. The disadvantage is that the DLL runs in the server process and could corrupt the server, as described in the last bullet. Therefore, use only trusted DLLs.

The server code is Windows-specific, unlike the client, due to thread management and other Windows dependencies.

The Main Program

Program 1 shows the main program and the thread to accept client connections. It also includes some global declarations and definitions, including an enumerated type, SERVER_THREAD_STATE, used by each individual server thread.

Program 2 shows the server thread function; there is one instance for each connected client. The server state can change in both the main program and the server thread.

Program 2. serverSK: Server Thread Code
static DWORD WINAPI Server (PVOID pArg)

/* Server thread function. One thread for every potential client. */
{
/* Each thread keeps its own request, response,
and bookkeeping data structures on the stack. */
BOOL done = FALSE;
STARTUPINFO startInfoCh;
SECURITY_ATTRIBUTES tempSA = {. . .}; /* Inheritable handles */
PROCESS_INFORMATION procInfo;
SOCKET connectSock;
int commandLen;
REQUEST request;/* Defined in ClientServer.h */
RESPONSE response;/* Defined in ClientServer.h.*/
char sysCommand[MAX_RQRS_LEN], tempFile[100];
HANDLE hTmpFile;
FILE *fp = NULL;
int (__cdecl *dl_addr)(char *, char *);
SERVER_ARG * pThArg = (SERVER_ARG *)pArg;
enum SERVER_THREAD_STATE threadState;

GetStartupInfo (&startInfoCh);

connectSock = pThArg->sock;
/* Create a temp file name */
_stprintf (tempFile, _T("ServerTemp%d.tmp"), pThArg->number);

while (!done && !shutFlag) { /* Main Server Command Loop. */
done = ReceiveRequestMessage (&request, connectSock);

request.record[sizeof(request.record)-1] = '\0';
commandLen = strcspn (request.record, "\n\t");
memcpy (sysCommand, request.record, commandLen);
sysCommand[commandLen] = '\0';
_tprintf (_T("Command received on server slot %d: %s\n"),
pThArg->number, sysCommand);

/* Retest shutFlag; could be set in console control handler. */
done = done || (strcmp (request.record, "$Quit") == 0)
|| shutFlag;
if (done) continue;

/* Open the temporary results file. */
hTmpFile = CreateFile (tempFile, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, &tempSA,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

/* Check for shared library command. For simplicity, shared */
/* library commands take precedence over process commands */
dl_addr = NULL; /* will be set if GetProcAddress succeeds */
if (pThArg->hDll != NULL) { /* Try Server "In process" */
char commandName[256] = "";
int commandNameLength = strcspn (sysCommand, " ");
strncpy (commandName, sysCommand,
min(commandNameLength, sizeof(commandName)));
dl_addr = (int (*)(char *, char *))GetProcAddress
(pThArg->hDll, commandName);
/* Trust this DLL not to corrupt the server */
if (dl_addr != NULL) { /* Call the DLL */
(*dl_addr)(request.record, tempFile);
}
}

if (dl_addr == NULL) { /* No inprocess support */
/* Create a process to carry out the command. */
/* Same as in serverNP*/
. . .
}

/* Respond a line at a time. It is convenient to use
C library line-oriented routines at this point. */

/* Send temp file, one line at a time, to the client. */
/* Same as in serverNP */
. . .
} /* End of main command loop. Get next command */

/* done || shutFlag */
/* End of command processing loop. Free resources; exit thread. */
_tprintf (_T("Shuting down server thread # %d\n"), pThArg->number);
closesocket (connectSock);

EnterCriticalSection(&(pThArg->threadCs));
__try {
threadState = pThArg->thState = SERVER_THREAD_STOPPED;
}
__finally { LeaveCriticalSection(&(pThArg->threadCs)); }

return threadState;
}


The server state logic involves both the boss and server threads. 

 

The Server Thread

Program 2 shows the socket server thread function. There are many similarities to the named pipe server function, and some code is elided for simplicity. Also, the code uses some of the global declarations and definitions from Program 1.

Running the Socket Server

Run 2 shows the server in operation, with several printed information messages that are not in the listings for Programs 1 and 2. The server has several clients, one of which is the client shown in Run 1 (slot 0).

Run 1. serverSK: Requests from Several Clients


The termination at the end occurs in the accept thread; the shutdown closes the socket, causing the accept call to fail. An exercise suggests ways to make this shutdown cleaner.

A Security Note

This client/server system, as presented, is not secure. If you are running the server on your computer and someone else knows the port and your computer name, your computer is at risk. The other user, running the client, can run commands on your computer that could, for example, delete or modify files.

Other  
  •  Network Programming with Windows Sockets : A Socket-Based Client
  •  Network Programming with Windows Sockets : A Socket Message Receive Function
  •  Exchange Server 2010 : Operating Without Traditional Point-in-Time Backups
  •  Exchange Server 2010 : Performing Backup and Recovery for Mailbox Server Roles
  •  Exchange Server 2010 : Performing Backup and Recovery for Non-Mailbox Server Roles
  •  Exchange Server 2010 : Backup and Disaster Recovery Planning
  •  Changes to Backup and Restore in Exchange Server 2010
  •  Programming Windows Azure : Using the SDK and Development Storage
  •  Programming Windows Azure : Building a Storage Client
  •  Working with the REST API
  •  Excel Programmer : Fix Misteakes
  •  Excel Programmer : Change Recorded Code
  •  Excel Programmer : Record and Read Code
  •  Configuring Server Roles in Windows 2008 : New Roles in 2008
  •  Windows Server 2003 : Creating and Configuring Application Directory Partitions
  •  Windows Server 2003 : Configuring Forest and Domain Functional Levels
  •  Windows Server 2003 : Installing and Configuring Domain Controllers
  •  Manage Server Core
  •  Configure Server Core Postinstallation
  •  Install Server Core
  •  
    Top 10
    Fujifilm XF1 - The Stylish Shooter
    Nikon 1 V2 - Still Fast and Handles Better
    Asustor AS-604T 4-Bay NAS Review (Part 3)
    Asustor AS-604T 4-Bay NAS Review (Part 2)
    Asustor AS-604T 4-Bay NAS Review (Part 1)
    Toshiba Satellite U925t Review (Part 3)
    Toshiba Satellite U925t Review (Part 2)
    Toshiba Satellite U925t Review (Part 1)
    iBall Andi 4.5H - Pretty In White
    The HTC Butterfly - Full HD In 5 Inches Only
    Most View
    Windows 7 : Command-Line and Automation Tools - Batch Files, Windows PowerShell
    IIS 7.0 : Managing Configuration - Backing Up Configuration, Using Configuration History & Exporting and Importing Configuration
    SharePoint 2010: Business Connectivity Services - The Secure Store Service (part 2) - Creating a Secure Store Service Application for Impersonating
    Reporting Services with SQL Azure : Starting a SQL Azure–Based Report
    Windows Server 2008 : Transport-Level Security - Active Directory Rights Management Services
    Who’s Watching You? (Part 4)
    Automating Blind SQL Injection Exploitation
    Partitioning Disks and Preparing Them for Use in Vista
    AMD Radeon HD 7850 2GB vs. Nvidia GeForce GTX 660 2GB vs. AMD Radeon HD 7870 2GB (Part 1)
    SharePoint 2010 : Workflow Modeling and Development Tools (part 1) - Microsoft Visio 2010 & SharePoint Designer 2010
    Sony VAIO Duo 11 - The Notebook-Tablet Hybrid
    Programming .NET Components : Serialization Events (part 3) - Type-Version Tolerance
    All You Need To Know About iOS 6 (Part 3)
    Windows Phone 8 Operating System Review – Part2
    Transferring Bookmarks With Internet Usernames And Passwords To A New PC
    Advanced ASP.NET : Data Caching (part 1) - Adding Items to the Cache & A Simple Cache Test
    Asus Rog Tytan CG8565 - Clash Of The Tytan
    2012 - The Year to Come (Part 2)
    Compact System Cameras : Samsung NX200, Nikon 1J1, Olympus PEN E-PM1, Panasonic GX1, Sony NEX-5N
    Nokia Lumia 620 Review - Basic Smartphone With Good Performance And Stunning Design (Part 1)