//*********************************************************************
// socket.cpp
//
// Note: it may improve performance to buffer the character input
// instead of calling the WinSock recv() function for each single
// character. When there is no buffer, allocate a fixed size buffer,
// then copy the data from WinSock to the buffer up to the size of the
// buffer. Then re-code the Receive() and GetChar() methods to draw
// from the buffer instead of calling the WinSock recv() function. When
// the buffer runs dry, refresh it. That means I need a size and an
// ofst value along with the buffer. I also need to write a buffer
// server that both methods can use.
//
//*********************************************************************
//
// Sockets library interface class
//
//*********************************************************************
#include "prefixb.h"
#include "console.h"
#include "socket.h"
#include "syslog.h"
#define BACKLOG 10
//#define LENGTH_STRING_SIZE 10
short CSocket::nrSocketsOpen = 0;
short CSocket::timeOut = 100;
//*********************************************************************
// CSocket implementation
//*********************************************************************
//---------------------------------------------------------------------
// Constructor
//
CSocket::CSocket(int _handle, bool _blocking):
CAtom (),
handle (_handle),
name (0),
port (-1),
blocking (_blocking),
lastError(0),
errNo (0) {
cls = iSocket;
nrSocketsOpen++;
} // CSocket
//---------------------------------------------------------------------
// Destructor
//
CSocket::~CSocket() {
Disconnect();
delete[] name;
name = 0;
delete[] lastError;
lastError = 0;
nrSocketsOpen--;
} // ~CSocket
//---------------------------------------------------------------------
//
RTTI_CPP(Socket, Atom)
//---------------------------------------------------------------------
// Static method to start WinSock
//
bool CSocket::Initialize(short _timeOut) {
Assert(nrSocketsOpen == 0);
timeOut = _timeOut;
#ifdef FV_MSWIN32
WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA wsaData;
int err = ::WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
return false;
#endif // FV_MSWIN32
SysLog::Log('K', "CSocket.Initialize: WinSock started");
return true;
} // Initialize
//---------------------------------------------------------------------
// Static method to stop WinSock
//
void CSocket::UnInitialize(void) {
Assert(nrSocketsOpen == 0);
#ifdef FV_MSWIN32
::WSACleanup();
#endif // FV_MSWIN32
} // UnInitialize
//---------------------------------------------------------------------
// Store an error message
//
void CSocket::SError(Pchar _caller) {
Pchar m;
#if defined(FV_MSWIN32)
errNo = ::WSAGetLastError();
m = GetErrorString(errNo);
#elif defined(FV_UNIX)
errNo = errno;
m = sys_errlist[errNo];
#endif // FV_UNIX
char line[sizeString];
sprintf(line, "%s: %s", _caller, m);
Assert(strlen(line) < sizeString);
delete[] lastError;
lastError = new char[strlen(line)+1];
strcpy(lastError, line);
} // SError
//---------------------------------------------------------------------
// Store an error message
//
void CSocket::MsgError(Pchar _msg) {
delete[] lastError;
if (StrEmpty(_msg)) {
lastError = new char[1];
lastError[0] = 0;
}
else {
lastError = new char[strlen(_msg)+1];
strcpy(lastError, _msg);
}
} // MsgError
//---------------------------------------------------------------------
//
bool CSocket::blockSocket(bool _block) {
if (_block)
SysLog::Log('K', "CSocket.blockSocket * Blocking handle=%d", handle);
else
SysLog::Log('K', "CSocket.blockSocket * Not blocking handle=%d", handle);
// Set the socket to block or not block as specified
ulong block = (_block ? 0 : 1);
#ifdef FV_MSWIN32
if (ioctlsocket(handle, FIONBIO, &block)) {
SError("ioctlsocket()");
return false;
}
#else
if (ioctl (handle, FIONBIO, &block)) {
SError("ioctl()");
return false;
}
#endif // FV_MSWIN32
return true;
} // blockSocket
//---------------------------------------------------------------------
// Connect the server to a port to listen for incoming messages
//
bool CSocket::ServerConnect(short _port) {
SysLog::Log('K', "CSocket.ServerConnect Entry (port=%d)", _port);
delete[] lastError;
lastError = 0;
errNo = 0;
if ((handle = ::socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR) {
SError("socket()");
return false;
}
if (_port < 0)
_port = defaultPort;
sockaddr_in addr;
memset((char *)&addr, '\0', sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(_port);
addr.sin_addr.s_addr = INADDR_ANY;
if (::bind(handle, (sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) {
SError("bind()");
return false;
}
// Set the socket to nonblocking if so specified (socket is
// initialized to block)
if (!blocking && !blockSocket(false))
return false;
if (::listen(handle, BACKLOG) == SOCKET_ERROR) {
SError("listen()");
return false;
}
SysLog::Log('K', "CSocket.ServerConnect: Connected to socket %d", handle);
return true;
} // ServerConnect
//---------------------------------------------------------------------
// Set up and return a socket for the calling client. Return
// zero if there is nothing to accept
//
PCSocket CSocket::Accept(void) {
SysLog::Log('K', "CSocket.Accept: * Entry");
delete[] lastError;
lastError = 0;
errNo = 0;
sockaddr_in addr;
memset(Pchar(&addr), '\0', sizeof(addr));
int newHandle;
int sinSize = sizeof(addr);
if ((newHandle = ::accept(handle, (sockaddr *)&addr, (socklen_t *)&sinSize)) == SOCKET_ERROR) {
SError("accept()");
return 0;
}
// Get the name and port of the client
delete[] name;
name = new char[strlen(inet_ntoa(addr.sin_addr))+1];
strcpy(name, inet_ntoa(addr.sin_addr));
port = addr.sin_port;
SysLog::Log('K', "CSocket.Accept: Socket %d connected from %s: %d", handle,
name,
port);
// Create and return a new socket object
PCSocket newSocket = new CSocket(newHandle);
newSocket->name = new char[strlen(name)+1];
strcpy(newSocket->name, name);
newSocket->port = port;
return newSocket;
} // Accept
//---------------------------------------------------------------------
// Connect the client to a port to send a message
//
bool CSocket::ClientConnect(Pchar _host, short _port) {
SysLog::Log('K', "CSocket.ClientConnect Entry (%s:%d)", _host, _port);
delete[] lastError;
lastError = 0;
errNo = 0;
Disconnect();
char buf[sizeSocketBuf+1];
if (!StrEmpty(_host))
strcpy(buf, _host);
else if (::gethostname(buf, sizeSocketBuf) == SOCKET_ERROR) {
SError("gethostname()");
return false;
}
hostent *he = ::gethostbyname(buf);
if (!he) {
SError("gethostbyname()");
return false;
}
if (_port == -1)
_port = defaultPort;
if ((handle = ::socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR) {
SError("socket()");
return false;
}
sockaddr_in server;
memset(Pchar(&server), '\0', sizeof(server));
server.sin_family = AF_INET;
server.sin_port = ::htons(_port);
server.sin_addr = *((in_addr *)he->h_addr);
if (::connect(handle, (sockaddr *)&server, sizeof(server)) == SOCKET_ERROR) {
SError("connect()");
return false;
}
// Set the socket to nonblocking if so specified (socket is
// initialized to block)
if (!blocking && !blockSocket(false))
return false;
// Get the name and port of the server
delete[] name;
name = 0;
port = -1;
// Save the name of the server
if (strlen(inet_ntoa(server.sin_addr))) {
name = new char[strlen(inet_ntoa(server.sin_addr))+1];
strcpy(name, inet_ntoa(server.sin_addr));
port = server.sin_port;
}
SysLog::Log('K', "CSocket.ClientConnect: Socket %d connected to %s: %d", handle,
name,
port);
return true;
} // ClientConnect
//---------------------------------------------------------------------
//
void CSocket::Disconnect(void) {
SysLog::Log('K', "CSocket.Disconnect: * Entry - socket %d (%s:%d)", handle,
name,
port);
delete[] lastError;
lastError = 0;
errNo = 0;
if (!IsConnected())
return;
if (::closesocket(handle) == SOCKET_ERROR) {
SError("closesocket()");
return;
}
SysLog::Log('K', "CSocket.Disconnect: Socket %d disconnected", handle);
handle = SOCKET_ERROR;
} // Disconnect
//---------------------------------------------------------------------
// Send a message
//
bool CSocket::Send(Pchar _msg, ushort _len) {
SysLog::Log('K', "CSocket.Send: * Entry %d (%s:%d)", _len,
name,
port);
delete[] lastError;
lastError = 0;
errNo = 0;
if (!_msg) {
MsgError("send message is empty");
return false;
}
if (_len == 0) {
MsgError("send length is 0");
return false;
}
// Send the message
if (::send(handle, _msg, _len, 0) == SOCKET_ERROR) {
SError("send()");
return false;
}
SysLog::Log('K', "CSocket.Send: Socket %d sent %d bytes", handle,
_len);
return true;
} // Send
//---------------------------------------------------------------------
// Send a block
//
bool CSocket::Send(RPCBlock _block) {
SysLog::Log('K', "CSocket.Send: * Entry (Block) - socket %d (%s:%d)",
handle,
name,
port);
delete[] lastError;
lastError = 0;
errNo = 0;
if (!_block) {
MsgError("send block is null");
return false;
}
Pchar p = _block->Lock();
if (!Send(p, _block->GetSize()))
return false;
return true;
} // Send
//---------------------------------------------------------------------
// Send a list of strings or a list of blocks
//
bool CSocket::Send(RPCList _list) {
SysLog::Log('K', "CSocket.Send: * Entry (List) - socket %d (%s:%d)",
handle,
name,
port);
delete[] lastError;
lastError = 0;
errNo = 0;
if (!_list) {
MsgError("send list is null");
return false;
}
if (_list->GotoHead()) do {
PCItem pItem = _list->Item();
if (pItem->Isa(iStrItem)) {
// Add CRLF to the end of string items
char msg[sizeString];
PCStrItem pStrItem = TYPECAST(pItem, StrItem);
Strcpy(msg, pStrItem->GetString(false), sizeString);
ChrCat(msg, '\r', sizeString);
ChrCat(msg, '\n', sizeString);
if (!Send(msg, strlen(msg)))
return false;
}
else if (pItem->Isa(iBlock)) {
// User is responsible for any CRLFs needed in blocks
PCBlock pBlk = TYPECAST(pItem, Block);
if (!Send(pBlk))
return false;
}
else // Can only handle strings and blocks
Assert(false);
} while (_list->GotoNext());
return true;
} // Send
//---------------------------------------------------------------------
// Return the number of bytes available to be received
//
ulong CSocket::GetAvailableSize(void) {
SysLog::Log('K', "CSocket.GetAvailableSize Entry - socket %d (%s:%d)",
handle,
name,
port);
delete[] lastError;
lastError = 0;
errNo = 0;
ulong szAvail = 0;
#ifdef FV_MSWIN32
if (::ioctlsocket(handle, FIONREAD, &szAvail)) {
SError("ioctlsocket()");
return 0;
}
#else
if (::ioctl (handle, FIONREAD, &szAvail)) {
SError("ioctl()");
return 0;
}
#endif // FV_MSWIN32
SysLog::Log('K', "CSocket.GetAvailableSize Exit (%d)", szAvail);
return szAvail;
} // GetAvailableSize
//---------------------------------------------------------------------
// Read the data from the socket. Note that when the socket is
// opened as a SOCK_STREAM socket, the buffer size does not truncate
// the amount read.
//
ulong CSocket::Receive(RPCBlock _msg, ulong _maxSize) {
SysLog::Log('K', "CSocket.Receive: * Entry %d - socket %d (%s:%d)",
_maxSize,
handle,
name,
port);
delete[] lastError;
lastError = 0;
errNo = 0;
char buf[sizeSocketBuf+1];
memset(buf, 0, sizeSocketBuf+1);
// Wait for data available to receive
short time = timeOut;
for (;;) {
if (time-- <= 0) {
sprintf(buf, "Receive() - Ran out of time - socket %d (%s:%d)",
handle,
name,
port);
delete[] lastError;
lastError = new char[strlen(buf)+1];
strcpy(lastError, buf);
return 0;
}
if (GetAvailableSize())
break;
Pause(10);
}
// Receive data
int len = ::recv(handle, buf, Min(_maxSize, sizeSocketBuf), 0);
if (len == SOCKET_ERROR) {
SError("Receive()");
return 0;
}
SysLog::Log('K', "CSocket.Receiver Socket %d (%s:%d) received %d bytes",
handle,
name,
port,
len);
// Return the data in the specified block
if (len)
_msg->PutBlock(buf, len+1);
return len;
} // Receive
//---------------------------------------------------------------------
// Get a character from the socket
//
char CSocket::GetChar(bool _block) {
delete[] lastError;
lastError = 0;
errNo = 0;
char buf[sizeSocketBuf+1];
memset(buf, 0, sizeSocketBuf+1);
if (blocking)
; // do nothing
else if (_block) {
if (!blockSocket(true))
return 0;
}
else {
// Wait for data available to receive
short time = timeOut;
for (;;) {
if (time-- <= 0) {
sprintf(buf, "GetChar() - Ran out of time - socket %d (%s:%d)",
handle,
name,
port);
delete[] lastError;
lastError = new char[strlen(buf)+1];
strcpy(lastError, buf);
return 0;
}
if (GetAvailableSize())
break;
Pause(10);
}
}
// Receive one character
ulong len = ::recv(handle, buf, 1, 0);
if (!blocking && _block)
blockSocket(false);
if (len == SOCKET_ERROR) {
SError("getChar()");
return 0;
}
else if (len == 0)
return 0;
else
return buf[0];
} // GetChar
//---------------------------------------------------------------------
// Get a text line from the socket. Line is limited to sizeString-1
// bytes per call.
//
bool CSocket::GetLine(Pchar _line, short _size, bool _block) {
delete[] lastError;
lastError = 0;
errNo = 0;
char line[sizeString];
memset(line, 0, sizeString);
// Read line from the input
// (ignore '\0' values)
char ch = GetChar(_block);
if (lastError)
return false;
// Note: may not work for Mac depending on what is sending the text
// since Mac eol = \r
while (ch != '\n') {
if (ch != 0 && ch != '\r')
ChrCat(line, ch, sizeString);
ch = GetChar();
if (lastError)
return false;
}
SysLog::Log('K', "CSocket.GetLine: Socket %d received <%.32s>", handle,
line);
// Return it to the customer
Strcpy(_line, line, _size);
return true;
} // GetLine
//---------------------------------------------------------------------
// WinSock error messages
//
#ifdef FV_MSWIN32
Pchar CSocket::GetErrorString(int err) {
switch (err) {
case WSAEINTR:
return "Interrupted system call";
case WSAEBADF:
return "Bad file number";
case WSAEACCES:
return "Permission denied";
case WSAEFAULT:
return "Bad address passed";
case WSAEINVAL:
return "Invalid parameter passed";
case WSAEMFILE:
return "Too many open files";
case WSAEWOULDBLOCK:
return "Operation would block";
case WSAEINPROGRESS:
return "Operation is now in progress";
case WSAEALREADY:
return "Operation is already in progress";
case WSAENOTSOCK:
return "Socket operation on non-socket";
case WSAEDESTADDRREQ:
return "Destination address required";
case WSAEMSGSIZE:
return "Message is too long";
case WSAEPROTOTYPE:
return "The protocol is of the wrong type for the socket";
case WSAENOPROTOOPT:
return "The requested protocol is not available";
case WSAEPROTONOSUPPORT:
return "The requested protocol is not supported";
case WSAESOCKTNOSUPPORT:
return "The specified socket type is not supported";
case WSAEOPNOTSUPP:
return "The specified operation is not supported";
case WSAEPFNOSUPPORT:
return "The specified protocol family is not supported";
case WSAEAFNOSUPPORT:
return "The specified address family is not supported";
case WSAEADDRINUSE:
return "The specified address is already in use";
case WSAEADDRNOTAVAIL:
return "The requested address is unassignable";
case WSAENETDOWN:
return "The network appears to be down";
case WSAENETUNREACH:
return "The network is unreachable";
case WSAENETRESET:
return "The network dropped the connection on reset";
case WSAECONNABORTED:
return "Software caused a connection abort";
case WSAECONNRESET:
return "Connection was reset by peer";
case WSAENOBUFS:
return "Out of buffer space";
case WSAEISCONN:
return "Socket is already connected";
case WSAENOTCONN:
return "Socket is not presently connected";
case WSAESHUTDOWN:
return "Can't send data because socket is shut down";
case WSAETOOMANYREFS:
return "Too many references, unable to splice";
case WSAETIMEDOUT:
return "The connection timed out";
case WSAECONNREFUSED:
return "The connection was refused";
case WSAELOOP:
return "Too many symbolic link levels";
case WSAENAMETOOLONG:
return "File name is too long";
case WSAEHOSTDOWN:
return "The host appears to be down";
case WSAEHOSTUNREACH:
return "The host is unreachable";
case WSAENOTEMPTY:
return "The directory is not empty";
case WSAEPROCLIM:
return "There are too many processes";
case WSAEUSERS:
return "There are too many users";
case WSAEDQUOT:
return "The disk quota is exceeded";
case WSAESTALE:
return "Bad NFS file handle";
case WSAEREMOTE:
return "There are too many levels of remote in the path";
case WSAEDISCON:
return "Disconnect";
case WSASYSNOTREADY:
return "Network sub-system is not ready or unusable";
case WSAVERNOTSUPPORTED:
return "The requested version is not supported";
case WSANOTINITIALISED:
return "Socket system is not initialized";
case WSAHOST_NOT_FOUND:
return "The host was not found";
case WSATRY_AGAIN:
return "Non-authoritative host not found";
case WSANO_RECOVERY:
return "Non-recoverable error";
case WSANO_DATA:
return "No information of the requested type was available";
default:
return "Unknown error.";
}
} // GetErrorString
#endif // FV_MSWIN32