/* // This implements the flexible communications interface. The module is responsible for: // * Efficient "signal blocks": that is, blocks that render the governing program idle until a signal arrives. When a signal arrives, the // governing program activates to handle the signal, and then blocks again; each signal handler gets protected space in which it receives and // returns data. The signal block terminates when each signal client unregisters. // * (Signal clients must register themselves and increment a counter. This is not a very safe way to keep track of clients, but I, who am the writer of the server, // trust Myself, who write the client. * Efficient "signal client calls". A signal call will not return until the signal it has generated is handled. Information is exchanged automatically, and the exchange of information is encapsulated in this module. Of course, information is exchanged exclusively through the encapsulated blocks of data. Under Windows, I've implemented them as memory-mapped files; in UNIX they will be pipes. Signals are handled one at a time; whenever a signal is issued that cannot be handled, the program blocks. A signal server is identified uniquely by its CommunID handle, which corresponds to different things under different systems and different implementations. */ /* Implementation details: Each server is associated with a simple shared dataspace, shared between all the clients. When a client has a request for the server, it modifies the data space by loading it with the handle of its own data space, This data space is associated with a semaphore; when the semaphore becomes non-signalled (i.e., the count becomes 0), the server blocks and waits. Under UNIX, given what we know, we can simulate this semaphore between processes using a signal; if a signal cannot be sent, then the semaphore is signalled, and the current signal handler will dispatch all remaining requests when it is finished. To protect the request-dataspace from multiple clients, we use the usual mechanisms - mutexes under Windows, closed pipes under UNIX. */ ; #ifdef _WIN32 #include #include #endif // Common. #include "CommunIface.h" #ifdef _WIN32 typedef struct { HANDLE hMMF; HANDLE hMutex; void* pMem; int iRefs; } SWinDS; typedef struct { // SWinClient "inherits" from SClient HDS hDataSpace; HServer hServer; PROCClientBase pfnClient; // ------ HANDLE hDataEvent; } SWinClient; typedef struct { HDS hds_Requests; PROCServerRequest pfnRQ; int iClients; // -------- HANDLE hNerf; HANDLE hevtClients; } SWinServer; typedef struct { HClient hClient; HServer hSrv; } SWinClientBundle; // Thread base. DWORD __stdcall CLT_ThreadBase (void* pvParam); #endif typedef struct { HDS hDataSpace; HServer hServer; PROCClientBase pfnClient; } SClient; // Overlay on SWinClient. typedef struct { HDS hds_Requests; PROCServerRequest pfnRQ; int iClients; } SServer; #define MAX_DS_SIZE 800 // Say. const char g_szDR[] = "DREG"; // Dataspaces // It is assumed that the data space handles will be equal between clients and servers. This is true under Windows when threads implement the system, // and it's true under UNIX fork because the entire address space of the process is duplicated. #ifdef _WIN32 void* OpenDataSpace (HANDLE hMMF) { return MapViewOfFile (hMMF, FILE_MAP_ALL_ACCESS, 0, 0, 0); } void CloseDataSpace (void* pv) { UnmapViewOfFile (pv); } #endif // NOTE: It is indeed proper to stack calls to Request and Release. The outermost set determines the scope of a dataspace's use. // (there is a reference counter) BOOL RequestDataSpace (HDS hds) { #ifdef _WIN32 SWinDS* pds = (SWinDS*)hds; // Prepare for reading if (!pds->pMem) { WaitForSingleObjectEx (pds->hMutex, INFINITE, FALSE); pds->pMem = OpenDataSpace (pds->hMMF); } if (pds->iRefs > 55) __asm int 3; pds->iRefs++; return TRUE; #endif } BOOL ReleaseDataSpace (HDS hds) { #ifdef _WIN32 SWinDS* pds; pds = (SWinDS*)hds; if (pds->iRefs > 0) pds->iRefs--; else return FALSE; // Not open (a Release with no Request). // Close. if (pds->iRefs == 0) { CloseDataSpace (pds->pMem); pds->pMem = NULL; ReleaseMutex (pds->hMutex); } return TRUE; #endif } HDS MakeDataSpace () { // This command creates a data space. A data space is associated with an accessible, protectible set of data. #ifdef _WIN32 SWinDS* pds; HANDLE hMMF; pds = malloc (sizeof (SWinDS) ); // We begin by creating a memory-mapped file. hMMF = CreateFileMapping (INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MAX_DS_SIZE, NULL); // We don't need a name, since we are inside one process. if (!hMMF) return NULL; pds->hMMF = hMMF; pds->hMutex = CreateMutex (NULL, FALSE, NULL) ; // No protection until we block. pds->pMem = NULL; pds->iRefs = 0; // 0 refs - not open. return pds; #endif } BOOL FreeDataSpace (HDS hds) { // Free a data space. // This function should never be called when someone is waiting to write on a dataspace. // I don't guarantee it explicitly, but it is a boiling pot. #ifdef _WIN32 SWinDS* pds; pds = (SWinDS*)hds; if (!pds) return FALSE; // Close the MMF CloseHandle (pds->hMMF); // Destroy the data structure free (pds); return TRUE; #endif } void WriteDataSpace (HDS hDS, const void* pvVar, MEMREF mrOff, MEMREF mrSize) { #ifdef _WIN32 SWinDS* pds; void* pAddr; pds = (SWinDS*)hDS; pAddr = (void*)((MEMREF)pds->pMem + mrOff); memcpy (pAddr, pvVar, mrSize); #endif } void ReadDataSpace (HDS hDS, void* pvVar, MEMREF mrOff, MEMREF mrSize) { #ifdef _WIN32 SWinDS* pds; void* pAddr; pds = (SWinDS*)hDS; pAddr = (void*)((MEMREF)pds->pMem + mrOff); memcpy (pvVar, pAddr, mrSize); #endif } // Data crackers #ifdef _WIN32 HANDLE ClientGetDataEvent (HClient hClient) { SWinClient* pWC = (SWinClient*)hClient; return pWC->hDataEvent; } // A server "nerf" is a mutex which is handed from client to client to streamline communication. HANDLE ServerGetNerf (HServer hSrv) { SWinServer* pWS = (SWinServer*)hSrv; return pWS->hNerf; } HANDLE ServerGetClientEvent (HServer hSrv) { SWinServer* pWS = (SWinServer*)hSrv; return pWS->hevtClients; } #endif HDS ClientGetDS (HClient hClient) { SClient* pClient = (SClient*)hClient; return pClient->hDataSpace; } PROCClientBase ClientGetStartRoutine (HClient hClient) { SClient* pClient = (SClient*)hClient; return pClient->pfnClient; } HDS ServerGetRequestDS (HServer hSrv) { SServer* pSrv = (SServer*)hSrv; return pSrv->hds_Requests; } // Server functions. // Registration information is kept at the server HClient SRVSpawn (HServer hSrv, HDS hDataSpace, PROCClientBase pfnClient) { #ifdef _WIN32 SWinClient* pWC = (SWinClient*)malloc (sizeof(SWinClient) ); pWC->hDataSpace = hDataSpace; pWC->pfnClient = pfnClient; // Initialize the "nerf" mutex. pWC->hDataEvent = CreateEvent (NULL, TRUE, FALSE, NULL); // This event is reset at the beginning of a request to the server. The client will block on it while waiting for the request to be processed. pWC->hServer = hSrv; // Redundancy. return (HClient)pWC; #endif } HServer SRVInitialize (PROCServerRequest pfnRQ) { #ifdef _WIN32 SWinServer* pSrv = (SWinServer*)malloc (sizeof(SWinServer)); pSrv->hds_Requests = MakeDataSpace (); // The requests dataspace. pSrv->pfnRQ = pfnRQ; pSrv->iClients = 0; pSrv->hNerf = CreateMutex (NULL, FALSE, NULL); // The "nerf" mutex we pass around between the clients. ReleaseMutex (pSrv->hNerf); pSrv->hevtClients = CreateEvent (NULL, TRUE, FALSE, NULL); // Used to awaken the client when at least one client is pending. return (HServer)pSrv; #endif } void SRVLaunchClient (HServer hSrv, HClient hClt) { // To launch a client, we create the associated thread or fork. #ifdef _WIN32 unsigned int hThread; HANDLE hT; int iPr; SWinClientBundle* pCB = (SWinClientBundle*)malloc (sizeof(SWinClientBundle) ); pCB->hSrv = hSrv; pCB->hClient = hClt; _beginthreadex (NULL, 0, CLT_ThreadBase, pCB, 0, &hThread); #endif // The parameters are now input. } BOOL SRVWaitPending (HServer hSrv) { // This function will wait on an associated semaphore (WIN32) or busily for a signal (whose handler receives the same information). #ifdef _WIN32 SWinServer* pSrv = (SWinServer*)hSrv; return WaitForSingleObjectEx (pSrv->hevtClients, INFINITE, FALSE); #endif } BOOL SRVSatisfy (HServer hSrv, HClient hClient) { #ifdef _WIN32 // As described above, we must notify the client via its event that the information it requests is available. // This we do by signalling the appropriate event. //printf ("Trying to satisfy client %x with event %x\n", (unsigned int)hClient, (unsigned int)ClientGetDataEvent (hClient)); return SetEvent (ClientGetDataEvent (hClient) ); #endif } /* void SRVUpdateWaitStatus_NoClients (HServer hSrv) { #ifdef _WIN32 // We check the mutex. ResetEvent (ServerGetClientEvent (hSrv)); // No clients waiting. #endif } */ // Notice that this code below is system-independent. BOOL SRVEnterHandler (HServer hSrv) { // This handler executes after every client is active, and exits when the last client deregisters MEMREF mrOffset; SServer* pSrv = (SServer*)hSrv; if (!pSrv) return FALSE; while (pSrv->iClients) { HDS hSubDS; HClient hClient, hNextClient = NULL, hNull = NULL; char szIdentifier[5]; // iClients keeps track of the number of active clients. // This is a busy-waiting loop. This way, we avoid deadlocks. // After SRVWaitPending, the hdsRequests contains a handle to a dataspace. Our handler is FIFO. // The loop below executes until SVReadHandle, which reads the Requests dataspace, can no longer read. // If other clients make requests now, they wait: (a) to edit the request dataspace; (b) to obtain a response. // In Windows, we accomplish this with a mutex *between clients*. A client places a request on the queue (i.e., in the dataspace), // and then halts execution, waiting for a response on a general event. While it waits for a response, it owns a mutex. The instant the // client receives its answer (i.e., after SRVSatisfy is called in the server), it continues to execute and releases the mutex. This way, // one client at a time makes requests. // The order of the clients in the dataspace, then, matches the order on the mutex; we accomplish this by requesting ownership of the mutex immediately // after adding the request, so that when the wait is over, the object can continue and wait for its request to be satisfied. // If there is no "next client" waiting on the mutex, the mutex in question (which is a property of the server) becomes non-signalled - so we can use it to check. // There are problems no less thorny than usual here. When does the client signal that its request isn't satisfied? Presumably, before waiting on the mutex. // But this means that there must be a latent event object per client. This is not troublesome, but it may be cumbersome. // We begin by protecting the dataspace. RequestDataSpace (pSrv->hds_Requests); // We want to find the last handle. To do this, we iterate through a series of handles mrOffset = 0; hClient = NULL; do { // Client = next-client hClient = hNextClient; // Read the dataspace ReadDataSpace (pSrv->hds_Requests, &hNextClient, mrOffset, sizeof(HClient) ); mrOffset += sizeof(HClient); } while (hNextClient != NULL); mrOffset -= sizeof(HClient) * 2; if (hClient) { // Remove client from list. WriteDataSpace (pSrv->hds_Requests, &hNull, mrOffset, sizeof(HClient) ); // hSubDS now contains a handle to the dataspace of the requester. hSubDS = ClientGetDS (hClient); // hClient is never NULL. RequestDataSpace (hSubDS); // By convention, the first four letters contain the request. The combination "DREG" will deregister this client; everything else is passed on to the server. ReadDataSpace (hSubDS, szIdentifier, 0, sizeof(g_szDR)); szIdentifier[4] = '\0'; ReleaseDataSpace (pSrv->hds_Requests); // We unprotect, since we don't need it anymore. if (!strcmp (szIdentifier, g_szDR)) { // Magic value. pSrv->iClients--; } else { // Prepare the data space // Call the server handler, which will potentially write the reply. pSrv->pfnRQ (hSubDS); } ReleaseDataSpace (hSubDS); // We satisfy this client by signaling that it may continue. SRVSatisfy (hSrv, hClient); //printf ("Satisfied client %x\n", (unsigned int)hClient); } else { ReleaseDataSpace (pSrv->hds_Requests); // We unprotect, since we don't need it anymore. } } ReleaseDataSpace (pSrv->hds_Requests); return TRUE; } void CLTDeregisterClient (HClient hClient, HServer hSrv) { // This is a special sort of request HDS hDS; hDS = ClientGetDS (hClient); RequestDataSpace (hDS); WriteDataSpace (hDS, (const void*)g_szDR, 0, sizeof(g_szDR) ); ReleaseDataSpace (hDS); CLTSubmitRequest (hSrv, hClient); // Free the client in the process { SClient* pClient = (SClient*)hClient; FreeDataSpace (pClient->hDataSpace); } #ifdef _WIN32 { SWinClient* pWC = (SWinClient*)hClient; CloseHandle (pWC->hDataEvent); } #endif } BOOL CLTMakeRequest (HServer hSrv, HClient hClient) { #ifdef _WIN32 //printf ("Client requesting: %x\n", (unsigned int)hClient); //SetEvent (ServerGetClientEvent (hSrv) ); // This makes sure the server is awake. Once the last client has made its request and an idle period ensues, // the client will fall asleep. return ResetEvent (ClientGetDataEvent (hClient) ); // This event indicates we are waiting for an answer. #endif } void CLTRequestInQueue (HServer hSrv, HClient hClient) { #ifdef _WIN32 // This is an associated mutex. //printf ("Client is waiting its turn: %x\n", (unsigned int)hClient); WaitForSingleObjectEx (ServerGetNerf (hSrv), INFINITE, FALSE); //printf ("Client's turn: %x\n", (unsigned int)hClient); #endif } void CLTReleaseFromQueue (HServer hSrv, HClient hClient) { #ifdef _WIN32 ReleaseMutex (ServerGetNerf (hSrv) ); #endif } void CLTAwaitResponse (HServer hSrv, HClient hClient) { #ifdef _WIN32 // Under Windows, we wait on an event. //printf ("Client waiting for answer: %x, on event %x\n", (unsigned int)hClient, (unsigned int)ClientGetDataEvent (hClient) ); WaitForSingleObjectEx (ClientGetDataEvent (hClient), INFINITE, FALSE); //printf ("Client is satisfied: %x\n", (unsigned int)hClient); #endif } BOOL CLTSubmitRequest (HServer hSrv, HClient hClient) { // We treat HServer as a cookie, but we admit that all the information associated with it is either valid across // or has been duplicated for our operation. (Of course, under UNIX the state variables will be invalid, but we aren't and oughtn't be interested in the state variables.) HDS hRequests; HClient hNextClient; MEMREF mrOffset; // Gain permission CLTRequestInQueue (hSrv, hClient); hRequests = ServerGetRequestDS (hSrv); // Get access. RequestDataSpace (hRequests); // Either the clients or the server might be using the MMF, but we expect every user to return. // File the request. As above mrOffset = 0; do { // Read the dataspace ReadDataSpace (hRequests, &hNextClient, mrOffset, sizeof(HClient) ); mrOffset += sizeof(HClient); } while (hNextClient != NULL ); // Final mrOffset to write at is one less. mrOffset -= sizeof(HClient); // mrOffset now contains the first free position. WriteDataSpace (hRequests, &hClient, mrOffset, sizeof(HClient)); // mrOffset is the first non-zero value. ReleaseDataSpace (hRequests); // Indicate request. CLTMakeRequest (hSrv, hClient); CLTAwaitResponse (hSrv, hClient); CLTReleaseFromQueue (hSrv, hClient); // The next client can now execute. return TRUE; } HClient SRVMakeClient (HServer hSrv, PROCClientBase pfnClient) { // We reference clients only by counting them. SServer* pSrv; HClient hClient; HDS hDS; pSrv = (SServer*)hSrv; // Create a dataspace for the client hDS = MakeDataSpace (); hClient = SRVSpawn (hSrv, hDS, pfnClient); // Register pSrv->iClients++; return hClient; } #ifdef _WIN32 DWORD __stdcall CLT_ThreadBase (void* pvParam) { // pvParam points to a SWinClientBundle, which contains an hServer and an hClient. SWinClientBundle* pWCB = (SWinClientBundle*)pvParam; // Launch the client. (ClientGetStartRoutine (pWCB->hClient))(pWCB->hSrv, pWCB->hClient); // Deregister the client as it terminates CLTDeregisterClient (pWCB->hClient, pWCB->hSrv); // Free the information structure free (pWCB); // Finish. return 0; } #endif