-////////////////////////////////////////////////////////////////////////////////\r
-\r
-// Author: Andy Rushton\r
-// Copyright: (c) Southampton University 1999-2004\r
-// (c) Andy Rushton 2004-2009\r
-// License: BSD License, see ../docs/license.html\r
-\r
-// Contains all the platform-specific socket handling used by the TCP and UDP classes\r
-\r
-// TODO - any conversion required to support IPv6\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-\r
-#include "ip_sockets.hpp"\r
-#include "dprintf.hpp"\r
-#include <string.h>\r
-\r
-#ifdef MSWINDOWS\r
-// Windoze-specific includes\r
-#include <winsock2.h>\r
-#define ERRNO WSAGetLastError()\r
-#define HERRNO WSAGetLastError()\r
-#define IOCTL ioctlsocket\r
-#define CLOSE closesocket\r
-#define SHUT_RDWR SD_BOTH\r
-#define EINPROGRESS WSAEINPROGRESS\r
-#define EWOULDBLOCK WSAEWOULDBLOCK\r
-#define ECONNRESET WSAECONNRESET\r
-#define SOCKLEN_T int\r
-#else\r
-// Generic Unix includes\r
-// fix for older versions of Darwin?\r
-#define _BSD_SOCKLEN_T_ int\r
-#include <sys/types.h>\r
-#include <sys/socket.h>\r
-#include <sys/ioctl.h>\r
-#include <sys/time.h>\r
-#include <netinet/in.h>\r
-#include <errno.h>\r
-#include <netdb.h>\r
-#include <unistd.h>\r
-#define INVALID_SOCKET -1\r
-#define ERRNO errno\r
-#define HERRNO h_errno\r
-#define SOCKET int\r
-#define SOCKET_ERROR -1\r
-#define IOCTL ::ioctl\r
-#define CLOSE ::close\r
-#define SOCKLEN_T socklen_t\r
-#ifdef SOLARIS\r
-// Sun put some definitions in a different place\r
-#include <sys/filio.h>\r
-#endif\r
-#endif\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-\r
-namespace stlplus\r
-{\r
-\r
- ////////////////////////////////////////////////////////////////////////////////\r
- // Utilities\r
-\r
- // get an operating-system error message given an error code\r
- static std::string error_string(int error)\r
- {\r
- std::string result = "error " + dformat("%d",error);\r
-#ifdef MSWINDOWS\r
- char* message = 0;\r
- FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,\r
- 0,\r
- error,\r
- MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // "User default language"\r
- (LPTSTR)&message,\r
- 0,0);\r
- if (message) \r
- {\r
- result = message;\r
- LocalFree(message);\r
- }\r
- // the error message is for some perverse reason newline terminated - remove this\r
- if (result[result.size()-1] == '\n')\r
- result.erase(result.end()-1);\r
- if (result[result.size()-1] == '\r')\r
- result.erase(result.end()-1);\r
-#else\r
- char* message = strerror(error);\r
- if (message && message[0])\r
- result = message;\r
-#endif\r
- return result;\r
- }\r
-\r
- // convert address:port into a sockaddr\r
- static void convert_address(unsigned long address, unsigned short port, sockaddr& sa)\r
- {\r
- sa.sa_family = AF_INET;\r
- unsigned short network_port = htons(port);\r
- memcpy(&sa.sa_data[0], &network_port, sizeof(network_port));\r
- unsigned long network_address = htonl(address);\r
- memcpy(&sa.sa_data[2], &network_address, sizeof(network_address));\r
- }\r
-\r
-// // convert host:port into a sockaddr\r
-// static void convert_host(hostent& host, unsigned short port, sockaddr& sa)\r
-// {\r
-// sa.sa_family = host.h_addrtype;\r
-// unsigned short network_port = htons(port);\r
-// memcpy(&sa.sa_data[0], &network_port, sizeof(network_port));\r
-// memcpy(&sa.sa_data[2], host.h_addr, host.h_length);\r
-// }\r
-\r
- // convert sockaddr to address:port\r
- static void convert_sockaddr(const sockaddr& sa, unsigned long& address, unsigned short& port)\r
- {\r
- unsigned short network_port = 0;\r
- memcpy(&network_port, &sa.sa_data[0], sizeof(network_port));\r
- port = ntohs(network_port);\r
- unsigned long network_address = 0;\r
- memcpy(&network_address, &sa.sa_data[2], sizeof(network_address));\r
- address = ntohl(network_address);\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////////\r
- // Initialisation\r
- // Windows requires that Winsock is initialised before use and closed after\r
- // These routines initialise once on first use and close on the destruction of the last object using it\r
- // on non-windows platforms, I still increment/decrement the sockets count variable for diagnostic purposes\r
-\r
- static int sockets_count = 0;\r
-\r
- static int sockets_init(void)\r
- {\r
- int error = 0;\r
- if (sockets_count++ == 0)\r
- {\r
-#ifdef MSWINDOWS\r
- WSAData winsock_info;\r
- // request Winsock 2.0 or higher\r
- error = WSAStartup(MAKEWORD(2,0),&winsock_info);\r
-#endif\r
- }\r
- return error;\r
- }\r
-\r
- static int sockets_close(void)\r
- {\r
- int error = 0;\r
- if (--sockets_count == 0)\r
- {\r
-#ifdef MSWINDOWS\r
- if (WSACleanup() == SOCKET_ERROR)\r
- error = ERRNO;\r
-#endif\r
- }\r
- return error;\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////////\r
- // Socket Implementation - common code to manipulate a TCP socket\r
-\r
- class IP_socket_internals\r
- {\r
- private:\r
- IP_socket_type m_type;\r
- SOCKET m_socket;\r
- unsigned long m_remote_address;\r
- unsigned short m_remote_port;\r
- mutable int m_error;\r
- mutable std::string m_message;\r
- unsigned m_count;\r
-\r
- // disable copying of the internals\r
- IP_socket_internals(const IP_socket_internals&);\r
- IP_socket_internals& operator=(const IP_socket_internals&);\r
-\r
- public:\r
-\r
- ////////////////////////////////////////////////////////////////////////////\r
- // PIMPL alias counting \r
-\r
- void increment(void)\r
- {\r
- ++m_count;\r
- }\r
-\r
- bool decrement(void)\r
- {\r
- --m_count;\r
- return m_count == 0;\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////\r
- // constructors/destructors\r
-\r
- // construct an invalid socket\r
- IP_socket_internals(void) : m_type(undefined_socket_type), m_socket(INVALID_SOCKET), m_error(0), m_count(1)\r
- {\r
- set_error(sockets_init());\r
- }\r
-\r
- // close on destroy\r
- ~IP_socket_internals(void)\r
- {\r
- close();\r
- set_error(sockets_close());\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////\r
- // initialisation, connection\r
-\r
- bool initialised(void) const\r
- {\r
- return m_socket != INVALID_SOCKET;\r
- }\r
-\r
- // attach this object to a pre-opened socket\r
- bool set(SOCKET socket, unsigned long remote_address, unsigned short remote_port)\r
- {\r
- if (initialised()) close();\r
- clear_error();\r
- m_socket = socket;\r
- m_remote_address = remote_address;\r
- m_remote_port = remote_port;\r
- return true;\r
- }\r
-\r
- // create a raw socket attached to this object\r
- bool initialise(IP_socket_type type)\r
- {\r
- if (initialised()) close();\r
- clear_error();\r
- if ((type != TCP) && (type != UDP))\r
- {\r
- set_error(-1, "Illegal socket type");\r
- return false;\r
- }\r
- // create an anonymous socket\r
- m_socket = ::socket(AF_INET, ((type == TCP) ? SOCK_STREAM : SOCK_DGRAM), 0);\r
- if (m_socket == INVALID_SOCKET)\r
- {\r
- set_error(ERRNO);\r
- close();\r
- return false;\r
- }\r
- // record the type on success only\r
- m_type = type;\r
- // set the socket into non-blocking mode\r
- unsigned long nonblocking = 1;\r
- if (IOCTL(m_socket, FIONBIO, &nonblocking) == SOCKET_ERROR)\r
- {\r
- set_error(ERRNO);\r
- return false;\r
- }\r
- return true;\r
- }\r
- \r
- // function for performing IP lookup (i.e. gethostbyname)\r
- // could be standalone but making it a member means that it can use the socket's error handler\r
- // - remote_address: IP name or number\r
- // - returns the IP address as a number - zero if there's an error\r
- unsigned long ip_lookup(const std::string& remote_address)\r
- {\r
- unsigned long result = 0;\r
- // Lookup the IP address to convert it into a host record\r
- // this DOES lookup IP address names as well (not according to MS help !!)\r
- // TODO - convert this to use ::getaddrinfo - ::gethostbyname is deprecated\r
- hostent* host_info = ::gethostbyname(remote_address.c_str());\r
- if (!host_info)\r
- {\r
- set_error(HERRNO);\r
- return 0;\r
- }\r
- // extract the address from the host info\r
- unsigned long network_address = 0;\r
- memcpy(&network_address, host_info->h_addr, host_info->h_length);\r
- result = ntohl(network_address);\r
- return result;\r
- }\r
-\r
- // tests whether a socket is ready for communication\r
- bool select(bool readable, bool writeable, unsigned wait)\r
- {\r
- if (!initialised()) return false;\r
- // set up the readable set\r
- fd_set readable_set;\r
- fd_set* readable_set_ptr = 0;\r
- if (readable)\r
- {\r
- FD_ZERO(&readable_set);\r
- FD_SET(m_socket,&readable_set);\r
- readable_set_ptr = &readable_set;\r
- }\r
- // set up the writeable set\r
- fd_set writeable_set;\r
- fd_set* writeable_set_ptr = 0;\r
- if (writeable)\r
- {\r
- FD_ZERO(&writeable_set);\r
- FD_SET(m_socket,&writeable_set);\r
- writeable_set_ptr = &writeable_set;\r
- }\r
- // TODO - check the error set and lookup the error?\r
- fd_set* error_set_ptr = 0;\r
- // set up the timout value\r
- // Note: a null pointer implements a blocking select\r
- // a pointer to a zero value implements a zero-wait poll\r
- // a pointer to a positive value implements a poll with a timeout\r
- // I currently only implement polling with timeout which may be zero - no blocking\r
- timeval timeout;\r
- timeval* timeout_ptr = 0;\r
- timeout.tv_sec = wait/1000000;\r
- timeout.tv_usec = wait%1000000;\r
- timeout_ptr = &timeout;\r
- // now test the socket\r
- int select_result = ::select(m_socket+1, readable_set_ptr, writeable_set_ptr, error_set_ptr, timeout_ptr);\r
- switch(select_result)\r
- {\r
- case SOCKET_ERROR:\r
- // select failed with an error - trap the error\r
- set_error(ERRNO);\r
- return false;\r
- case 0:\r
- // timeout exceeded without a connection appearing\r
- return false;\r
- default:\r
- // at least one connection is pending\r
- // TODO - do we need to do the extra socket options checking on Posix?\r
- // TODO - does this connect in any way to the error_set above?\r
- return true;\r
- }\r
- }\r
-\r
- // bind the socket to a port so that it can receive from specific address\r
- bool bind(unsigned long remote_address, unsigned short local_port)\r
- {\r
- if (!initialised()) return false;\r
- // name the socket and bind it to a port - this is a requirement for a server\r
- sockaddr server;\r
- convert_address(INADDR_ANY, local_port, server);\r
- if (::bind(m_socket, &server, sizeof(server)) == SOCKET_ERROR)\r
- {\r
- set_error(ERRNO);\r
- close();\r
- return false;\r
- }\r
- return true;\r
- }\r
- \r
- // bind the socket to a port so that it can receive from any address\r
- bool bind_any(unsigned short local_port)\r
- {\r
- return bind(INADDR_ANY, local_port);\r
- }\r
-\r
- // set this socket up to be a listening port\r
- // must have been bound to a local port already\r
- // - length of backlog queue to manage - may be zero\r
- // - returns success status\r
- bool listen(unsigned short queue)\r
- {\r
- if (!initialised()) return false;\r
- // set the port to listen for incoming connections\r
- if (::listen(m_socket, (int)queue) == SOCKET_ERROR)\r
- {\r
- set_error(ERRNO);\r
- close();\r
- return false;\r
- }\r
- return true;\r
- }\r
-\r
- // test whether there's an incoming connection on the socket\r
- // only applicable if it has been set up as a listening port\r
- bool accept_ready(unsigned wait)\r
- {\r
- // the test for a connection being ready is the same as the test for whether the socket is readable\r
- // see documentation for select\r
- return select(true, false, wait);\r
- }\r
-\r
- // accept a connection on the socket\r
- // only applicable if it has been set up as a listening port\r
- // - returns socket filled in with the accepted connection's details - or with the error fields set\r
- IP_socket accept(void)\r
- {\r
- if (!initialised()) return IP_socket();\r
- IP_socket result;\r
- // accept the connection, at the same time getting the address of the connecting client\r
- sockaddr saddress;\r
- SOCKLEN_T saddress_length = sizeof(saddress);\r
- SOCKET socket = ::accept(m_socket, &saddress, &saddress_length);\r
- if (socket == INVALID_SOCKET)\r
- {\r
- // only set the result socket with an error\r
- result.m_impl->set_error(ERRNO);\r
- return result;\r
- }\r
- // extract the contents of the address\r
- unsigned long remote_address = 0;\r
- unsigned short remote_port = 0;\r
- convert_sockaddr(saddress, remote_address, remote_port);\r
- result.m_impl->set(socket, remote_address, remote_port);\r
- return result;\r
- }\r
-\r
- // client connect to a server\r
- // - remote_address: IP number of remote address to connect to\r
- // - remote_port: port to connect to\r
- bool connect(unsigned long remote_address, unsigned short remote_port)\r
- {\r
- if (!initialised()) return false;\r
- // fill in the connection data structure\r
- sockaddr connect_data;\r
- convert_address(remote_address, remote_port, connect_data);\r
- // connect binds the socket to a local address\r
- // if connectionless it simply sets the default remote address\r
- // if connectioned it makes the connection\r
- if (::connect(m_socket, &connect_data, sizeof(connect_data)) == SOCKET_ERROR)\r
- {\r
- // the socket is non-blocking, so connect will almost certainly fail with EINPROGRESS which is not an error\r
- // only catch real errors\r
- int error = ERRNO;\r
- if (error != EINPROGRESS && error != EWOULDBLOCK)\r
- {\r
- set_error(error);\r
- return false;\r
- }\r
- }\r
- // extract the remote connection details for local storage\r
- convert_sockaddr(connect_data, m_remote_address, m_remote_port);\r
- return true;\r
- }\r
-\r
- // test whether a socket is connected and ready to communicate\r
- bool connected(unsigned wait)\r
- {\r
- if (!initialised()) return false;\r
- // Linux and Windows docs say test with select for whether socket is\r
- // writable. However, a problem has been reported with Linux whereby\r
- // the OS will report a socket as writable when it isn't\r
- // first use the select method\r
- if (!select(false, true, wait))\r
- return false;\r
-#ifdef MSWINDOWS\r
- // Windows needs no further processing - select method works\r
- return true;\r
-#else\r
- // Posix version needs further checking using the socket options\r
- // DJDM: socket has returned EINPROGRESS on the first attempt at connection\r
- // it has also returned that it can be written to\r
- // we must now ask it if it has actually connected - using getsockopt\r
- int error = 0;\r
- socklen_t serror = sizeof(int);\r
- if (::getsockopt(m_socket, SOL_SOCKET, SO_ERROR, &error, &serror)==0)\r
- // handle the error value - one of them means that the socket has connected\r
- if (!error || error == EISCONN)\r
- return true;\r
- return false;\r
-#endif\r
- }\r
-\r
- bool close(void)\r
- {\r
- bool result = true;\r
- if (initialised())\r
- {\r
- if (shutdown(m_socket,SHUT_RDWR) == SOCKET_ERROR)\r
- {\r
- set_error(ERRNO);\r
- result = false;\r
- }\r
- if (CLOSE(m_socket) == SOCKET_ERROR)\r
- {\r
- set_error(ERRNO);\r
- result = false;\r
- }\r
- }\r
- m_socket = INVALID_SOCKET;\r
- m_remote_address = 0;\r
- m_remote_port = 0;\r
- return result;\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////\r
- // sending/receiving\r
-\r
- bool send_ready(unsigned wait)\r
- {\r
- // determines whether the socket is ready to send by testing whether it is writable\r
- return select(false, true, wait);\r
- }\r
-\r
- bool send (std::string& data)\r
- {\r
- if (!initialised()) return false;\r
- // send the data - this will never block but may not send all the data\r
- int bytes = ::send(m_socket, data.c_str(), data.size(), 0);\r
- if (bytes == SOCKET_ERROR)\r
- {\r
- set_error(ERRNO);\r
- return false;\r
- }\r
- // remove the sent bytes from the data buffer so that the buffer represents the data still to be sent\r
- data.erase(0,bytes);\r
- return true;\r
- }\r
-\r
- bool send_packet(std::string& data, unsigned long address = 0, unsigned short port = 0)\r
- {\r
- if (!initialised()) return false;\r
- // if no address specified, rely on the socket having been connected (can I test this?)\r
- // so use the standard send, otherwise use the sendto function\r
- int bytes = 0;\r
- if (!address)\r
- {\r
- bytes = ::send(m_socket, data.c_str(), data.size(), 0);\r
- }\r
- else\r
- {\r
- sockaddr saddress;\r
- convert_address(address, port, saddress);\r
- bytes = ::sendto(m_socket, data.c_str(), data.size(), 0, &saddress, sizeof(saddress));\r
- }\r
- if (bytes == SOCKET_ERROR)\r
- {\r
- set_error(ERRNO);\r
- return false;\r
- }\r
- // remove the sent bytes from the data buffer so that the buffer represents the data still to be sent\r
- data.erase(0,bytes);\r
- return true;\r
- }\r
-\r
- bool receive_ready(unsigned wait)\r
- {\r
- // determines whether the socket is ready to receive by testing whether it is readable\r
- return select(true, false, wait);\r
- }\r
-\r
- bool receive (std::string& data)\r
- {\r
- if (!initialised()) return false;\r
- // determine how much data is available to read\r
- unsigned long bytes = 0;\r
- if (IOCTL(m_socket, FIONREAD, &bytes) == SOCKET_ERROR)\r
- {\r
- set_error(ERRNO);\r
- return false;\r
- }\r
- // get the data up to the amount claimed to be present - this is non-blocking\r
- char* buffer = new char[bytes+1];\r
- int read = ::recv(m_socket, buffer, bytes, 0);\r
- if (read == SOCKET_ERROR)\r
- {\r
- delete[] buffer;\r
- set_error(ERRNO);\r
- close();\r
- return false;\r
- }\r
- if (read == 0)\r
- {\r
- // TODO - check whether this is an appropriate conditon to close the socket\r
- close();\r
- }\r
- else\r
- {\r
- // this is binary data so copy the bytes including nulls\r
- data.append(buffer,read);\r
- }\r
- delete[] buffer;\r
- return true;\r
- }\r
-\r
- bool receive_packet(std::string& data, unsigned long& address, unsigned short& port)\r
- {\r
- if (!initialised()) return false;\r
- // determine how much data is available to read\r
- unsigned long bytes = 0;\r
- if (IOCTL(m_socket, FIONREAD, &bytes) == SOCKET_ERROR)\r
- {\r
- set_error(ERRNO);\r
- return false;\r
- }\r
- // get the data up to the amount claimed to be present - this is non-blocking\r
- // also get the sender's details\r
- char* buffer = new char[bytes+1];\r
- sockaddr saddress;\r
- SOCKLEN_T saddress_length = sizeof(saddress);\r
- int read = ::recvfrom(m_socket, buffer, bytes, 0, &saddress, &saddress_length);\r
- if (read == SOCKET_ERROR)\r
- {\r
- // UDP connection reset means that a previous sent failed to deliver cos the address was unknown\r
- // this is NOT an error with the sending server socket, which IS still usable\r
- int error = ERRNO;\r
- if (error != ECONNRESET)\r
- {\r
- delete[] buffer;\r
- set_error(error);\r
- close();\r
- return false;\r
- }\r
- }\r
- // this is binary data so copy the bytes including nulls\r
- data.append(buffer,read);\r
- // also retrieve the sender's details\r
- convert_sockaddr(saddress, address, port);\r
- delete[] buffer;\r
- return true;\r
- }\r
-\r
- bool receive_packet(std::string& data)\r
- {\r
- // call the above and then discard the address details\r
- unsigned long address = 0;\r
- unsigned short port = 0;\r
- return receive_packet(data, address, port);\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////\r
- // informational\r
-\r
- IP_socket_type type(void) const\r
- {\r
- return m_type;\r
- }\r
-\r
- unsigned short local_port(void) const\r
- {\r
- if (!initialised()) return 0;\r
- sockaddr saddress;\r
- SOCKLEN_T saddress_length = sizeof(saddress);\r
- if (::getsockname(m_socket, &saddress, &saddress_length) != 0)\r
- {\r
- set_error(ERRNO);\r
- return 0;\r
- }\r
- unsigned long address = 0;\r
- unsigned short port = 0;\r
- convert_sockaddr(saddress, address, port);\r
- return port;\r
- }\r
-\r
- unsigned long remote_address(void) const\r
- {\r
- return m_remote_address;\r
- }\r
-\r
- unsigned short remote_port(void) const\r
- {\r
- return m_remote_port;\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////\r
- // error handling\r
-\r
- void set_error (int error, const char* message = 0) const\r
- {\r
- if (error != 0)\r
- {\r
- m_error = error;\r
- if (message && (message[0] != 0))\r
- m_message = message;\r
- else\r
- m_message = error_string(error);\r
- }\r
- }\r
-\r
- int error(void) const\r
- {\r
- return m_error;\r
- }\r
-\r
- void clear_error (void) const\r
- {\r
- m_error = 0;\r
- m_message.erase();\r
- }\r
-\r
- std::string message(void) const\r
- {\r
- return m_message;\r
- }\r
-\r
- };\r
-\r
- ////////////////////////////////////////////////////////////////////////////////\r
- // Socket - common code to manipulate a socket\r
-\r
- // create an uninitialised socket\r
- IP_socket::IP_socket(void) : m_impl(new IP_socket_internals)\r
- {\r
- }\r
-\r
- // create an initialised socket\r
- // - type: create either a TCP or UDP socket - if neither, creates an uninitialised socket\r
- IP_socket::IP_socket(IP_socket_type type) : m_impl(new IP_socket_internals)\r
- {\r
- initialise(type);\r
- }\r
-\r
- // destroy the socket, closing it if open\r
- IP_socket::~IP_socket(void)\r
- {\r
- if (m_impl->decrement())\r
- delete m_impl;\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////\r
- // copying is implemented as aliasing\r
-\r
- IP_socket::IP_socket(const IP_socket& right) : m_impl(0)\r
- {\r
- // make this an alias of right\r
- m_impl = right.m_impl;\r
- m_impl->increment();\r
- }\r
-\r
- IP_socket& IP_socket::operator=(const IP_socket& right)\r
- {\r
- // make self-copy safe\r
- if (m_impl == right.m_impl) return *this;\r
- // first dealias the existing implementation\r
- if (m_impl->decrement())\r
- delete m_impl;\r
- // now make this an alias of right\r
- m_impl = right.m_impl;\r
- m_impl->increment();\r
- return *this;\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////\r
- // initialisation, connection\r
-\r
- // initialise the socket\r
- // - type: create either a TCP or UDP socket\r
- // - returns success status\r
- bool IP_socket::initialise(IP_socket_type type)\r
- {\r
- return m_impl->initialise(type);\r
- }\r
-\r
- // test whether this is an initialised socket\r
- // - returns whether this is initialised\r
- bool IP_socket::initialised(void) const\r
- {\r
- return m_impl->initialised();\r
- }\r
-\r
- // close, i.e. disconnect the socket\r
- // - returns a success flag\r
- bool IP_socket::close(void)\r
- {\r
- return m_impl->close();\r
- }\r
-\r
- // function for performing IP lookup (i.e. gethostbyname)\r
- // could be standalone but making it a member means that it can use the socket's error handler\r
- // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96)\r
- // - returns the IP address as a long integer - zero if there's an error\r
- unsigned long IP_socket::ip_lookup(const std::string& remote_address)\r
- {\r
- return m_impl->ip_lookup(remote_address);\r
- }\r
-\r
- // test whether a socket is ready to communicate\r
- // - readable: test whether socket is ready to read\r
- // - writeable: test whether a socket is ready to write\r
- // - timeout: if socket is not ready, time to wait before giving up - in micro-seconds - 0 means don't wait\r
- // returns false if not ready or error - use error() method to tell - true if ready\r
- bool IP_socket::select(bool readable, bool writeable, unsigned timeout)\r
- {\r
- return m_impl->select(readable, writeable, timeout);\r
- }\r
-\r
- // bind the socket to a port so that it can receive from specific address - typically used by a client\r
- // - remote_address: IP number of remote server to send/receive to/from\r
- // - local_port: port on local machine to bind to the address\r
- // - returns success flag\r
- bool IP_socket::bind(unsigned long remote_address, unsigned short local_port)\r
- {\r
- return m_impl->bind(remote_address, local_port);\r
- }\r
-\r
- // bind the socket to a port so that it can receive from any address - typically used by a server\r
- // - local_port: port on local machine to bind to the address\r
- // - returns success flag\r
- bool IP_socket::bind_any(unsigned short local_port)\r
- {\r
- return m_impl->bind_any(local_port);\r
- }\r
-\r
- // initialise a socket and set this socket up to be a listening port\r
- // - queue: length of backlog queue to manage - may be zero\r
- // - returns success status\r
- bool IP_socket::listen(unsigned short queue)\r
- {\r
- return m_impl->listen(queue);\r
- }\r
-\r
- // test for a connection on the object's socket - only applicable if it has been set up as a listening port\r
- // - returns true if a connection is ready to be accepted\r
- bool IP_socket::accept_ready(unsigned timeout) const\r
- {\r
- return m_impl->accept_ready(timeout);\r
- }\r
-\r
- // accept a connection on the object's socket - only applicable if it has been set up as a listening port\r
- // - returns the connection as a new socket\r
- IP_socket IP_socket::accept(void)\r
- {\r
- return m_impl->accept();\r
- }\r
-\r
- // client connect to a server\r
- // - address: IP number already lookup up with ip_lookup\r
- // - port: port to connect to\r
- // - returns a success flag\r
- bool IP_socket::connect(unsigned long address, unsigned short port)\r
- {\r
- return m_impl->connect(address, port);\r
- }\r
-\r
- // test whether a socket is connected and ready to communicate, returns on successful connect or timeout\r
- // - timeout: how long to wait in microseconds if not connected yet\r
- // - returns success flag\r
- bool IP_socket::connected(unsigned timeout)\r
- {\r
- return m_impl->connected(timeout);\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////\r
- // sending/receiving\r
-\r
- // test whether a socket is connected and ready to send data, returns if ready or on timeout\r
- // - timeout: how long to wait in microseconds if not connected yet (blocking)\r
- // - returns status\r
- bool IP_socket::send_ready(unsigned timeout)\r
- {\r
- return m_impl->send_ready(timeout);\r
- }\r
-\r
- // send data through the socket - if the data is long only part of it may\r
- // be sent. The sent part is removed from the data, so the same string can\r
- // be sent again and again until it is empty.\r
- // - data: string containing data to be sent - any data successfully sent is removed\r
- // - returns success flag\r
- bool IP_socket::send (std::string& data)\r
- {\r
- return m_impl->send(data);\r
- }\r
-\r
- // send data through a connectionless (UDP) socket\r
- // the data will be sent as a single packet\r
- // - packet: string containing data to be sent - any data successfully sent is removed\r
- // - remote_address: address of the remote host to send to - optional if the socket has been connected to remote\r
- // - remote_port: port of the remote host to send to - optional if the socket has been connected to remote\r
- // - returns success flag\r
- bool IP_socket::send_packet(std::string& packet, unsigned long remote_address, unsigned short remote_port)\r
- {\r
- return m_impl->send_packet(packet, remote_address, remote_port);\r
- }\r
-\r
- // send data through a connectionless (UDP) socket\r
- // the data will be sent as a single packet\r
- // only works if the socket has been connected to remote\r
- // - packet: string containing data to be sent - any data successfully sent is removed\r
- // - returns success flag\r
- bool IP_socket::send_packet(std::string& packet)\r
- {\r
- return m_impl->send_packet(packet);\r
- }\r
-\r
- // test whether a socket is connected and ready to receive data, returns if ready or on timeout\r
- // - timeout: how long to wait in microseconds if not connected yet (blocking)\r
- // - returns status\r
- bool IP_socket::receive_ready(unsigned timeout)\r
- {\r
- return m_impl->receive_ready(timeout);\r
- }\r
-\r
- // receive data through a connection-based (TCP) socket\r
- // if the data is long only part of it may be received. The received data\r
- // is appended to the string, building it up in stages, so the same string\r
- // can be received again and again until all information has been\r
- // received.\r
- // - data: string receiving data from socket - any data successfully received is appended\r
- // - returns success flag\r
- bool IP_socket::receive (std::string& data)\r
- {\r
- return m_impl->receive(data);\r
- }\r
-\r
- // receive data through a connectionless (UDP) socket\r
- // - packet: string receiving data from socket - any data successfully received is appended\r
- // - remote_address: returns the address of the remote host received from\r
- // - remote_port: returns the port of the remote host received from\r
- // - returns success flag\r
- bool IP_socket::receive_packet(std::string& packet, unsigned long& remote_address, unsigned short& remote_port)\r
- {\r
- return m_impl->receive_packet(packet, remote_address, remote_port);\r
- }\r
-\r
- // variant of above which does not give back the address and port of the sender\r
- // receive data through a connectionless (UDP) socket\r
- // - packet: string receiving data from socket - any data successfully received is appended\r
- // - returns success flag\r
- bool IP_socket::receive_packet(std::string& packet)\r
- {\r
- return m_impl->receive_packet(packet);\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////\r
- // informational\r
-\r
- IP_socket_type IP_socket::type(void) const\r
- {\r
- return m_impl->type();\r
- }\r
-\r
- unsigned short IP_socket::local_port(void) const\r
- {\r
- return m_impl->local_port();\r
- }\r
-\r
- unsigned long IP_socket::remote_address(void) const\r
- {\r
- return m_impl->remote_address();\r
- }\r
-\r
- unsigned short IP_socket::remote_port(void) const\r
- {\r
- return m_impl->remote_port();\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////\r
- // error handling\r
-\r
- void IP_socket::set_error (int error, const std::string& message) const\r
- {\r
- m_impl->set_error(error, message.c_str());\r
- }\r
-\r
- void IP_socket::clear_error (void) const\r
- {\r
- m_impl->clear_error();\r
- }\r
-\r
- int IP_socket::error(void) const\r
- {\r
- return m_impl->error();\r
- }\r
-\r
- std::string IP_socket::message(void) const\r
- {\r
- return m_impl->message();\r
- }\r
-\r
- ////////////////////////////////////////////////////////////////////////////////\r
-\r
-} // end namespace stlplus\r
+////////////////////////////////////////////////////////////////////////////////
+
+// Author: Andy Rushton
+// Copyright: (c) Southampton University 1999-2004
+// (c) Andy Rushton 2004-2009
+// License: BSD License, see ../docs/license.html
+
+// Contains all the platform-specific socket handling used by the TCP and UDP classes
+
+// TODO - any conversion required to support IPv6
+
+////////////////////////////////////////////////////////////////////////////////
+
+#include "ip_sockets.hpp"
+#include "dprintf.hpp"
+#include <string.h>
+
+#ifdef MSWINDOWS
+// Windoze-specific includes
+#include <winsock2.h>
+#define ERRNO WSAGetLastError()
+#define HERRNO WSAGetLastError()
+#define IOCTL ioctlsocket
+#define CLOSE closesocket
+#define SHUT_RDWR SD_BOTH
+#define EINPROGRESS WSAEINPROGRESS
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#define ECONNRESET WSAECONNRESET
+#define SOCKLEN_T int
+#else
+// Generic Unix includes
+// fix for older versions of Darwin?
+#define _BSD_SOCKLEN_T_ int
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <netdb.h>
+#include <unistd.h>
+#define INVALID_SOCKET -1
+#define ERRNO errno
+#define HERRNO h_errno
+#define SOCKET int
+#define SOCKET_ERROR -1
+#define IOCTL ::ioctl
+#define CLOSE ::close
+#define SOCKLEN_T socklen_t
+#ifdef SOLARIS
+// Sun put some definitions in a different place
+#include <sys/filio.h>
+#endif
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace stlplus
+{
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // Utilities
+
+ // get an operating-system error message given an error code
+ static std::string error_string(int error)
+ {
+ std::string result = "error " + dformat("%d",error);
+#ifdef MSWINDOWS
+ char* message = 0;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
+ 0,
+ error,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // "User default language"
+ (LPTSTR)&message,
+ 0,0);
+ if (message)
+ {
+ result = message;
+ LocalFree(message);
+ }
+ // the error message is for some perverse reason newline terminated - remove this
+ if (result[result.size()-1] == '\n')
+ result.erase(result.end()-1);
+ if (result[result.size()-1] == '\r')
+ result.erase(result.end()-1);
+#else
+ char* message = strerror(error);
+ if (message && message[0])
+ result = message;
+#endif
+ return result;
+ }
+
+ // convert address:port into a sockaddr
+ static void convert_address(unsigned long address, unsigned short port, sockaddr& sa)
+ {
+ sa.sa_family = AF_INET;
+ unsigned short network_port = htons(port);
+ memcpy(&sa.sa_data[0], &network_port, sizeof(network_port));
+ unsigned long network_address = htonl(address);
+ memcpy(&sa.sa_data[2], &network_address, sizeof(network_address));
+ }
+
+// // convert host:port into a sockaddr
+// static void convert_host(hostent& host, unsigned short port, sockaddr& sa)
+// {
+// sa.sa_family = host.h_addrtype;
+// unsigned short network_port = htons(port);
+// memcpy(&sa.sa_data[0], &network_port, sizeof(network_port));
+// memcpy(&sa.sa_data[2], host.h_addr, host.h_length);
+// }
+
+ // convert sockaddr to address:port
+ static void convert_sockaddr(const sockaddr& sa, unsigned long& address, unsigned short& port)
+ {
+ unsigned short network_port = 0;
+ memcpy(&network_port, &sa.sa_data[0], sizeof(network_port));
+ port = ntohs(network_port);
+ unsigned long network_address = 0;
+ memcpy(&network_address, &sa.sa_data[2], sizeof(network_address));
+ address = ntohl(network_address);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // Initialisation
+ // Windows requires that Winsock is initialised before use and closed after
+ // These routines initialise once on first use and close on the destruction of the last object using it
+ // on non-windows platforms, I still increment/decrement the sockets count variable for diagnostic purposes
+
+ static int sockets_count = 0;
+
+ static int sockets_init(void)
+ {
+ int error = 0;
+ if (sockets_count++ == 0)
+ {
+#ifdef MSWINDOWS
+ WSAData winsock_info;
+ // request Winsock 2.0 or higher
+ error = WSAStartup(MAKEWORD(2,0),&winsock_info);
+#endif
+ }
+ return error;
+ }
+
+ static int sockets_close(void)
+ {
+ int error = 0;
+ if (--sockets_count == 0)
+ {
+#ifdef MSWINDOWS
+ if (WSACleanup() == SOCKET_ERROR)
+ error = ERRNO;
+#endif
+ }
+ return error;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // Socket Implementation - common code to manipulate a TCP socket
+
+ class IP_socket_internals
+ {
+ private:
+ IP_socket_type m_type;
+ SOCKET m_socket;
+ unsigned long m_remote_address;
+ unsigned short m_remote_port;
+ mutable int m_error;
+ mutable std::string m_message;
+ unsigned m_count;
+
+ // disable copying of the internals
+ IP_socket_internals(const IP_socket_internals&);
+ IP_socket_internals& operator=(const IP_socket_internals&);
+
+ public:
+
+ ////////////////////////////////////////////////////////////////////////////
+ // PIMPL alias counting
+
+ void increment(void)
+ {
+ ++m_count;
+ }
+
+ bool decrement(void)
+ {
+ --m_count;
+ return m_count == 0;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // constructors/destructors
+
+ // construct an invalid socket
+ IP_socket_internals(void) : m_type(undefined_socket_type), m_socket(INVALID_SOCKET), m_error(0), m_count(1)
+ {
+ set_error(sockets_init());
+ }
+
+ // close on destroy
+ ~IP_socket_internals(void)
+ {
+ close();
+ set_error(sockets_close());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // initialisation, connection
+
+ bool initialised(void) const
+ {
+ return m_socket != INVALID_SOCKET;
+ }
+
+ // attach this object to a pre-opened socket
+ bool set(SOCKET socket, unsigned long remote_address, unsigned short remote_port)
+ {
+ if (initialised()) close();
+ clear_error();
+ m_socket = socket;
+ m_remote_address = remote_address;
+ m_remote_port = remote_port;
+ return true;
+ }
+
+ // create a raw socket attached to this object
+ bool initialise(IP_socket_type type)
+ {
+ if (initialised()) close();
+ clear_error();
+ if ((type != TCP) && (type != UDP))
+ {
+ set_error(-1, "Illegal socket type");
+ return false;
+ }
+ // create an anonymous socket
+ m_socket = ::socket(AF_INET, ((type == TCP) ? SOCK_STREAM : SOCK_DGRAM), 0);
+ if (m_socket == INVALID_SOCKET)
+ {
+ set_error(ERRNO);
+ close();
+ return false;
+ }
+ // record the type on success only
+ m_type = type;
+ // set the socket into non-blocking mode
+ unsigned long nonblocking = 1;
+ if (IOCTL(m_socket, FIONBIO, &nonblocking) == SOCKET_ERROR)
+ {
+ set_error(ERRNO);
+ return false;
+ }
+ return true;
+ }
+
+ // function for performing IP lookup (i.e. gethostbyname)
+ // could be standalone but making it a member means that it can use the socket's error handler
+ // - remote_address: IP name or number
+ // - returns the IP address as a number - zero if there's an error
+ unsigned long ip_lookup(const std::string& remote_address)
+ {
+ unsigned long result = 0;
+ // Lookup the IP address to convert it into a host record
+ // this DOES lookup IP address names as well (not according to MS help !!)
+ // TODO - convert this to use ::getaddrinfo - ::gethostbyname is deprecated
+ hostent* host_info = ::gethostbyname(remote_address.c_str());
+ if (!host_info)
+ {
+ set_error(HERRNO);
+ return 0;
+ }
+ // extract the address from the host info
+ unsigned long network_address = 0;
+ memcpy(&network_address, host_info->h_addr, host_info->h_length);
+ result = ntohl(network_address);
+ return result;
+ }
+
+ // tests whether a socket is ready for communication
+ bool select(bool readable, bool writeable, unsigned wait)
+ {
+ if (!initialised()) return false;
+ // set up the readable set
+ fd_set readable_set;
+ fd_set* readable_set_ptr = 0;
+ if (readable)
+ {
+ FD_ZERO(&readable_set);
+ FD_SET(m_socket,&readable_set);
+ readable_set_ptr = &readable_set;
+ }
+ // set up the writeable set
+ fd_set writeable_set;
+ fd_set* writeable_set_ptr = 0;
+ if (writeable)
+ {
+ FD_ZERO(&writeable_set);
+ FD_SET(m_socket,&writeable_set);
+ writeable_set_ptr = &writeable_set;
+ }
+ // TODO - check the error set and lookup the error?
+ fd_set* error_set_ptr = 0;
+ // set up the timout value
+ // Note: a null pointer implements a blocking select
+ // a pointer to a zero value implements a zero-wait poll
+ // a pointer to a positive value implements a poll with a timeout
+ // I currently only implement polling with timeout which may be zero - no blocking
+ timeval timeout;
+ timeval* timeout_ptr = 0;
+ timeout.tv_sec = wait/1000000;
+ timeout.tv_usec = wait%1000000;
+ timeout_ptr = &timeout;
+ // now test the socket
+ int select_result = ::select(m_socket+1, readable_set_ptr, writeable_set_ptr, error_set_ptr, timeout_ptr);
+ switch(select_result)
+ {
+ case SOCKET_ERROR:
+ // select failed with an error - trap the error
+ set_error(ERRNO);
+ return false;
+ case 0:
+ // timeout exceeded without a connection appearing
+ return false;
+ default:
+ // at least one connection is pending
+ // TODO - do we need to do the extra socket options checking on Posix?
+ // TODO - does this connect in any way to the error_set above?
+ return true;
+ }
+ }
+
+ // bind the socket to a port so that it can receive from specific address
+ bool bind(unsigned long remote_address, unsigned short local_port)
+ {
+ if (!initialised()) return false;
+ // name the socket and bind it to a port - this is a requirement for a server
+ sockaddr server;
+ convert_address(INADDR_ANY, local_port, server);
+ if (::bind(m_socket, &server, sizeof(server)) == SOCKET_ERROR)
+ {
+ set_error(ERRNO);
+ close();
+ return false;
+ }
+ return true;
+ }
+
+ // bind the socket to a port so that it can receive from any address
+ bool bind_any(unsigned short local_port)
+ {
+ return bind(INADDR_ANY, local_port);
+ }
+
+ // set this socket up to be a listening port
+ // must have been bound to a local port already
+ // - length of backlog queue to manage - may be zero
+ // - returns success status
+ bool listen(unsigned short queue)
+ {
+ if (!initialised()) return false;
+ // set the port to listen for incoming connections
+ if (::listen(m_socket, (int)queue) == SOCKET_ERROR)
+ {
+ set_error(ERRNO);
+ close();
+ return false;
+ }
+ return true;
+ }
+
+ // test whether there's an incoming connection on the socket
+ // only applicable if it has been set up as a listening port
+ bool accept_ready(unsigned wait)
+ {
+ // the test for a connection being ready is the same as the test for whether the socket is readable
+ // see documentation for select
+ return select(true, false, wait);
+ }
+
+ // accept a connection on the socket
+ // only applicable if it has been set up as a listening port
+ // - returns socket filled in with the accepted connection's details - or with the error fields set
+ IP_socket accept(void)
+ {
+ if (!initialised()) return IP_socket();
+ IP_socket result;
+ // accept the connection, at the same time getting the address of the connecting client
+ sockaddr saddress;
+ SOCKLEN_T saddress_length = sizeof(saddress);
+ SOCKET socket = ::accept(m_socket, &saddress, &saddress_length);
+ if (socket == INVALID_SOCKET)
+ {
+ // only set the result socket with an error
+ result.m_impl->set_error(ERRNO);
+ return result;
+ }
+ // extract the contents of the address
+ unsigned long remote_address = 0;
+ unsigned short remote_port = 0;
+ convert_sockaddr(saddress, remote_address, remote_port);
+ result.m_impl->set(socket, remote_address, remote_port);
+ return result;
+ }
+
+ // client connect to a server
+ // - remote_address: IP number of remote address to connect to
+ // - remote_port: port to connect to
+ bool connect(unsigned long remote_address, unsigned short remote_port)
+ {
+ if (!initialised()) return false;
+ // fill in the connection data structure
+ sockaddr connect_data;
+ convert_address(remote_address, remote_port, connect_data);
+ // connect binds the socket to a local address
+ // if connectionless it simply sets the default remote address
+ // if connectioned it makes the connection
+ if (::connect(m_socket, &connect_data, sizeof(connect_data)) == SOCKET_ERROR)
+ {
+ // the socket is non-blocking, so connect will almost certainly fail with EINPROGRESS which is not an error
+ // only catch real errors
+ int error = ERRNO;
+ if (error != EINPROGRESS && error != EWOULDBLOCK)
+ {
+ set_error(error);
+ return false;
+ }
+ }
+ // extract the remote connection details for local storage
+ convert_sockaddr(connect_data, m_remote_address, m_remote_port);
+ return true;
+ }
+
+ // test whether a socket is connected and ready to communicate
+ bool connected(unsigned wait)
+ {
+ if (!initialised()) return false;
+ // Linux and Windows docs say test with select for whether socket is
+ // writable. However, a problem has been reported with Linux whereby
+ // the OS will report a socket as writable when it isn't
+ // first use the select method
+ if (!select(false, true, wait))
+ return false;
+#ifdef MSWINDOWS
+ // Windows needs no further processing - select method works
+ return true;
+#else
+ // Posix version needs further checking using the socket options
+ // DJDM: socket has returned EINPROGRESS on the first attempt at connection
+ // it has also returned that it can be written to
+ // we must now ask it if it has actually connected - using getsockopt
+ int error = 0;
+ socklen_t serror = sizeof(int);
+ if (::getsockopt(m_socket, SOL_SOCKET, SO_ERROR, &error, &serror)==0)
+ // handle the error value - one of them means that the socket has connected
+ if (!error || error == EISCONN)
+ return true;
+ return false;
+#endif
+ }
+
+ bool close(void)
+ {
+ bool result = true;
+ if (initialised())
+ {
+ if (shutdown(m_socket,SHUT_RDWR) == SOCKET_ERROR)
+ {
+ set_error(ERRNO);
+ result = false;
+ }
+ if (CLOSE(m_socket) == SOCKET_ERROR)
+ {
+ set_error(ERRNO);
+ result = false;
+ }
+ }
+ m_socket = INVALID_SOCKET;
+ m_remote_address = 0;
+ m_remote_port = 0;
+ return result;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // sending/receiving
+
+ bool send_ready(unsigned wait)
+ {
+ // determines whether the socket is ready to send by testing whether it is writable
+ return select(false, true, wait);
+ }
+
+ bool send (std::string& data)
+ {
+ if (!initialised()) return false;
+ // send the data - this will never block but may not send all the data
+ int bytes = ::send(m_socket, data.c_str(), data.size(), 0);
+ if (bytes == SOCKET_ERROR)
+ {
+ set_error(ERRNO);
+ return false;
+ }
+ // remove the sent bytes from the data buffer so that the buffer represents the data still to be sent
+ data.erase(0,bytes);
+ return true;
+ }
+
+ bool send_packet(std::string& data, unsigned long address = 0, unsigned short port = 0)
+ {
+ if (!initialised()) return false;
+ // if no address specified, rely on the socket having been connected (can I test this?)
+ // so use the standard send, otherwise use the sendto function
+ int bytes = 0;
+ if (!address)
+ {
+ bytes = ::send(m_socket, data.c_str(), data.size(), 0);
+ }
+ else
+ {
+ sockaddr saddress;
+ convert_address(address, port, saddress);
+ bytes = ::sendto(m_socket, data.c_str(), data.size(), 0, &saddress, sizeof(saddress));
+ }
+ if (bytes == SOCKET_ERROR)
+ {
+ set_error(ERRNO);
+ return false;
+ }
+ // remove the sent bytes from the data buffer so that the buffer represents the data still to be sent
+ data.erase(0,bytes);
+ return true;
+ }
+
+ bool receive_ready(unsigned wait)
+ {
+ // determines whether the socket is ready to receive by testing whether it is readable
+ return select(true, false, wait);
+ }
+
+ bool receive (std::string& data)
+ {
+ if (!initialised()) return false;
+ // determine how much data is available to read
+ unsigned long bytes = 0;
+ if (IOCTL(m_socket, FIONREAD, &bytes) == SOCKET_ERROR)
+ {
+ set_error(ERRNO);
+ return false;
+ }
+ // get the data up to the amount claimed to be present - this is non-blocking
+ char* buffer = new char[bytes+1];
+ int read = ::recv(m_socket, buffer, bytes, 0);
+ if (read == SOCKET_ERROR)
+ {
+ delete[] buffer;
+ set_error(ERRNO);
+ close();
+ return false;
+ }
+ if (read == 0)
+ {
+ // TODO - check whether this is an appropriate conditon to close the socket
+ close();
+ }
+ else
+ {
+ // this is binary data so copy the bytes including nulls
+ data.append(buffer,read);
+ }
+ delete[] buffer;
+ return true;
+ }
+
+ bool receive_packet(std::string& data, unsigned long& address, unsigned short& port)
+ {
+ if (!initialised()) return false;
+ // determine how much data is available to read
+ unsigned long bytes = 0;
+ if (IOCTL(m_socket, FIONREAD, &bytes) == SOCKET_ERROR)
+ {
+ set_error(ERRNO);
+ return false;
+ }
+ // get the data up to the amount claimed to be present - this is non-blocking
+ // also get the sender's details
+ char* buffer = new char[bytes+1];
+ sockaddr saddress;
+ SOCKLEN_T saddress_length = sizeof(saddress);
+ int read = ::recvfrom(m_socket, buffer, bytes, 0, &saddress, &saddress_length);
+ if (read == SOCKET_ERROR)
+ {
+ // UDP connection reset means that a previous sent failed to deliver cos the address was unknown
+ // this is NOT an error with the sending server socket, which IS still usable
+ int error = ERRNO;
+ if (error != ECONNRESET)
+ {
+ delete[] buffer;
+ set_error(error);
+ close();
+ return false;
+ }
+ }
+ // this is binary data so copy the bytes including nulls
+ data.append(buffer,read);
+ // also retrieve the sender's details
+ convert_sockaddr(saddress, address, port);
+ delete[] buffer;
+ return true;
+ }
+
+ bool receive_packet(std::string& data)
+ {
+ // call the above and then discard the address details
+ unsigned long address = 0;
+ unsigned short port = 0;
+ return receive_packet(data, address, port);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // informational
+
+ IP_socket_type type(void) const
+ {
+ return m_type;
+ }
+
+ unsigned short local_port(void) const
+ {
+ if (!initialised()) return 0;
+ sockaddr saddress;
+ SOCKLEN_T saddress_length = sizeof(saddress);
+ if (::getsockname(m_socket, &saddress, &saddress_length) != 0)
+ {
+ set_error(ERRNO);
+ return 0;
+ }
+ unsigned long address = 0;
+ unsigned short port = 0;
+ convert_sockaddr(saddress, address, port);
+ return port;
+ }
+
+ unsigned long remote_address(void) const
+ {
+ return m_remote_address;
+ }
+
+ unsigned short remote_port(void) const
+ {
+ return m_remote_port;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // error handling
+
+ void set_error (int error, const char* message = 0) const
+ {
+ if (error != 0)
+ {
+ m_error = error;
+ if (message && (message[0] != 0))
+ m_message = message;
+ else
+ m_message = error_string(error);
+ }
+ }
+
+ int error(void) const
+ {
+ return m_error;
+ }
+
+ void clear_error (void) const
+ {
+ m_error = 0;
+ m_message.erase();
+ }
+
+ std::string message(void) const
+ {
+ return m_message;
+ }
+
+ };
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // Socket - common code to manipulate a socket
+
+ // create an uninitialised socket
+ IP_socket::IP_socket(void) : m_impl(new IP_socket_internals)
+ {
+ }
+
+ // create an initialised socket
+ // - type: create either a TCP or UDP socket - if neither, creates an uninitialised socket
+ IP_socket::IP_socket(IP_socket_type type) : m_impl(new IP_socket_internals)
+ {
+ initialise(type);
+ }
+
+ // destroy the socket, closing it if open
+ IP_socket::~IP_socket(void)
+ {
+ if (m_impl->decrement())
+ delete m_impl;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // copying is implemented as aliasing
+
+ IP_socket::IP_socket(const IP_socket& right) : m_impl(0)
+ {
+ // make this an alias of right
+ m_impl = right.m_impl;
+ m_impl->increment();
+ }
+
+ IP_socket& IP_socket::operator=(const IP_socket& right)
+ {
+ // make self-copy safe
+ if (m_impl == right.m_impl) return *this;
+ // first dealias the existing implementation
+ if (m_impl->decrement())
+ delete m_impl;
+ // now make this an alias of right
+ m_impl = right.m_impl;
+ m_impl->increment();
+ return *this;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // initialisation, connection
+
+ // initialise the socket
+ // - type: create either a TCP or UDP socket
+ // - returns success status
+ bool IP_socket::initialise(IP_socket_type type)
+ {
+ return m_impl->initialise(type);
+ }
+
+ // test whether this is an initialised socket
+ // - returns whether this is initialised
+ bool IP_socket::initialised(void) const
+ {
+ return m_impl->initialised();
+ }
+
+ // close, i.e. disconnect the socket
+ // - returns a success flag
+ bool IP_socket::close(void)
+ {
+ return m_impl->close();
+ }
+
+ // function for performing IP lookup (i.e. gethostbyname)
+ // could be standalone but making it a member means that it can use the socket's error handler
+ // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96)
+ // - returns the IP address as a long integer - zero if there's an error
+ unsigned long IP_socket::ip_lookup(const std::string& remote_address)
+ {
+ return m_impl->ip_lookup(remote_address);
+ }
+
+ // test whether a socket is ready to communicate
+ // - readable: test whether socket is ready to read
+ // - writeable: test whether a socket is ready to write
+ // - timeout: if socket is not ready, time to wait before giving up - in micro-seconds - 0 means don't wait
+ // returns false if not ready or error - use error() method to tell - true if ready
+ bool IP_socket::select(bool readable, bool writeable, unsigned timeout)
+ {
+ return m_impl->select(readable, writeable, timeout);
+ }
+
+ // bind the socket to a port so that it can receive from specific address - typically used by a client
+ // - remote_address: IP number of remote server to send/receive to/from
+ // - local_port: port on local machine to bind to the address
+ // - returns success flag
+ bool IP_socket::bind(unsigned long remote_address, unsigned short local_port)
+ {
+ return m_impl->bind(remote_address, local_port);
+ }
+
+ // bind the socket to a port so that it can receive from any address - typically used by a server
+ // - local_port: port on local machine to bind to the address
+ // - returns success flag
+ bool IP_socket::bind_any(unsigned short local_port)
+ {
+ return m_impl->bind_any(local_port);
+ }
+
+ // initialise a socket and set this socket up to be a listening port
+ // - queue: length of backlog queue to manage - may be zero
+ // - returns success status
+ bool IP_socket::listen(unsigned short queue)
+ {
+ return m_impl->listen(queue);
+ }
+
+ // test for a connection on the object's socket - only applicable if it has been set up as a listening port
+ // - returns true if a connection is ready to be accepted
+ bool IP_socket::accept_ready(unsigned timeout) const
+ {
+ return m_impl->accept_ready(timeout);
+ }
+
+ // accept a connection on the object's socket - only applicable if it has been set up as a listening port
+ // - returns the connection as a new socket
+ IP_socket IP_socket::accept(void)
+ {
+ return m_impl->accept();
+ }
+
+ // client connect to a server
+ // - address: IP number already lookup up with ip_lookup
+ // - port: port to connect to
+ // - returns a success flag
+ bool IP_socket::connect(unsigned long address, unsigned short port)
+ {
+ return m_impl->connect(address, port);
+ }
+
+ // test whether a socket is connected and ready to communicate, returns on successful connect or timeout
+ // - timeout: how long to wait in microseconds if not connected yet
+ // - returns success flag
+ bool IP_socket::connected(unsigned timeout)
+ {
+ return m_impl->connected(timeout);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // sending/receiving
+
+ // test whether a socket is connected and ready to send data, returns if ready or on timeout
+ // - timeout: how long to wait in microseconds if not connected yet (blocking)
+ // - returns status
+ bool IP_socket::send_ready(unsigned timeout)
+ {
+ return m_impl->send_ready(timeout);
+ }
+
+ // send data through the socket - if the data is long only part of it may
+ // be sent. The sent part is removed from the data, so the same string can
+ // be sent again and again until it is empty.
+ // - data: string containing data to be sent - any data successfully sent is removed
+ // - returns success flag
+ bool IP_socket::send (std::string& data)
+ {
+ return m_impl->send(data);
+ }
+
+ // send data through a connectionless (UDP) socket
+ // the data will be sent as a single packet
+ // - packet: string containing data to be sent - any data successfully sent is removed
+ // - remote_address: address of the remote host to send to - optional if the socket has been connected to remote
+ // - remote_port: port of the remote host to send to - optional if the socket has been connected to remote
+ // - returns success flag
+ bool IP_socket::send_packet(std::string& packet, unsigned long remote_address, unsigned short remote_port)
+ {
+ return m_impl->send_packet(packet, remote_address, remote_port);
+ }
+
+ // send data through a connectionless (UDP) socket
+ // the data will be sent as a single packet
+ // only works if the socket has been connected to remote
+ // - packet: string containing data to be sent - any data successfully sent is removed
+ // - returns success flag
+ bool IP_socket::send_packet(std::string& packet)
+ {
+ return m_impl->send_packet(packet);
+ }
+
+ // test whether a socket is connected and ready to receive data, returns if ready or on timeout
+ // - timeout: how long to wait in microseconds if not connected yet (blocking)
+ // - returns status
+ bool IP_socket::receive_ready(unsigned timeout)
+ {
+ return m_impl->receive_ready(timeout);
+ }
+
+ // receive data through a connection-based (TCP) socket
+ // if the data is long only part of it may be received. The received data
+ // is appended to the string, building it up in stages, so the same string
+ // can be received again and again until all information has been
+ // received.
+ // - data: string receiving data from socket - any data successfully received is appended
+ // - returns success flag
+ bool IP_socket::receive (std::string& data)
+ {
+ return m_impl->receive(data);
+ }
+
+ // receive data through a connectionless (UDP) socket
+ // - packet: string receiving data from socket - any data successfully received is appended
+ // - remote_address: returns the address of the remote host received from
+ // - remote_port: returns the port of the remote host received from
+ // - returns success flag
+ bool IP_socket::receive_packet(std::string& packet, unsigned long& remote_address, unsigned short& remote_port)
+ {
+ return m_impl->receive_packet(packet, remote_address, remote_port);
+ }
+
+ // variant of above which does not give back the address and port of the sender
+ // receive data through a connectionless (UDP) socket
+ // - packet: string receiving data from socket - any data successfully received is appended
+ // - returns success flag
+ bool IP_socket::receive_packet(std::string& packet)
+ {
+ return m_impl->receive_packet(packet);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // informational
+
+ IP_socket_type IP_socket::type(void) const
+ {
+ return m_impl->type();
+ }
+
+ unsigned short IP_socket::local_port(void) const
+ {
+ return m_impl->local_port();
+ }
+
+ unsigned long IP_socket::remote_address(void) const
+ {
+ return m_impl->remote_address();
+ }
+
+ unsigned short IP_socket::remote_port(void) const
+ {
+ return m_impl->remote_port();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // error handling
+
+ void IP_socket::set_error (int error, const std::string& message) const
+ {
+ m_impl->set_error(error, message.c_str());
+ }
+
+ void IP_socket::clear_error (void) const
+ {
+ m_impl->clear_error();
+ }
+
+ int IP_socket::error(void) const
+ {
+ return m_impl->error();
+ }
+
+ std::string IP_socket::message(void) const
+ {
+ return m_impl->message();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+} // end namespace stlplus