blob: b21865dd18c8ec9c7f780f63a3852089fca027bf [file] [log] [blame]
/**
*
* This class demonstrates use of the CommonC++ SocketPort class.
*
* Copyright 2001 - Nick Liebmann <nick@ukmail.org>
*
* The SampleSocketPort is an implementation of the CommonC++ SocketPort class
* that irons out some problems that I found with disconnection, and also demonstrates
* a way of using the SocketPort to reliably extract and send data from/to a TCP/IP socket.
*
* In addition to this the SampleSocketPort includes some additional functionality
* to determine whether the data stream has become corrupted
* (missing terminator / incorrect formatting). For this feature a timer is used which if it is
* allowed to expire, indicates that a 'packet' took too long to arrive, and as such the data
* in the buffer is 'corrupt'.
*
* The SampleSocketPort can be used as-is...Modify the contents of the pending()
* function if your data is formatted differently to the default (i.e. not terminated with \r\n)
*
*
* This sample code is distributed under the same terms and conditions of the CommonC++ library.
*
* CHANGE HISTORY:
*
*
* 07/01/02 NL There have been slight changes to the way CommonC++ starts threads,
* a possible bug in InetHostAddress constructor, and a bug fix for SocketService
* #496276. The following changes address those issues
*
* New thread start semantics. SocketService Thread is now explicitly started
* by the SampleSocketServiceServer.
*
* Added SampleSocketServiceServer::StartServer() to start the server and wait
* for the thread to get up and running.
*
* Added SampleSocketServiceServer::StopServer() to cleanly stop the server, and
* ensure that there are no partially constructed SocketPorts left lying around
*
* Removed setDetectOutput(true)...this does not seem to be required anymore
* as the SocketService functions correctly now.
*
* InetHostAddress constructor does not treat INADDR_ANY as it used to.
*
* 07/01/02 NL main() - now waits for a 'quit' command, and deletes the Server object.
*/
#include "SampleSocketPort.h"
SampleSocketPort::SampleSocketPort(SocketService *pService, TCPSocket & tcpSocket) :
SocketPort(pService, tcpSocket)
{
tpport_t port;
InetHostAddress ia = getPeer( & port );
cerr << "connecting from " << ia.getHostname() << ":" << port << endl;
// Set up non-blocking reads
setCompletion( false );
//1.9.3 THIS LINE DOES NOT SEEM TO BE REQUIRED ANYMORE!
//This sorts out a bug which prevents connections after a disconnect
//setDetectOutput(true);
m_bOpen = true;
m_bDoDisconnect = false;
m_bTimedOut = false;
m_bReceptionStarted = false;
m_nLastBytesAvail = 0;
m_pBuf = new char[MAX_RXBUF];
}
SampleSocketPort::~SampleSocketPort()
{
endSocket();
delete [] m_pBuf;
}
void SampleSocketPort::pending(void)
{
//cerr << "Pending called " << endl;
if(!m_bOpen)
return;
// Read all available bytes into our buffer
int nBytesAvail = peek(m_pBuf, MAX_RXBUF);
//cerr << "Pending .. " << nBytesAvail << endl;
if(!m_bReceptionStarted)
{ //Start the receive timer
ResetReadTimeout(MAX_RXTIMEOUT); //Got 'n' seconds to get all the data else we timeout
m_bReceptionStarted = true;
}
else {
if(m_bTimedOut) //The receive timer has expired...this is a timeout condition
{
ResetReadTimeout(MAX_RXTIMEOUT); //Clear the timeout flag
m_nLastBytesAvail = 0; //Reset the flags
m_bReceptionStarted = false;
OnRxTimeout(); //Do whatever 'we' do for a timeout (probably a flush or disconnect)...
return;
}
}
if(m_nLastBytesAvail == nBytesAvail) //Check if any more data has been received since last time
{ //No point in parsing unless this has changed!
//Maybe yield in here!
//Thread::yield();
if(nBytesAvail == 0) //If we have been called with 0 bytes available (twice now)
{ //a disconnection has occurred
if(!m_bDoDisconnect) {
CloseSocket(); //Force the close
}
}
return;
}
//Depending on your application you may want to attempt to process the extra data
//(or change your MAX_RXBUF).
//
//Here I just flush the whole lot, because I assume a 'legal' client wont send more than
//we can receive....maybe someone is trying to flood / overrun us!
if(nBytesAvail > MAX_RXBUF) {
cerr << "TCP/IP overflow..." << endl;
FlushRxData();
m_nLastBytesAvail = 0;
m_bReceptionStarted = false;
return;
}
m_nLastBytesAvail = nBytesAvail;
//In this loop you may parse the received data to determine whether a whole
//'packet' has arrived. What you do in here depends on what data you are sending.
//Here we will just look for a /r/n terminator sequence.
for(int i=0; i < nBytesAvail; i++) {
/***************************SHOULD BE CUSTOMISED*******************/
if(m_pBuf[i] == '\r') {
if(i+1 < nBytesAvail) {
if(m_pBuf[i+1] == '\n')
{ //Terminator sequence found
/**************************************************************/
// COMPULSORY ... Clear the flag and count..
// do this when you have received a good packet
m_nLastBytesAvail = 0;
m_bReceptionStarted = false;
/**************************************************************/
// Now receive the data into a buffer and call our receive function
int nLen = i+2;
char *pszRxData = new char[nLen+1]; //Allow space for terminator
receive(pszRxData, nLen); //Receive the data
pszRxData[nLen] = '\0'; //Terminate it
OnDataReceived(pszRxData, nLen);
delete [] pszRxData;
return;
}
}
}
/***************************END CUSTOMISATION*******************/
}
}
void SampleSocketPort::disconnect(void)
{
if(m_bOpen) {
m_bDoDisconnect = true;
CloseSocket();
}
}
void SampleSocketPort::expired(void)
{
if(m_bDoDisconnect && m_bOpen) {
CloseSocket();
}
else if(m_bOpen && m_bReceptionStarted) {
//Timer must have expired because the rx data has not all been received
m_bTimedOut = true;
}
}
bool SampleSocketPort::CloseSocket(void)
{
if(m_bOpen && m_bDoDisconnect)
{ //This is where the disconnection really occurs
m_bOpen = false; //If m_bDoDisconnect == true we know this has been called
OnConnectionClosed(); //through the timer, so 'delete this' is safe!
delete this;
}
else if(m_bOpen) {
m_bDoDisconnect = true; //Just set the timer and the flag so we can
setTimer(DISCONNECT_MS); //disconnect safely, in DISCONNECT_MS
}
return(true);
}
ssize_t SampleSocketPort::DoSend(void *buf, size_t len)
{
//If we are disconnecting, just pretend all the bytes were sent
if(m_bDoDisconnect)
return((ssize_t)len);
ssize_t nSent = send(buf, len);
while(!isPending(Socket::pendingOutput, 0)) //Wait for output to complete
{
if(m_bDoDisconnect || !m_bOpen) {
//If we are disconnecting, just pretend all the bytes were sent
return((ssize_t)len);
}
//I like to yield whenever waiting for things...
//this is optional and may not suit your implementation!
Thread::yield();
}
return(nSent);
}
bool SampleSocketPort::WriteData(const char *szTxData, const size_t nByteCount)
{
//First calculate how many bytes we are to send
ssize_t nLen = nByteCount;
if(nLen == -1)
nLen = (ssize_t)strlen(szTxData);
size_t nBytesToSend = nLen;
while(m_bOpen && nLen) {
nLen -= DoSend((void *)&(szTxData[nBytesToSend - nLen]), nLen);
}
// If we are sending a terminator.....uncomment the following lines
// char chTerminator = '\n';
// while(DoSend((void *)&chTerminator, 1) != 1);
return(true);
}
#define WITH_EXAMPLE
#ifdef WITH_EXAMPLE
/************ THE FOLLOWING CODE DEMONSTRATES THE USE OF THE ABOVE CLASS ********************
****
**** To test it, compile with:
****
**** g++ SampleSocketPort.cpp -lccgnu -lpthread -ldl -oSampleSocketPort -ggdb -I/usr/local/include/cc++/
**** Run the program.
****
**** From another terminal telnet to port 3999 of the server
****
**** 'telnet localhost 3999'
****
**** Anything you type should be sent back to you in reverse!
****
**** To test the corrupt data detection, send a control code (like ^D),
**** if the terminating charcters are not detected within the specified time
**** the receive timeout will occur.
****
****/
//define the following to include the example classes and functions
int g_nOpenPorts = 0; //Dirty global to allow us to quit simply
class ReverserPort : public SampleSocketPort
{
public:
ReverserPort(SocketService *pService, TCPSocket & tcpSocket) :
SampleSocketPort(pService, tcpSocket) {
g_nOpenPorts++;
}
virtual ~ReverserPort() {
g_nOpenPorts--;
}
virtual void OnConnectionClosed(void)
{ cerr << "Connection Closed!" << endl; }
/**
* Called when a 'packet' of data has been received.
* This implementation simply reverses all the data and sends it back
*/
virtual void OnDataReceived(char *pszData, unsigned int nByteCount) {
//Reverse the data and send it back
size_t nLen = strlen(pszData);
char *szToSend = new char[nLen+1];
//No need to reverse the \r\n or \0
size_t nIndex = nLen-3;
size_t i;
for(i=0; i < nLen - 2; i++) {
szToSend[i] = pszData[nIndex - i];
}
szToSend[i++] = '\r';
szToSend[i++] = '\n';
szToSend[nLen] = '\0';
WriteData(szToSend, nLen);
delete [] szToSend;
}
};
class ReverserServer : public SampleSocketServiceServer
{
public:
ReverserServer(InetHostAddress & machine, int port) :
TCPSocket(machine, port), Thread(), SampleSocketServiceServer(machine, port) {}
virtual ~ReverserServer() {}
virtual SocketPort *CreateSocketPort(SocketService *pService, TCPSocket & Socket) {
return(new ReverserPort(pService, Socket));
}
};
int main(void)
{
InetHostAddress LocalHost;
LocalHost = htonl(INADDR_ANY);
ReverserServer *Server = NULL;
try {
Server = new ReverserServer(LocalHost, 3999);
Server->StartServer();
}
catch(...) {
cerr << "Failed to start server" << endl;
return(false);
}
cerr << "Waiting for connections...type \"quit\" to exit." << endl;
char cmd[255];
cin.getline(cmd, 255);
while(strcmp(cmd, "quit") != 0) {
cin.getline(cmd, 255);
}
Server->StopServer();
delete Server;
return 0;
}
#endif //WITH_EXAMPLE