Files
mars-flaim/flaim/src/fcs_tcp.cpp
dsandersoremutah c55dab446f Renamed version4 to flaim and version5 to xflaim
git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@7 0109f412-320b-0410-ab79-c3e0c5ffbbe6
2006-01-27 21:06:39 +00:00

961 lines
20 KiB
C++

//-------------------------------------------------------------------------
// Desc: TCP/IP networking.
// Tabs: 3
//
// Copyright (c) 1998-2006 Novell, Inc. All Rights Reserved.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 2 of the GNU General Public
// License as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, contact Novell, Inc.
//
// To contact Novell about this file by physical or electronic mail,
// you may find current contact information at www.novell.com
//
// $Id: fcs_tcp.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $
//-------------------------------------------------------------------------
// These must be defined BEFORE any includes. Unfortunately, this
// also means that we can't use our FLM_HPUX define because it hasn't
// been set yet...
#if defined( __hpux) || defined( hpux)
#define _XOPEN_SOURCE_EXTENDED 1
#define _INCLUDE_HPUX_SOURCE
#endif
#include "flaimsys.h"
#if defined( FLM_NLM) && !defined ( __MWERKS__)
// Disable errors for "expression for 'while' is always false"
// Needed for FD_SET macro
#pragma warning 555 9
#endif
#ifdef FLM_WIN
#pragma warning(disable : 4127) // conditional expression is constant (from FD_SET())
#endif
/********************************************************************
Desc: Constructor
*********************************************************************/
FCS_TCP::FCS_TCP( void)
{
m_pszIp[ 0] = '\0';
m_pszName[ 0] = '\0';
m_pszPeerIp[ 0] = '\0';
m_pszPeerName[ 0] = '\0';
m_uiIOTimeout = 10;
m_iSocket = INVALID_SOCKET;
m_ulRemoteAddr = 0;
m_bInitialized = FALSE;
m_bConnected = FALSE;
#ifndef FLM_UNIX
if( !WSAStartup( MAKEWORD(2, 0), &m_wsaData))
{
m_bInitialized = TRUE;
}
#endif
}
/********************************************************************
Desc: Destructor
*********************************************************************/
FCS_TCP::~FCS_TCP( void )
{
if( m_bConnected)
{
close();
}
#ifndef FLM_UNIX
if( m_bInitialized)
{
WSACleanup();
}
#endif
}
/********************************************************************
Desc: Gets information about the local host machine.
*********************************************************************/
RCODE FCS_TCP::_GetLocalInfo( void)
{
struct hostent * pHostEnt;
FLMUINT32 ui32IPAddr;
RCODE rc = FERR_OK;
m_pszIp[ 0] = '\0';
m_pszName[ 0] = '\0';
if( m_pszName[ 0] == '\0')
{
if( gethostname( m_pszName, (unsigned)sizeof( m_pszName)))
{
rc = RC_SET( FERR_SVR_SOCK_FAIL);
goto Exit;
}
}
if( m_pszIp[ 0] == '\0' &&
(pHostEnt = gethostbyname( m_pszName)) != NULL)
{
ui32IPAddr = (FLMUINT32)(*((u_long*)pHostEnt->h_addr));
if( ui32IPAddr != (FLMUINT32)-1)
{
struct in_addr InAddr;
InAddr.s_addr = ui32IPAddr;
f_strcpy( m_pszIp, inet_ntoa( InAddr));
}
}
Exit:
return( rc);
}
/********************************************************************
Desc: Gets information about the remote machine.
*********************************************************************/
RCODE FCS_TCP::_GetRemoteInfo( void)
{
struct sockaddr_in SockAddrIn;
char * InetAddr = NULL;
struct hostent * HostsName;
RCODE rc = FERR_OK;
m_pszPeerIp[ 0] = '\0';
m_pszPeerName[ 0] = '\0';
SockAddrIn.sin_addr.s_addr = (unsigned)m_ulRemoteAddr;
/*
inet_ntoa() - converts a 32-bit value in in_addr format into an ASCII
string representing the address in dotted notation.
VISIT:
NetWare: Macro in arpa/inet.h. "Apps with multiple threads should use
NWinet_ntoa instead of inet_ntoa. Then we can get rid of the semaphore!
*/
InetAddr = inet_ntoa( SockAddrIn.sin_addr );
f_strcpy( m_pszPeerIp, InetAddr );
/*
Try to get the peer's host name by looking up his IP
address. If found, copy IP Host name "BEVIS@NOVELL.COM" to TCPInfo
otherwise, use his IP address as IP name.
VISIT:
Netware: "If your app has multiple threads, use either NWgethostbyaddr
or NetDBgethostbyaddr(). This does the blocking? This may be done
already in netdb.h - it is hard to tell.
*/
HostsName = gethostbyaddr( (char *)&SockAddrIn.sin_addr.s_addr,
(unsigned)sizeof( u_long), AF_INET );
if( HostsName != NULL)
{
f_strcpy( m_pszPeerName, (char*) HostsName->h_name );
}
else
{
if (!InetAddr)
{
InetAddr = inet_ntoa( SockAddrIn.sin_addr);
}
f_strcpy( m_pszPeerName, InetAddr );
}
return( rc);
}
/********************************************************************
Desc: Tests for socket data readiness
*********************************************************************/
RCODE FCS_TCP::_SocketPeek(
FLMINT iTimeoutVal,
FLMBOOL bPeekRead
)
{
struct timeval TimeOut;
int iMaxDescs;
fd_set GenDescriptors;
fd_set * DescrRead;
fd_set * DescrWrt;
RCODE rc = FERR_OK;
if( m_iSocket != INVALID_SOCKET)
{
FD_ZERO( &GenDescriptors );
FD_SET( m_iSocket, &GenDescriptors );
iMaxDescs = (int)(m_iSocket + 1);
DescrRead = bPeekRead ? &GenDescriptors : NULL;
DescrWrt = bPeekRead ? NULL : &GenDescriptors;
TimeOut.tv_sec = (long)iTimeoutVal;
TimeOut.tv_usec = (long)0;
if( select( iMaxDescs, DescrRead, DescrWrt, NULL, &TimeOut) < 0 )
{
rc = RC_SET( FERR_SVR_SELECT_ERR);
goto Exit;
}
else
{
if( !FD_ISSET( m_iSocket, &GenDescriptors))
{
rc = bPeekRead
? RC_SET( FERR_SVR_READ_TIMEOUT)
: RC_SET( FERR_SVR_WRT_TIMEOUT);
}
}
}
else
{
rc = RC_SET( FERR_SVR_CONNECT_FAIL);
}
Exit:
return( rc);
}
/********************************************************************
Desc: Writes data to the connection.
*********************************************************************/
RCODE FCS_TCP::write(
FLMBYTE * pucDataBuffer,
FLMUINT uiDataCnt,
FLMUINT * puiWrtCnt)
{
FLMUINT uiPartialCnt;
FLMUINT uiToWrite;
FLMUINT uiHaveWritten = 0;
RCODE rc = FERR_OK;
if( m_iSocket == INVALID_SOCKET)
{
rc = RC_SET( FERR_SVR_CONNECT_FAIL);
}
uiToWrite = uiDataCnt;
*puiWrtCnt = 0;
while( uiToWrite > 0)
{
/* The internal write call checks the arguments. */
if( RC_BAD( rc = _write( pucDataBuffer,
uiToWrite, &uiPartialCnt)))
{
goto Exit;
}
pucDataBuffer += uiPartialCnt;
uiHaveWritten += uiPartialCnt;
uiToWrite = (FLMUINT)(uiDataCnt - uiHaveWritten);
*puiWrtCnt = uiHaveWritten;
}
Exit:
return( rc);
}
RCODE FCS_TCP::_write(
FLMBYTE * pucBuffer,
FLMUINT uiDataCnt,
FLMUINT *puiWrtCnt)
{
FLMINT iRetryCount = 0;
FLMINT iWrtCnt = 0;
RCODE rc = FERR_OK;
flmAssert( m_iSocket != INVALID_SOCKET && pucBuffer && uiDataCnt);
Retry:
*puiWrtCnt = 0;
if ( RC_OK( rc = _SocketPeek( m_uiIOTimeout, FALSE)))
{
iWrtCnt = send( m_iSocket, (char *)pucBuffer, (int)uiDataCnt, 0 );
switch ( iWrtCnt )
{
case -1:
*puiWrtCnt = 0;
rc = RC_SET( FERR_SVR_WRT_FAIL);
break;
case 0:
rc = RC_SET( FERR_SVR_DISCONNECT);
break;
default:
*puiWrtCnt = (FLMUINT)iWrtCnt;
break;
}
}
if( RC_BAD( rc) && rc != FERR_SVR_WRT_TIMEOUT)
{
#ifndef FLM_UNIX
FLMINT iSockErr = WSAGetLastError();
#else
FLMINT iSockErr = errno;
#endif
#if defined( FLM_WIN) || defined( FLM_NLM)
if( iSockErr == WSAECONNABORTED)
#else
if( iSockErr == ECONNABORTED)
#endif
{
rc = RC_SET( FERR_SVR_DISCONNECT);
}
#if defined( FLM_WIN) || defined( FLM_NLM)
else if( iSockErr == WSAEWOULDBLOCK && iRetryCount < 5)
#else
else if( iSockErr == EWOULDBLOCK && iRetryCount < 5)
#endif
{
iRetryCount++;
f_sleep( (FLMUINT)(100 * iRetryCount));
goto Retry;
}
}
return( rc);
}
/********************************************************************
Desc: Reads data from the connection
*********************************************************************/
RCODE FCS_TCP::read(
FLMBYTE * pucBuffer,
FLMUINT uiDataCnt,
FLMUINT * puiReadCnt)
{
FLMINT iReadCnt = 0;
RCODE rc = FERR_OK;
flmAssert( m_bConnected && pucBuffer && uiDataCnt);
if( RC_OK( rc = _SocketPeek( m_uiIOTimeout, TRUE)))
{
iReadCnt = (FLMINT)recv( m_iSocket,
(char *)pucBuffer, (int)uiDataCnt, 0);
switch ( iReadCnt)
{
case -1:
iReadCnt = 0;
#if defined( FLM_WIN) || defined( FLM_NLM)
if ( WSAGetLastError() == WSAECONNRESET)
#else
if( errno == ECONNRESET)
#endif
{
rc = RC_SET( FERR_SVR_DISCONNECT);
}
else
{
rc = RC_SET( FERR_SVR_READ_FAIL);
}
break;
case 0:
rc = RC_SET( FERR_SVR_DISCONNECT);
break;
default:
break;
}
}
if( puiReadCnt)
{
*puiReadCnt = (FLMUINT)iReadCnt;
}
return( rc);
}
/********************************************************************
Desc: Reads data from the connection - Timeout valkue is zero, no error
is generated if timeout occurs.
*********************************************************************/
RCODE FCS_TCP::readNoWait(
FLMBYTE * pucBuffer,
FLMUINT uiDataCnt,
FLMUINT * puiReadCnt)
{
FLMINT iReadCnt = 0;
RCODE rc = FERR_OK;
flmAssert( m_bConnected && pucBuffer && uiDataCnt);
if( puiReadCnt)
{
*puiReadCnt = 0;
}
if( RC_OK( rc = _SocketPeek( (FLMUINT)0, TRUE)))
{
iReadCnt = recv( m_iSocket, (char *)pucBuffer, (int)uiDataCnt, 0);
switch ( iReadCnt)
{
case -1:
*puiReadCnt = 0;
#if defined( FLM_WIN) || defined( FLM_NLM)
if ( WSAGetLastError() == WSAECONNRESET)
#else
if( errno == ECONNRESET)
#endif
{
rc = RC_SET( FERR_SVR_DISCONNECT);
}
else
{
rc = RC_SET( FERR_SVR_READ_FAIL);
}
goto Exit;
case 0:
rc = RC_SET( FERR_SVR_DISCONNECT);
goto Exit;
default:
break;
}
}
else if (rc == FERR_SVR_READ_TIMEOUT)
{
rc = FERR_OK;
}
if( puiReadCnt)
{
*puiReadCnt = (FLMUINT)iReadCnt;
}
Exit:
return( rc);
}
/********************************************************************
Desc: Reads data and does not return until all requested data has
been read or a timeout error has been encountered.
*********************************************************************/
RCODE FCS_TCP::readAll(
FLMBYTE * pucBuffer,
FLMUINT uiDataCnt,
FLMUINT * puiReadCnt)
{
FLMUINT uiToRead = 0;
FLMUINT uiHaveRead = 0;
FLMUINT uiPartialCnt;
RCODE rc = FERR_OK;
flmAssert( m_bConnected && pucBuffer && uiDataCnt);
uiToRead = uiDataCnt;
while( uiToRead)
{
if( RC_BAD( rc = read( pucBuffer, uiToRead, &uiPartialCnt)))
{
goto Exit;
}
pucBuffer += uiPartialCnt;
uiHaveRead += uiPartialCnt;
uiToRead = (FLMUINT)(uiDataCnt - uiHaveRead);
if( puiReadCnt)
{
*puiReadCnt = uiHaveRead;
}
}
Exit:
return( rc);
}
/********************************************************************
Desc: Enables or disables Nagle's algorithm
*********************************************************************/
RCODE FCS_TCP::setTcpDelay(
FLMBOOL bOn)
{
RCODE rc = FERR_OK;
int iOn;
if( m_iSocket != INVALID_SOCKET)
{
iOn = bOn ? 1 : 0;
if( (setsockopt( m_iSocket, IPPROTO_TCP, TCP_NODELAY, (char *)&iOn,
(unsigned)sizeof( iOn) )) < 0)
{
rc = RC_SET( FERR_SVR_SOCKOPT_FAIL);
goto Exit;
}
}
else
{
rc = RC_SET( FERR_SVR_ALREADY_CLOSED);
}
Exit:
return( rc);
}
/********************************************************************
Desc: Closes any open connections
*********************************************************************/
void FCS_TCP::close(
FLMBOOL bForce)
{
if( m_iSocket == INVALID_SOCKET)
{
goto Exit;
}
#ifdef FLM_NLM
F_UNREFERENCED_PARM( bForce);
#else
if( !bForce)
{
char ucTmpBuf[ 128];
struct timeval tv;
fd_set fds;
fd_set fds_read;
fd_set fds_err;
// Close our half of the connection
shutdown( m_iSocket, 1);
// Set up to wait for readable data on the socket
FD_ZERO( &fds);
FD_SET( m_iSocket, &fds);
tv.tv_sec = 10;
tv.tv_usec = 0;
fds_read = fds;
fds_err = fds;
// Wait for data or an error
while( select( m_iSocket + 1, &fds_read, NULL, &fds_err, &tv) > 0)
{
if( recv( m_iSocket, ucTmpBuf, sizeof( ucTmpBuf), 0) <= 0)
{
break;
}
fds_read = fds;
fds_err = fds;
}
shutdown( m_iSocket, 2);
}
#endif
#ifndef FLM_UNIX
closesocket( m_iSocket);
#else
::close( m_iSocket);
#endif
Exit:
m_iSocket = INVALID_SOCKET;
m_bConnected = FALSE;
}
/********************************************************************
Desc: Creates a client object
*********************************************************************/
FCS_TCP_CLIENT::FCS_TCP_CLIENT( void) : FCS_TCP()
{
m_bConnected = FALSE;
}
/********************************************************************
Desc: Closes any connections and frees client resources
*********************************************************************/
FCS_TCP_CLIENT::~FCS_TCP_CLIENT( void )
{
(void)close();
}
/********************************************************************
Desc: Opens a new connection
*********************************************************************/
RCODE FCS_TCP_CLIENT::openConnection(
const char * pucHostName,
FLMUINT uiPort,
FLMUINT uiConnectTimeout,
FLMUINT uiDataTimeout)
{
FLMINT iSockErr;
FLMINT iTries;
FLMINT iMaxTries = 5;
struct sockaddr_in address;
struct hostent * pHostEntry;
u_long ulIPAddr;
RCODE rc = FERR_OK;
flmAssert( !m_bConnected);
m_iSocket = INVALID_SOCKET;
if( pucHostName && pucHostName[ 0] != '\0')
{
ulIPAddr = inet_addr( (char *)pucHostName);
if( ulIPAddr == (u_long)INADDR_NONE)
{
pHostEntry = gethostbyname( (char *)pucHostName);
if( !pHostEntry)
{
rc = RC_SET( FERR_SVR_NOIP_ADDR);
goto Exit;
}
else
{
ulIPAddr = *((u_long*)pHostEntry->h_addr);
}
}
}
else
{
ulIPAddr = inet_addr( (char *)"127.0.0.1");
}
/******************************************************/
/* Fill in the Socket structure with family type */
/******************************************************/
f_memset( (char*)&address, 0, sizeof( struct sockaddr_in));
address.sin_family = AF_INET;
address.sin_addr.s_addr = (unsigned)ulIPAddr;
address.sin_port = htons( (u_short)uiPort);
/*
Allocate a socket, then attempt to connect to it!
*/
if( (m_iSocket = socket( AF_INET,
SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
rc = RC_SET( FERR_SVR_SOCK_FAIL);
goto Exit;
}
/******************************************************/
/* Now attempt to connect with the specified */
/* partner host, time-out if connection */
/* doesn't complete within alloted time */
/******************************************************/
#ifdef FLM_WIN
/*
**
*/
if ( uiConnectTimeout )
{
if ( uiConnectTimeout < 5 )
{
iMaxTries = (iMaxTries * uiConnectTimeout) / 5;
uiConnectTimeout = 5;
}
}
else
{
iMaxTries = 1;
}
#endif
for( iTries = 0; iTries < iMaxTries; iTries++ )
{
iSockErr = 0;
if( connect( m_iSocket, (struct sockaddr *)&address,
(unsigned)sizeof(struct sockaddr)) >= 0)
{
/* SUCCESS! */
break;
}
#ifndef FLM_UNIX
iSockErr = WSAGetLastError();
#else
iSockErr = errno;
#endif
#ifdef FLM_WIN
/*
In WIN, we sometimes get WSAEINVAL when, if we keep
trying, we will eventually connect. Therefore,
here we'll treat WSAEINVAL as EINPROGRESS.
*/
if( iSockErr == WSAEINVAL)
{
#ifndef FLM_UNIX
closesocket( m_iSocket);
#else
::close( m_iSocket);
#endif
if( (m_iSocket = socket( AF_INET,
SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
rc = RC_SET( FERR_SVR_SOCK_FAIL);
goto Exit;
}
#if defined( FLM_WIN) || defined( FLM_NLM)
iSockErr = WSAEINPROGRESS;
#else
iSockErr = EINPROGRESS;
#endif
continue;
}
#endif
#if defined( FLM_WIN) || defined( FLM_NLM)
if( iSockErr == WSAEISCONN )
#else
if( iSockErr == EISCONN )
#endif
{
break;
}
#if defined( FLM_WIN) || defined( FLM_NLM)
else if( iSockErr == WSAEWOULDBLOCK)
#else
else if( iSockErr == EWOULDBLOCK)
#endif
{
/*
** Let's wait a split second to give the connection
** request a chance.
*/
f_sleep( 100 );
continue;
}
#if defined( FLM_WIN) || defined( FLM_NLM)
else if( iSockErr == WSAEINPROGRESS)
#else
else if( iSockErr == EINPROGRESS)
#endif
{
if( RC_OK( rc = _SocketPeek( uiConnectTimeout, FALSE)))
{
/*
** Let's wait a split second to give the connection
** request a chance.
*/
f_sleep( 100 );
continue;
}
}
rc = RC_SET( FERR_SVR_CONNECT_FAIL);
}
if( RC_BAD( rc))
{
if( m_iSocket != INVALID_SOCKET)
{
#ifndef FLM_UNIX
closesocket( m_iSocket);
#else
::close( m_iSocket);
#endif
m_iSocket = INVALID_SOCKET;
}
goto Exit;
}
m_uiIOTimeout = uiDataTimeout;
setTcpDelay( TRUE);
m_bConnected = TRUE;
Exit:
return( rc);
}
/********************************************************************
Desc: Constructor
*********************************************************************/
FCS_TCP_SERVER::FCS_TCP_SERVER( void) : FCS_TCP()
{
m_bBound = FALSE;
}
/********************************************************************
Desc: Destructor
*********************************************************************/
FCS_TCP_SERVER::~FCS_TCP_SERVER( void)
{
if( m_bBound)
{
close( TRUE);
}
}
/********************************************************************
Desc: Bind to a port prior to listening for connections
*********************************************************************/
RCODE FCS_TCP_SERVER::bind(
FLMUINT uiBindPort,
FLMBYTE * pucBindAddr)
{
struct sockaddr_in address;
RCODE rc = FERR_OK;
if( m_bBound)
{
rc = RC_SET( FERR_SVR_SOCK_FAIL);
goto Exit;
}
if( (m_iSocket = socket( AF_INET,
SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
rc = RC_SET( FERR_SVR_SOCK_FAIL);
goto Exit;
}
f_memset( &address, 0, sizeof( address));
address.sin_family = AF_INET;
if( !pucBindAddr)
{
address.sin_addr.s_addr = htonl( INADDR_ANY);
}
else
{
address.sin_addr.s_addr = inet_addr( (char *)pucBindAddr);
}
address.sin_port = htons( (u_short)uiBindPort);
// Bind to the address+port
if( ::bind( m_iSocket, (struct sockaddr *)&address,
(unsigned)sizeof( address)) != 0)
{
rc = RC_SET( FERR_SVR_BIND_FAIL);
goto Exit;
}
/*
** Bind succeeded,
** listen() prepares a socket to accept a connection and specifies a
** queue limit for incoming connections. The accept() accepts the connection.
** Listen returns immediatly.
** Duane: Note for NetWare I spoke with Sravan Vadlakonda in San Jose,
** Netware allows 32 not 5 as the max. We set this high because the
** nonpreemptive nature of NLMs means we might not get back to this
** thread in time to accept all of the pending connections. As of
** Aug 97 the tcpip.nlm displays an error when we don't clean the q
** of pending connections fast enough.
*/
#ifdef FLM_NLM
if( listen( m_iSocket, 32 ) < 0)
#endif
{
if( listen( m_iSocket, 5 ) < 0)
{
rc = RC_SET( FERR_SVR_LISTEN_FAIL);
goto Exit;
}
}
/*
Disable the packet send delay.
*/
setTcpDelay( TRUE);
m_bBound = TRUE;
Exit:
if( RC_BAD( rc) && m_iSocket != INVALID_SOCKET)
{
#ifndef FLM_UNIX
closesocket( m_iSocket);
#else
::close( m_iSocket);
#endif
m_iSocket = INVALID_SOCKET;
}
return( rc);
}
/********************************************************************
Desc: Wait for and accept a client connection
*********************************************************************/
RCODE FCS_TCP_SERVER::connectClient(
FCS_TCP * pClient,
FLMINT uiConnectTimeout,
FLMINT uiDataTimeout)
{
SOCKET iSocket;
#if defined( FLM_UNIX)
socklen_t iAddrLen;
#else
int iAddrLen;
#endif
struct sockaddr_in address;
RCODE rc = FERR_OK;
if( !m_bBound)
{
rc = RC_SET( FERR_SVR_BIND_FAIL);
goto Exit;
}
if( RC_BAD( rc = _SocketPeek( uiConnectTimeout, TRUE)))
{
goto Exit;
}
iAddrLen = (int)sizeof( struct sockaddr);
if( (iSocket = accept( m_iSocket,
(struct sockaddr *)&address, &iAddrLen)) == INVALID_SOCKET)
{
rc = RC_SET( FERR_SVR_ACCEPT_FAIL);
goto Exit;
}
pClient->m_ulRemoteAddr = address.sin_addr.s_addr;
pClient->m_iSocket = iSocket;
pClient->m_bConnected = TRUE;
pClient->m_uiIOTimeout = uiDataTimeout;
pClient->setTcpDelay( TRUE);
Exit:
return( rc);
}