Saturday, November 21, 2009

Example: A Socket-Based Server with New Features









Example: A Socket-Based Server with New Features


serverSK, Program 12-2, is similar to serverNP, Program 11-3, but there are several changes and improvements.


  • 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. A sample DLL is shown in Program 12-3. If the DLL were to generate an exception, the entire server process would be destroyed, so the DLL function call is protected by a simple exception handler.


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 server code is Windows-specific, unlike the client, due to thread management and other Windows dependencies.


Program 12-2. 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. */

#define _NOEXCLUSIONS
#include "EvryThng.h"
#include "ClntSrvr.h" /* Defines 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. */

typedef struct SERVER_ARG_TAG { /* Server thread arguments. */
volatile DWORD number;
volatile SOCKET sock;
volatile DWORD status;
/* Explained in main thread comments. */
volatile HANDLE srv_thd;
HINSTANCE dlhandle; /* Shared library handle. */
} SERVER_ARG;
volatile static ShutFlag = FALSE;
static SOCKET SrvSock, ConnectSock;

int _tmain (DWORD argc, LPCTSTR argv [])
{
/* Server listening and connected sockets. */
BOOL Done = FALSE;
DWORD ith, tstatus, ThId;
SERVER_ARG srv_arg [MAX_CLIENTS];
HANDLE hAcceptTh = NULL;
HINSTANCE hDll = NULL;

/* Initialize the WSA library, Ver 2.0, although 1.1 will work. */
WSAStartup (MAKEWORD (2, 0), &WSStartData);

/* Open command library DLL if specified on command line. */
if (argc > 1) hDll = LoadLibrary (argv [1]);
/* Initialize thread arg array. */
for (ith = 0; ith < MAX_CLIENTS; ith++) {
srv_arg [ith].number = ith;
srv_arg [ith].status = 0; srv_arg [ith].sock = 0;
srv_arg [ith].dlhandle = hDll; srv_arg [ith].srv_thd = NULL;
}
/* Follow standard server socket/bind/listen/accept sequence. */
SrvSock = socket (AF_INET, SOCK_STREAM, 0);
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. */
/* status values: 0 -- slot is free; 1 -- thread stopped;
2 -- thread running; 3 -- stop entire system. */
while (!ShutFlag) {
for (ith = 0; ith < MAX_CLIENTS && !ShutFlag; ) {
if (srv_arg [ith].status==1 || srv_arg [ith].status==3) {
/* Thread stopped, normally or by shutdown request. */
WaitForSingleObject (srv_arg[ith].srv_thd INFINITE);
CloseHandle (srv_arg[ith].srv_thd);
if (srv_arg [ith].status == 3) ShutFlag = TRUE;
else srv_arg [ith].status = 0;
/* Free thread slot. */
}
if (srv_arg [ith].status == 0 || ShutFlag) break;
ith = (ith + 1) % MAX_CLIENTS;
if (ith == 0) Sleep (1000);
/* Break the polling loop. */
/* Alternative: use an event to signal a free slot. */
}
/* Wait for a connection on this socket. */
/* Separate thread so we can poll the ShutFlag flag. */
hAcceptTh = (HANDLE)_beginthreadex (NULL, 0, AcceptTh,
&srv_arg [ith], 0, &ThId);
while (!ShutFlag) {
tstatus = WaitForSingleObject (hAcceptTh, CS_TIMEOUT);
if (tstatus == WAIT_OBJECT_0) break;
/* Connection made. */
}
CloseHandle (hAcceptTh);
hAcceptTh = NULL; /* Prepare for next connection. */
}

_tprintf (_T ("Server shutdown. Wait for all srvr threads\n"));
/* Terminate the accept thread if it is still running.
* See the Web site for more detail on this shutdown logic. */
if (hDll != NULL) FreeLibrary (hDll);
if (hAcceptTh != NULL) TerminateThread (hAcceptTh, 0);
/* Wait for any active server threads to terminate. */
for (ith = 0; ith < MAX_CLIENTS; ith++)
if (srv_arg [ith].status != 0) {
WaitForSingleObject (srv_arg[ith].srv_thd, INFINITE);
CloseHandle (srv_arg[ith].srv_thd);
}
shutdown (SrvSock, 2);
closesocket (SrvSock);
WSACleanup ();
return 0;
}

static DWORD WINAPI AcceptTh (SERVER_ARG * pThArg)
{
/* Accepting thread that allows the main thread to poll the */
/* shutdown flag. This thread also creates the server thread. */
LONG AddrLen, ThId;

AddrLen = sizeof (ConnectSAddr);
pThArg->sock = accept (SrvSock, /* This is a blocking call. */
(struct sockaddr *) &ConnectSAddr, &AddrLen);
/* A new connection. Create a server thread. */
pThArg->status = 2;
pThArg->srv_thd =
(HANDLE) _beginthreadex (NULL, 0, Server, pThArg, 0, &ThId);
return 0; /* Server thread remains running. */
}
static DWORD WINAPI Server (SERVER_ARG * pThArg)
/* Server thread function. Thread created on demand. */
{
/* Each thread keeps its own request, response,
and bookkeeping data structures on the stack. */
/* ... Standard declarations from serverNP omitted ... */
SOCKET ConnectSock;
int Disconnect = 0, i;
int (*dl_addr)(char *, char *);
char *ws = " \0\t\n"; /* White space. */

GetStartupInfo (&StartInfoCh);
ConnectSock = pThArg->sock;
/* Create a temp file name. */
sprintf (TempFile, "%s%d%s", "ServerTemp",
pThArg->number, ".tmp");

while (!Done && !ShutFlag) { /* Main command loop. */
Disconnect = ReceiveRequestMessage (&Request, ConnectSock);
Done = Disconnect || (strcmp (Request.Record, "$Quit") == 0)
|| (strcmp (Request.Record, "$ShutDownServer") == 0);
if (Done) continue;
/* Stop this thread on "$Quit" or "$ShutDownServer". */
hTmpFile = CreateFile (TempFile,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, &TempSA,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

/* Check for a DLL command. For simplicity, shared */
/* library commands take precedence over process
commands. First, extract the command name. */

i = strcspn (Request.Record, ws); /* Length of token. */
memcpy (sys_command, Request.Record, i);
sys_command [i] = '\0';

dl_addr = NULL; /* Will be set if GetProcAddress succeeds. */
if (pThArg->dlhandle != NULL) { /* Try server "in process." */
dl_addr = (int (*)(char *, char *))
GetProcAddress (pThArg->dlhandle, sys_command);
if (dl_addr != NULL) __try {
/* Protect server process from exceptions in DLL. */
(*dl_addr) (Request.Record, TempFile);
} __except (EXCEPTION_EXECUTE_HANDLER {
ReportError (_T ("Exception in DLL"), 0, FALSE);
}
}
}
if (dl_addr == NULL) { /* No in-process support. */
/* Create a process to carry out the command. */
/* ... Same as in serverNP ... */
}
/* ... Same as in serverNP ... */

} /* End of main command loop. Get next command. */

/* End of command loop. Free resources; exit from the thread. */

_tprintf (_T ("Shutting down server# %d\n"), pThArg->number);
shutdown (ConnectSock, 2);
closesocket (ConnectSock);
pThArg->status = 1;
if (strcmp (Request.Record, "$ShutDownServer") == 0) {
pThArg->status = 3;
ShutFlag = TRUE;
}
return pThArg->status;
}



A Security Note


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


A complete discussion of security solutions is well beyond this book's scope. Nonetheless, Chapter 15 shows how to secure Windows objects, and Exercise 1214 suggests using SSL.









    No comments: