//********************************************************************* // 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