A Generalized Client Communication Class

In order to communicate with the client applications created in this chapter, we need to create some client software.

One way to allow clients to communicate using the protocol decided at run time is to create a couple of layers of DLLs. One layer can act as a generic interface. That layer can call down to a protocol-specific DLL when you make the decision about the protocol to use. This is the approach I take in my book, Windows Client/Server Developer's Guide . The protocol-independent DLLs documented in that book are still in use today. They've been used by C++, Borland Delphi, and Microsoft Visual Basic clients. Since then, I've thought about other ways to create protocol independence. Creating a C++ class hierarchy is one way to enable an application to work using WinSock or named pipes as appropriate.

Listing 12-5 shows the header file for the client communication, ClientComm.h. A base class, CClientComm , defines the basic operations required for any communication to take place. Open , Read , Write , and Close are virtual functions in the base class. There are two descendant classes, one for WinSock and one for named pipes. Both implement the virtual functions declared in the base class and both have any protocol-specific properties set by the constructor. Protocol-specific properties include naming information, named pipe handle, or WinSock socket. The server name is in the base class.

Listing 12-5

ClientComm.h

 typedefstruct_DATA_PACKET{ WORDServerHandle; WORDServiceCode; WORDReturnCode; DWORDDataLen; DWORDBufLen; charBuffer[1024]; }DATA_PACKET; classCClientComm{ public: boolfConnected; voidShowError(LPSTRdoingWhat); LPSTRGetServerName(LPSTRbuffer); CClientComm(LPCSTRszServerParam) { fConnected=FALSE; strncpy(szServer,szServerParam,255); } virtualDWORDOpen(); virtualDWORDRead(LPSTRdata, DWORDcBytesToRead, LPDWORDcBytesRead); virtualDWORDWrite(LPSTRdata, DWORDcBytesToWrite, LPDWORDcBytesWritten); virtualDWORDClose(); private: charszServer[255]; }; classCWinSockClientComm:publicvirtualCClientComm{ public: CWinSockClientComm(LPSTRszServerParam,intport); DWORDOpen(); DWORDRead(LPSTRdata, DWORDcBytesToRead, LPDWORDcBytesRead); DWORDWrite(LPSTRdata, DWORDcBytesToWrite, LPDWORDcBytesWritten); DWORDClose(); private: WSADATAwsaData; SOCKETm_cliSocket; intm_iPort; }; classCNPClientComm:publicvirtualCClientComm{ public: ~CNPClientComm(); CNPClientComm(LPCSTRszServerParam,LPCSTRszPipeNameParem); DWORDOpen(); DWORDRead(LPSTRdata, DWORDcBytesToRead, LPDWORDcBytesRead); DWORDWrite(LPSTRdata, DWORDcBytesToWrite, LPDWORDcBytesWritten); DWORDClose(); private: charm_szPipeName[255]; HANDLEm_hPipe; }; 

It is important to note that this is a client communication class. The details of implementing the server side of each protocol are different from the client-side implementation. For one thing, providing separate threads for communication is usually not important in a client. The client simply waits while the communication takes place.

