/*] Copyright (c) 2009-2010, Charles McGarvey [************************** **] All rights reserved. * * vi:ts=4 sw=4 tw=75 * * Distributable under the terms and conditions of the 2-clause BSD license; * see the file COPYING for a complete text of the license. * **************************************************************************/ /** * \file Network.hh * Network-related classes, including a reinterpreted sockets API. */ #ifndef _MOOF_SOCKET_HH_ #define _MOOF_SOCKET_HH_ #include #include #include #include #include #include #if defined(_WIN32) #include #include #include #else #include #include #include #include #if HAVE_FCNTL_H #include #else #include #endif #endif #include #include #include #ifndef SO_NONBLOCK #define SO_NONBLOCK 1024 #endif namespace Mf { /** * A class to represent the address of a remote host, including the type of * service and socket communication. */ class SocketAddress { public: /** * Construct an unspecified address. */ SocketAddress() : mSize(0), mType(0) { mAddr.sa.sa_family = AF_UNSPEC; mAddr.in.sin_port = 0; } /** * Construct an address with a specified host. The address can be used * to connect to a host. * \param service The service name or port number. * \param name The numeric IP address of the host. * \param type The type of socket; either SOCK_STREAM or SOCK_DGRAM. * \param family The family; can be AF_INET or AF_INET6. */ SocketAddress(const std::string& service, const std::string& name, int type = SOCK_STREAM, int family = AF_UNSPEC) { init(service, name, type, family); } /** * Construct an address without a specified host. The address can be * used to accept on a local port. * \param service The service name or port number. * \param type The type of socket; either SOCK_STREAM or SOCK_DGRAM. * \param family The family; can be AF_INET or AF_INET6. */ SocketAddress(const std::string& service, int type = SOCK_STREAM, int family = AF_UNSPEC) { init(service, type, family); } /** * Construct an address from the information in an addrinfo structure. * \param addr The addrinfo structure. */ SocketAddress(const struct addrinfo* addr) : mSize(addr->ai_addrlen), mType(addr->ai_socktype) { memcpy(&mAddr.sa, addr->ai_addr, addr->ai_addrlen); getNameAndService(mService, mName); } /** * Construct an address from a sockaddr structure. * \param addr The sockaddr structure. * \param size The size of the sockaddr structure. * \param type The type of socket; either SOCK_STREAM or SOCK_DGRAM. */ SocketAddress(const struct sockaddr* addr, size_t size, int type = SOCK_STREAM) : mSize(size), mType(type) { memcpy(&mAddr.sa, addr, size); getNameAndService(mService, mName); } /** * Get an IPv4 broadcast address. * \param service The service name or port number. * \return The socket address. */ static SocketAddress broadcast(const std::string& service) { std::istringstream stream(service); unsigned short port; stream >> port; struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_BROADCAST; memset(&addr.sin_zero, 0, sizeof(addr.sin_zero)); return SocketAddress((sockaddr*)&addr, sizeof(addr), SOCK_DGRAM); } /** * Initialize the address with a specified host. The address can be * used to connect to a host. * \param service The service name or port number. * \param name The numeric IP address of the host. * \param type The type of socket; either SOCK_STREAM or SOCK_DGRAM. * \param family The family; can be AF_INET or AF_INET6. */ void init(const std::string& service, const std::string& name, int type = SOCK_STREAM, int family = AF_UNSPEC) { struct addrinfo* addr = resolve(service.c_str(), name.c_str(), type, family, AI_ADDRCONFIG | AI_NUMERICHOST | AI_V4MAPPED); if (addr) { mSize = addr->ai_addrlen; mType = addr->ai_socktype; memcpy(&mAddr.sa, addr->ai_addr, addr->ai_addrlen); mService = service; mName = name; freeaddrinfo(addr); } else { mType = 0; mSize = 0; mAddr.sa.sa_family = AF_UNSPEC; mAddr.in.sin_port = 0; } } /** * Initialize the address without a specified host. The address can be * used to accept on a local port. * \param service The service name or port number. * \param type The type of socket; either SOCK_STREAM or SOCK_DGRAM. * \param family The family; can be AF_INET or AF_INET6. */ void init(const std::string& service, int type = SOCK_STREAM, int family = AF_UNSPEC) { struct addrinfo* addr = resolve(service.c_str(), 0, type, family, AI_PASSIVE); if (addr) { mSize = addr->ai_addrlen; mType = addr->ai_socktype; memcpy(&mAddr.sa, addr->ai_addr, addr->ai_addrlen); mService = service; getName(mName); freeaddrinfo(addr); } else { mType = 0; mSize = 0; mAddr.sa.sa_family = AF_UNSPEC; mAddr.in.sin_port = 0; } } /** * Get the name of the service. This could also be a port number if * there is no service name associated with the number. * \return The service. */ const std::string& service() const { return mService; } /** * Get the name of the host. This may be the host used to construct * the address, or a resolved numeric host if none was used. * \return The host. */ const std::string& name() const { return mName; } /** * Get the port number of the address service. * \return Port number. */ unsigned short port() const { return ntohs(mAddr.in.sin_port); } /** * Get the type of socket associated with the service of this address. * \return Socket type; either SOCK_STREAM or SOCK_DGRAM. */ int type() const { return mType; } /** * Get the family of the protocol associated with the address. * \return Protocol family; either AF_INET, AF_INET6, or AF_UNSPEC. */ int family() const { return mAddr.sa.sa_family; } /** * Get the sockaddr structure of the address. * \return The sockaddr structure. */ const struct sockaddr* address() const { return mSize != 0 ? &mAddr.sa : 0; } /** * Get the size of the sockaddr structure of the address. * \return The size of the sockaddr structure. */ size_t size() const { return mSize; } /** * Get a list of addresses resolved to by the given search criteria. * This can be used to perform lookups for name resolution, so this * method may take some time to return. Use the ResolveTask class to * resolve addresses asynchronously. * \param service The service name or port number. * \param name The name of the local or remote host. * \param type The type of socket; either SOCK_STREAM or SOCK_DGRAM. * \param family The family; can be AF_INET or AF_INET6. * \param resolved The list to be filled with addresses. * \return 0 on success, -1 on error. */ static int resolve(const std::string& service, const std::string& name, int type, int family, std::vector& resolved) { struct addrinfo* list = resolve(service.c_str(), name.c_str(), type, family, AI_ADDRCONFIG | AI_V4MAPPED); int result = collectAddresses(list, resolved); freeaddrinfo(list); return result; } /** * Get a list of addresses resolved to by the given search criteria. * The addresses will be suitable for accepting on a local port. * \param service The service name or port number. * \param type The type of socket; either SOCK_STREAM or SOCK_DGRAM. * \param family The family; can be AF_INET or AF_INET6. * \param resolved The list to be filled with addresses. * \return 0 on success, -1 on error. */ static int resolve(const std::string& service, int type, int family, std::vector& resolved) { struct addrinfo* list = resolve(service.c_str(), 0, type, family, AI_PASSIVE); int result = collectAddresses(list, resolved); freeaddrinfo(list); return result; } /** * Resolve the hostname of the address. The default behavior is to * avoid a reverse lookup by giving the numeric address. You can * change that behavior with the getnameinfo flags. * \param name The place to store the hostname or IP address. * \param flags The getnameinfo flags. */ void getName(std::string& name, int flags = NI_NUMERICHOST) { char node[256] = {'\0'}; int result = getnameinfo(&mAddr.sa, mSize, node, sizeof(node), 0, 0, flags); if (result == 0) name.assign(node); } /** * Resolve the service name of the address. * \param service The place to store the service name or port number. * \param flags The getnameinfo flags. */ void getService(std::string& service, int flags) { flags |= mType == SOCK_DGRAM ? NI_DGRAM : 0; char serv[64] = {'\0'}; int result = getnameinfo(&mAddr.sa, mSize, 0, 0, serv, sizeof(serv), flags); if (result == 0) service.assign(serv); } /** * Resolve the service and hostname of the address. The default * behavior is to avoid a reverse lookup by giving the numeric address. * You can change that behavior with the getnameinfo flags. * \param name The place to store the hostname or IP address. * \param service The place to store the service name or port number. * \param flags The getnameinfo flags. */ void getNameAndService(std::string& name, std::string& service, int flags = NI_NUMERICHOST) { flags |= mType == SOCK_DGRAM ? NI_DGRAM : 0; char serv[64] = {'\0'}; char node[256] = {'\0'}; int result = getnameinfo(&mAddr.sa, mSize, node, sizeof(node), serv, sizeof(serv), flags); if (result == 0) { service.assign(serv); name.assign(node); } } private: static struct addrinfo* resolve(const char* service, const char* node, int type, int family, int flags) { ASSERT(type == SOCK_STREAM || type == SOCK_DGRAM); ASSERT(family == AF_INET || family == AF_INET6 || family == AF_UNSPEC); struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = type; hints.ai_flags = flags; struct addrinfo* addr; int status = getaddrinfo(node, service, &hints, &addr); if (status == 0) { return addr; } else { Mf::logWarning(gai_strerror(status)); return 0; } } static int collectAddresses(struct addrinfo* addresses, std::vector& resolved) { if (addresses) { resolved.clear(); for (struct addrinfo* addr = addresses; addr != 0; addr = addr->ai_next) { resolved.push_back(SocketAddress(addr)); } return 0; } else return -1; } union { sockaddr sa; sockaddr_in in; sockaddr_storage storage; } mAddr; size_t mSize; int mType; std::string mName; std::string mService; }; /** * The socket class represents a connection or between this node and a * remote node. */ class Socket { struct Impl { SocketAddress address; int fd; bool isConnected; Impl() : fd(-1), isConnected(false) {} Impl(const SocketAddress& address, int flags = 0) : address(address), fd(::socket(address.family(), address.type(), flags)), isConnected(false) {} } mImpl; public: /** * Construct a socket with no associated peer. */ Socket() {} /** * Construct a socket with an address. * \param address The address. * \param flags The socket options. */ Socket(const SocketAddress& address, int flags = 0) : mImpl(address, flags) {} /** * Construct a socket with a specified host. The socket can be used to * connect to a host. * \param service The service name or port number. * \param name The numeric IP address of the host. * \param type The type of socket; either SOCK_STREAM or SOCK_DGRAM. * \param family The family; can be AF_INET or AF_INET6. * \param flags The socket options. */ Socket(const std::string& service, const std::string& name, int type = SOCK_STREAM, int family = AF_UNSPEC, int flags = 0) : mImpl(SocketAddress(service, name, type, family), flags) {} /** * Construct a socket without a specified host. The socket can be used * to accept sockets on a local port. * \param service The service name or port number. * \param type The type of socket; either SOCK_STREAM or SOCK_DGRAM. * \param family The family; can be AF_INET or AF_INET6. * \param flags The socket options. */ Socket(const std::string& service, int type = SOCK_STREAM, int family = AF_UNSPEC, int flags = 0) : mImpl(SocketAddress(service, type, family), flags) {} /** * Deconstruct the socket, closing it. */ ~Socket() { close(); } /** * Get whether or not the socket is connected. * \return True if the socket is connected, false otherwise. */ bool isConnected() const { return mImpl.isConnected; } /** * Get the address associated with the socket. */ const SocketAddress& address() const { return mImpl.address; } /** * Connect the socket to its peer. * \return 0 on success, -1 on failure. */ int connect() { int result = ::connect(mImpl.fd, mImpl.address.address(), mImpl.address.size()); mImpl.isConnected = result != -1; return result; } /** * Disconnect a connected socket from its peer. * \param flags Specify the socket directions to close. * \return 0 on success, -1 on failure. */ int disconnect(int flags = SHUT_RDWR) { return shutdown(mImpl.fd, flags); } /** * Bind the socket to interface and port number specified in the * address. * \return 0 on success, -1 on failure. */ int bind() { return ::bind(mImpl.fd, mImpl.address.address(), mImpl.address.size()); } /** * Listen on the socket for incoming connections. This is only useful * for sockets of type SOCK_STREAM. * \param backlog The number of unaccepted connections to queue. * \return 0 on success, -1 on failure. */ int listen(int backlog = SOMAXCONN) { return ::listen(mImpl.fd, backlog > 0 ? backlog : SOMAXCONN); } /** * Accept a new connection on the socket. This is only useful for * sockets of type SOCK_STREAM. * \param socket Set to the new socket on return. * \return 0 on success, -1 on failure. */ int accept(Socket& socket) { Socket temp = Socket(mImpl.fd); if (temp.mImpl.fd != -1) { socket = temp; return socket.mImpl.fd; } return -1; } /** * Set an integer socket option. * \param option The option to set. * \param value The new value. * \param level The layer to handle the option. * \return 0 on success, -1 on failure. */ template int set(int option, const T& value, int level = SOL_SOCKET) { return setsockopt(mImpl.fd, level, option, &value, sizeof(value)); } /** * Set a string socket option. * \param option The option to set. * \param value The new value. * \param level The layer to handle the option. * \return 0 on success, -1 on failure. */ int set(int option, const std::string& value, int level = SOL_SOCKET) { return setsockopt(mImpl.fd, level, option, value.data(), value.length()); } /** * Get an integer socket option. * \param option The option to set. * \param value The new value. * \param level The layer to handle the option. * \return 0 on success, -1 on failure. */ template int get(int option, T& value, int level = SOL_SOCKET) const { int size = sizeof(value); return getsockopt(mImpl.fd, level, option, &value, &size); } /** * Get a string socket option. * \param option The option to set. * \param value The new value. * \param level The layer to handle the option. * \return 0 on success, -1 on failure. */ int get(int option, std::string& value, int level = SOL_SOCKET) const { char str[256] = {'\0'}; socklen_t size = sizeof(str); int result = getsockopt(mImpl.fd, level, option, &str, &size); value.assign(str, size); return result; } void setBlocking(bool isBlocking) { int value = isBlocking; #ifdef HAVE_FCNTL int flags = fcntl(mImpl.fd, F_GETFL); fcntl(mImpl.fd, F_SETFL, flags | (value ? O_NONBLOCK : 0)); #else ioctl(mImpl.fd, FIONBIO, value); #endif } bool isBlocking() const { #ifdef HAVE_FCNTL int flags = fcntl(mImpl.fd, F_GETFL); return flags & O_NONBLOCK; #else int value; ioctl(mImpl.fd, FIONBIO, &value); return value; #endif } /** * Write some bytes to the socket. Use this for connected sockets. * \param bytes The bytes. * \param size The number of bytes. * \param flags The send options. * \return The number of bytes written. */ ssize_t write(const void* bytes, size_t size, int flags = 0) { return send(mImpl.fd, bytes, size, flags); } /** * Write some bytes to the socket using the given address. Use this * for unconnected sockets. * \param bytes The bytes. * \param size The number of bytes. * \param address The address to send to. * \param flags The send options. * \return The number of bytes written. */ ssize_t write(const void* bytes, size_t size, const SocketAddress& address, int flags = 0) { return sendto(mImpl.fd, bytes, size, flags, address.address(), address.size()); } /** * Write a packet to the socket. Use this for connected sockets. * \param packet The packet. * \param flags The send options. * \return The number of bytes written. */ ssize_t write(const Packet& packet, int flags = 0) { return write(packet.bytes(), packet.size(), flags); } /** * Write a packet to the socket using the given address. Use this for * unconnected sockets. * \param packet The packet. * \param address The address to send to. * \param flags The send options. * \return The number of bytes written. */ ssize_t write(const Packet& packet, const SocketAddress& address, int flags = 0) { return write(packet.bytes(), packet.size(), address, flags); } /** * Read some bytes from the socket. Use this for connected sockets. * \param bytes The buffer to store the bytes. * \param size The size of the buffer. * \param flags The recv options. * \return The number of bytes read. */ ssize_t read(void* bytes, size_t size, int flags = 0) { return recv(mImpl.fd, bytes, size, flags); } /** * Read some bytes from the socket using the given address. Use this * for unconnected sockets. * \param bytes The buffer to store the bytes. * \param size The size of the buffer. * \param address The address to read from. * \param flags The recv options. * \return The number of bytes read. */ ssize_t read(void* bytes, size_t size, SocketAddress& address, int flags = 0) { union { sockaddr sa; sockaddr_storage storage; } addr; socklen_t length = sizeof(addr); ssize_t result = recvfrom(mImpl.fd, bytes, size, flags, &addr.sa, &length); if (result != -1) { address = SocketAddress(&addr.sa, length, mImpl.address.type()); } return result; } /** * Read a packet from the socket. Use this for connected sockets. * \param packet Set to the packet read on return. * \param flags The recv options. * \return The number of bytes read. */ ssize_t read(Packet& packet, int flags = 0) { char buffer[65536]; ssize_t result = read(buffer, sizeof(buffer), flags); if (result != -1) packet = Packet(buffer, result); return result; } /** * Read a packet from the socket using the given address. Use this for * unconnected sockets. * \param packet Set to the packet read on return. * \param address The address to read from. * \param flags The recv options. * \return The number of bytes read. */ ssize_t read(Packet& packet, SocketAddress& address, int flags = 0) { char buffer[65536]; ssize_t result = read(buffer, sizeof(buffer), address, flags); if (result != -1) packet = Packet(buffer, result); return result; } // The rest of this junk is used to implement the "move" semantics // correctly, since it makes no sense for socket objects to be copied. Socket(Socket& move) : mImpl(move.mImpl) { move.mImpl.fd = -1; move.mImpl.isConnected = false; } Socket(Impl move) : mImpl(move) {} Socket& operator=(Socket& move) { close(); mImpl = move.mImpl; move.mImpl.fd = -1; move.mImpl.isConnected = false; return *this; } Socket& operator=(Impl move) { close(); mImpl = move; return *this; } operator Impl() { Impl impl(mImpl); mImpl.fd = -1; mImpl.isConnected = false; return impl; } private: Socket(int fd) { // for accepting a socket from fd union { sockaddr sa; sockaddr_storage storage; } addr; socklen_t length = sizeof(addr); mImpl.fd = ::accept(fd, &addr.sa, &length); if (mImpl.fd != -1) { mImpl.isConnected = true; mImpl.address = SocketAddress(&addr.sa, length); } } void close() { #if defined(_WIN32) if (mImpl.fd != -1) closesocket(mImpl.fd); #else if (mImpl.fd != -1) ::close(mImpl.fd); #endif } }; class SocketMultiplexer { public: typedef boost::function Function; SocketMultiplexer(Socket sock) : mSocket(sock) {} void setSocket(Socket sock) { Mutex::ScopedLock lock(mMutex); mSocket = sock; } Socket& socket() { return mSocket; } std::vector& protocols() { return mProtocols; } void update(Scalar t, Scalar dt) { SocketAddress address; Packet packet; ssize_t bytes = mSocket.read(packet, address); if (bytes > 0) { std::vector::iterator it; for (it = mProtocols.begin(); it < mProtocols.end(); ++it) { packet.reset(); if ((*it)(*this, packet, address)) break; } } } int background() { return 0; } private: Socket mSocket; std::vector mProtocols; Mutex mMutex; }; /** * An asynchronous task to resolve addresses. */ class ResolverTask : public ThreadedTask { public: /** * Construct a resolver task from a service and hostname. * \param service Server name or port number. * \param name The hostname or numeric address. * \param type The type of communication. * \param family The requested protocol family. */ ResolverTask(const std::string& service, const std::string& name, int type = SOCK_STREAM, int family = AF_UNSPEC) : mIsDone(false) { mFunction = boost::bind(&ResolverTask::resolve, this, service, name, type, family); } /** * Get whether or not the task is done. * \return True if the task has finished, false otherwise. */ bool isDone() const { return mIsDone; } /** * Start the task. This does nothing if the task was already run or is * currently running. */ void run() { if (!isDone() && !mThread.isValid()) { mThread = Thread::detach(mFunction); } } /** * Get the addresses resolved. This is filled and safe to access after * the task finishes. * \return List of addresses. * \see isDone() */ const std::vector& addresses() const { return mAddressList; } private: int resolve(const std::string& service, const std::string& name, int type, int family) { int status = SocketAddress::resolve(service, name, type, family, mAddressList); mIsDone = true; return status; } std::vector mAddressList; bool mIsDone; Thread::Function mFunction; }; std::ostream& operator<<(std::ostream& stream, const SocketAddress& addr) { stream << addr.name() << ":" << addr.service(); return stream; } std::ostream& operator<<(std::ostream& stream, const Socket& sock) { stream << sock.address(); return stream; } } // namespace Mf #endif // _MOOF_SOCKET_HH_