/*] 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. * **************************************************************************/ #ifndef _MOOF_SOCKET_HH_ #define _MOOF_SOCKET_HH_ /** * \file socket.hh * Network-related classes, including a reinterpreted sockets API. */ #include #include #include #include #include #include #include #if defined(_WIN32) #include #include #include #define SHUT_RD SD_RECEIVE #define SHUT_WR SD_SEND #define SHUT_RDWR SD_BOTH #else #include #include #include #include #endif #include #include #include #ifndef AI_ADDRCONFIG #define AI_ADDRCONFIG 0 #endif #ifndef AI_V4MAPPED #define AI_V4MAPPED 0 #endif namespace moof { /** * The socket class represents a connection or between this node and a * remote node. */ class socket { public: /** * A class to represent the address of a remote host, including the * type of service and socket communication. */ class address { public: /** * Construct an unspecified address. */ address() : size_(0), type_(0) { addr_.sa.sa_family = AF_UNSPEC; addr_.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. */ address(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. */ explicit address(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. */ address(const struct addrinfo* addr) : size_(addr->ai_addrlen), type_(addr->ai_socktype) { memcpy(&addr_.sa, addr->ai_addr, addr->ai_addrlen); get_name_and_service(name_, service_); } /** * 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. */ address(const struct sockaddr* addr, size_t size, int type = SOCK_STREAM) : size_(size), type_(type) { memcpy(&addr_.sa, addr, size); get_name_and_service(name_, service_); } /** * Get an IPv4 broadcast address. * \param service The service name or port number. * \return The socket address. */ static address 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 address((struct 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) { const int flags = AI_ADDRCONFIG | AI_NUMERICHOST | AI_V4MAPPED; struct addrinfo* addr = resolve(service.c_str(), name.c_str(), type, family, flags); if (addr) { size_ = addr->ai_addrlen; type_ = addr->ai_socktype; memcpy(&addr_.sa, addr->ai_addr, addr->ai_addrlen); service_ = service; name_ = name; freeaddrinfo(addr); } else { type_ = 0; size_ = 0; addr_.sa.sa_family = AF_UNSPEC; addr_.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) { size_ = addr->ai_addrlen; type_ = addr->ai_socktype; memcpy(&addr_.sa, addr->ai_addr, addr->ai_addrlen); service_ = service; get_name(name_); freeaddrinfo(addr); } else { type_ = 0; size_ = 0; addr_.sa.sa_family = AF_UNSPEC; addr_.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 service_; } /** * 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 name_; } /** * Get the port number of the address service. * \return Port number. */ unsigned short port() const { return ntohs(addr_.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 type_; } /** * 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 addr_.sa.sa_family; } /** * Get the sockaddr structure of the address. * \return The sockaddr structure. */ const struct sockaddr* sockaddr() const { return size_ != 0 ? &addr_.sa : 0; } /** * Get the size of the sockaddr structure of the address. * \return The size of the sockaddr structure. */ size_t size() const { return size_; } /** * 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 = collect_addresses(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 = collect_addresses(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 get_name(std::string& name, int flags = NI_NUMERICHOST) { char node[256] = {'\0'}; int result = getnameinfo(&addr_.sa, size_, 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 get_service(std::string& service, int flags) { flags |= type_ == SOCK_DGRAM ? NI_DGRAM : 0; char serv[64] = {'\0'}; int result = getnameinfo(&addr_.sa, size_, 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 get_name_and_service(std::string& name, std::string& service, int flags = NI_NUMERICHOST) { flags |= type_ == SOCK_DGRAM ? NI_DGRAM : 0; char serv[64] = {'\0'}; char node[256] = {'\0'}; int result = getnameinfo(&addr_.sa, size_, 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 { log_warning(gai_strerror(status)); return 0; } } static int collect_addresses(struct addrinfo* addresses, std::vector
& resolved) { if (addresses) { resolved.clear(); for (struct addrinfo* addr = addresses; addr != 0; addr = addr->ai_next) { resolved.push_back(address(addr)); } return 0; } else return -1; } union { struct sockaddr sa; struct sockaddr_in in; struct sockaddr_storage storage; } addr_; size_t size_; int type_; std::string name_; std::string service_; }; private: struct impl { socket::address address; int fd; bool is_connected; impl() : fd(-1), is_connected(false) {} impl(const socket::address& address, int flags = 0) : address(address), fd(::socket(address.family(), address.type(), flags)), is_connected(false) {} } impl_; 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 address& address, int flags = 0) : impl_(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) : impl_(address(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. */ explicit socket(const std::string& service, int type = SOCK_STREAM, int family = AF_UNSPEC, int flags = 0) : impl_(address(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 is_connected() const { return impl_.is_connected; } /** * Get the address associated with the socket. */ const address& peer_address() const { return impl_.address; } /** * Connect the socket to its peer. * \return 0 on success, -1 on failure. */ int connect() { int result = ::connect(impl_.fd, impl_.address.sockaddr(), impl_.address.size()); impl_.is_connected = 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(impl_.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(impl_.fd, impl_.address.sockaddr(), impl_.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(impl_.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) { moof::socket temp = moof::socket(impl_.fd); if (temp.impl_.fd != -1) { socket = temp; return socket.impl_.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) { #if defined(_WIN32) return setsockopt(impl_.fd, level, option, reinterpret_cast(&value), sizeof(value)); #else return setsockopt(impl_.fd, level, option, &value, sizeof(value)); #endif } /** * 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(impl_.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(impl_.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); #if defined(_WIN32) int result = getsockopt(impl_.fd, level, option, reinterpret_cast(&str), &size); #else int result = getsockopt(impl_.fd, level, option, &str, &size); #endif value.assign(str, size); return result; } /** * Set the socket IO mode to either blocking or non-blocking. * \param is_blocking True if the socket should block, false otherwise. */ void is_blocking(bool is_blocking) { #if defined(_WIN32) u_long value = is_blocking; ioctlsocket(impl_.fd, FIONBIO, &value); #else int flags = fcntl(impl_.fd, F_GETFL); flags = is_blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); fcntl(impl_.fd, F_SETFL, flags); #endif } /** * Get whether or not the socket is blocking or non-blocking. If the * IO mode can't be determined, this method will assume the socket is * a blocking socket. * \return True if the socket blocks, false otherwise. */ bool is_blocking() const { #if defined(_WIN32) return true; #else int flags = fcntl(impl_.fd, F_GETFL); return !(flags & O_NONBLOCK); #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) { #if defined(_WIN32) return send(impl_.fd, reinterpret_cast(bytes), size, flags); #else return send(impl_.fd, bytes, size, flags); #endif } /** * 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 address& address, int flags = 0) { #if defined(_WIN32) return sendto(impl_.fd, reinterpret_cast(bytes), size, flags, address.sockaddr(), address.size()); #else return sendto(impl_.fd, bytes, size, flags, address.sockaddr(), address.size()); #endif } /** * 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 address& 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) { #if defined(_WIN32) ssize_t result = recv(impl_.fd, reinterpret_cast(bytes), size, flags); #else ssize_t result = recv(impl_.fd, bytes, size, flags); #endif if (result == 0) impl_.is_connected = false; return result; } /** * 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, socket::address& address, int flags = 0) { union { struct sockaddr sa; struct sockaddr_storage storage; } addr; socklen_t length = sizeof(addr); #if defined(_WIN32) ssize_t result = recvfrom(impl_.fd, reinterpret_cast(bytes), size, flags, &addr.sa, &length); #else ssize_t result = recvfrom(impl_.fd, bytes, size, flags, &addr.sa, &length); #endif if (result != -1) { address = socket::address(&addr.sa, length, impl_.address.type()); } else if (result == 0) { impl_.is_connected = false; } 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 = moof::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, address& address, int flags = 0) { char buffer[65536]; ssize_t result = read(buffer, sizeof(buffer), address, flags); if (result != -1) packet = moof::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) : impl_(move.impl_) { move.impl_.fd = -1; move.impl_.is_connected = false; } socket(impl move) : impl_(move) {} socket& operator=(socket& move) { close(); impl_ = move.impl_; move.impl_.fd = -1; move.impl_.is_connected = false; return *this; } socket& operator=(impl move) { close(); impl_ = move; return *this; } operator impl() { impl impl(impl_); impl_.fd = -1; impl_.is_connected = false; return impl; } private: socket(int fd) { // for accepting a socket from fd union { struct sockaddr sa; struct sockaddr_storage storage; } addr; socklen_t length = sizeof(addr); impl_.fd = ::accept(fd, &addr.sa, &length); if (impl_.fd != -1) { impl_.is_connected = true; impl_.address = address(&addr.sa, length); } } void close() { #if defined(_WIN32) if (impl_.fd != -1) closesocket(impl_.fd); #else if (impl_.fd != -1) ::close(impl_.fd); #endif } }; class socket_multiplexer { public: typedef boost::function function; explicit socket_multiplexer(moof::socket sock) : socket_(sock) {} void socket(moof::socket sock) { mutex::scoped_lock lock(mutex_); socket_ = sock; } moof::socket& socket() { return socket_; } std::vector& protocols() { return protocols_; } void update(scalar t, scalar dt) { socket::address address; packet packet; ssize_t bytes = socket_.read(packet, address); if (bytes > 0) { std::vector::iterator it; for (it = protocols_.begin(); it < protocols_.end(); ++it) { packet.revert(); if ((*it)(*this, packet, address)) break; } } } int background() { return 0; } private: moof::socket socket_; std::vector protocols_; mutex mutex_; }; /** * An asynchronous task to resolve addresses. */ class resolver_task : public threaded_task { 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. */ resolver_task(const std::string& service, const std::string& name, int type = SOCK_STREAM, int family = AF_UNSPEC) : is_done_(false) { function_ = boost::bind(&resolver_task::resolve, this, service, name, type, family); } /** * Get whether or not the task is done. * \return True if the task has finished, false otherwise. */ bool is_done() const { return is_done_; } /** * Start the task. This does nothing if the task was already run or is * currently running. */ void run() { if (!is_done() && !thread_.is_valid()) { thread_ = thread::detach(function_); } } /** * Get the addresses resolved. This is filled and safe to access after * the task finishes. * \return List of addresses. * \see is_done() */ const std::vector& addresses() const { return address_list_; } private: int resolve(const std::string& service, const std::string& name, int type, int family) { int status = socket::address::resolve(service, name, type, family, address_list_); is_done_ = true; return status; } std::vector address_list_; bool is_done_; thread::function function_; }; /** * Insert a string representation of a socket address into a stream. * \param stream The output stream. * \param addr The socket address. * \return The stream. */ std::ostream& operator << (std::ostream& stream, const socket::address& addr) { stream << addr.name() << ":" << addr.service(); return stream; } /** * Insert a string representation of a socket into a stream. * \param stream The output stream. * \param addr The socket. * \return The stream. */ std::ostream& operator << (std::ostream& stream, const socket& sock) { stream << sock.peer_address(); return stream; } } // namespace moof #endif // _MOOF_SOCKET_HH_