You'll find both class implementations on the accompanying CD-ROM, but I will describe the named pipe implementation, CNPClientComm . The constructor simply sends the server name to the base constructor and sets the pipe name. The fragment below is the CNPClientComm implementation of Open.

 DWORDCNPClientComm::Open() { charszFullPipeName[255]; charbuffer[255]; intret=-1; wsprintf(szFullPipeName, "\\%s\pipe\%s", GetServerName(buffer), m_szPipeName); m_hPipe=CreateFile(szFullPipeName,//Thepipename GENERIC_READGENERIC_WRITE,//Readandwrite 0,//Don'tshare NULL,//Defaultsecurity OPEN_EXISTING,//Openanexistingpipe 0,//Defaultattributes NULL);//Notemplatefile if(m_hPipe==INVALID_HANDLE_VALUE) { charbuffer[255]; sprintf(buffer,"CreateFile(%s)",szFullPipeName); if(GetLastError()!=2) { ShowError(buffer); } ret=-1; } else { fConnected=TRUE; } return(fConnected==TRUE); } 

The pipe name is built up from the server name and the pipe name. Instead of a named pipes-specific function, CreateFile is called to open the pipe:

 HANDLECreateFile(LPCTSTRlpFileName,//Pointertonameofthefile DWORDdwDesiredAccess,//Access(read-write)mode DWORDdwShareMode,//Sharemode LPSECURITY_ATTRIBUTESlpSecurityAttributes, //Pointertosecurityattributes DWORDdwCreationDisposition,//Howtocreate DWORDdwFlagsAndAttributes,//Fileattributes HANDLEhTemplateFile//Handletofilewithattributes //tocopy); 

Most of the parameters are used exactly as you would expect. Table 12-2 describes some additional flags for named pipes for dwFlagsAndAttributes .

Table 12-2 Additional Flags for Opening Client-Side Named Pipes

Flag Meaning
SECURITY_ANONYMOUS Client impersonated at Anonymous level
SECURITY_IDENTIFICATION Impersonate client at the Identification impersonation level
SECURITY_IMPERSONATION Impersonate client at the Impersonation level
SECURITY_DELEGATION Impersonate client at the Delegation impersonation level
SECURITY_CONTEST_TRACKING Specifies that security tracking mode is dynamic; default is static
SECURITY_EFFECTIVE_ONLY Only enabled aspects of the client's security context are available (This allows the client to limit the groups and privileges the server can impersonate.)

The balance of the Open method of NPClientComm checks for errors. The Read method uses PeekNamedPipe to test for data being available and pauses briefly if no data is waiting to give it a chance to arrive .

 BOOLPeekNamedPipe(HANDLEhNamedPipe,//Handletopipetocopyfrom LPVOIDlpBuffer,//Pointertodatabuffer DWORDnBufferSize,//Size,inbytes,ofdatabuffer LPDWORDlpBytesRead,//Pointertonumberofbytesread LPDWORDlpTotalBytesAvail,//Pointertototalnumberof //bytesavailable LPDWORDlpBytesLeftThisMessage//Pointertounreadbytesin //thismessage); 

The pipe handle is passed as hNamedPipe . The second parameter, lpBuffer , points to a buffer to accept the data if available, and nBufferSize is the size of that buffer. The last three parameters are pointers to DWORDs that accept the number of bytes read, total bytes available, and bytes left in this message. If the function succeeds and data is available, the return value is nonzero. In any case, the function returns immediately. The actual reading and writing of data is done exactly as it would be for a normal file ”using ReadFile and WriteFile .

Figure 12-2 at the top of the next page shows a simple MFC client program. The first three fields require entry. When you click on Connect, the last field will be filled in with the text "Message Received!" if everything works.

click to view at full size.

Figure 12-2 A simple MFC dialog client that connects with NPService from Listing 12-4.

The code attached to the Connect button is simple:

 voidCNPClientDlg::OnOK() { DWORDdwValue; DWORDdwBytes; DATA_PACKETdata; CStringstrServer; CStringstrPipe; CStringstrPause; CStringstrServerResponse; m_ctlServer.GetWindowText(strServer); m_ctlPipe.GetWindowText(strPipe); m_ctlPause.GetWindowText(strPause); m_OK.EnableWindow(FALSE); dwValue=atol(strPause); data.ServiceCode=1; data.BufLen=sizeof(DATA_PACKET); data.DataLen=sizeof(DWORD); memcpy(&data.Buffer[0],(void*)&dwValue,sizeof(DWORD)); CNPClientCommMyClient((LPCSTR)strServer,(LPCSTR)strPipe); MyClient.Open(); MyClient.Write((char*)&data,sizeof(DATA_PACKET),&dwBytes); dwBytes=sizeof(DATA_PACKET); MyClient.Read((char*)&data,sizeof(DATA_PACKET),&dwBytes); m_ctlServerResp.SetWindowText(data.Buffer); MyClient.Close(); m_OK.EnableWindow(TRUE); //CDialog::OnOK(); } 

After setting the value of the data variable, an instance of CNPClientComm is declared. Open is called with the server name and pipe as entered in the dialog. Write is called with the data variable, followed by a call to Read to get the response. The result, returned in data.Buffer , is then placed into the final read-only edit box with the response from the server.



Inside Server-Based Applications
Inside Server-Based Applications (DV-MPS General)
ISBN: 1572318171
EAN: 2147483647
Year: 1999
Pages: 91

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net