X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fyoink;a=blobdiff_plain;f=src%2FMoof%2FSocket.hh;h=60fd8e054531aa080f7cc1740d6075664f3ffb18;hp=4b67887416429835f0ebff9b0cee6afae17faf96;hb=8c6a82feb045ac859b344cd7665a36ac00f4949e;hpb=41f8dd670e963aad94527ce2be0486268993a477 diff --git a/src/Moof/Socket.hh b/src/Moof/Socket.hh index 4b67887..60fd8e0 100644 --- a/src/Moof/Socket.hh +++ b/src/Moof/Socket.hh @@ -9,127 +9,412 @@ * **************************************************************************/ +/** + * \file Socket.hh + * Network-related classes, including a reinterpreted sockets API. + */ + #ifndef _MOOF_SOCKET_HH_ #define _MOOF_SOCKET_HH_ +#include #include +#include #include #include #include +#if HAVE_FCNTL_H +#include +#else +#error No alternative to fcntl implemented yet. +#endif + +#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 Mf { +/** + * A class to represent the address of a remote host, including the type of + * service and socket communication. + */ class SocketAddress { public: - SocketAddress(int type = SOCK_STREAM) + /** + * Construct an unspecified address. + */ + SocketAddress() : + mSize(0), + mType(0) { - mType = type; - memset(&mAddress.in4, 0, sizeof(mAddress.in4)); - mAddress.in4.sin_family = AF_INET; - mAddress.in4.sin_port = 0; - mAddress.in4.sin_addr.s_addr = INADDR_ANY; - mName = "[any]"; - mSize = sizeof(mAddress.in4); + mAddr.sa.sa_family = AF_UNSPEC; + mAddr.in.sin_port = 0; } - SocketAddress(const std::string& name, const std::string& service, - int type = SOCK_STREAM, int family = AF_INET) + /** + * 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) { - ASSERT(type == SOCK_STREAM || type == SOCK_DGRAM); - ASSERT(family == AF_INET || family == AF_INET6 || family == AF_UNSPEC); + init(service, name, type, family); + } - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = family; - hints.ai_socktype = type; - hints.ai_flags = AI_PASSIVE | AI_CANONNAME; + /** + * 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 SocketAddress(const std::string& service, + int type = SOCK_STREAM, + int family = AF_UNSPEC) + { + init(service, type, family); + } - struct addrinfo* addr; - int status = getaddrinfo(name.length() > 0 ? name.c_str() : 0, - service.length() > 0 ? service.c_str() : 0, &hints, &addr); - if (status != 0) - { - Mf::logError("uh oh, that didn't work!"); - return; - } + /** + * 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(mName, mService); + } + + /** + * 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(mName, mService); + } - mType = addr->ai_socktype; - memcpy(&mAddress.sa, addr->ai_addr, addr->ai_addrlen); - mName = addr->ai_canonname; - mSize = addr->ai_addrlen; - freeaddrinfo(addr); + /** + * 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); } - SocketAddress(const std::string& name, const struct addrinfo* addr) + + /** + * 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) { - mType = addr->ai_socktype; - memcpy(&mAddress.sa, addr->ai_addr, addr->ai_addrlen); - mName = name; - mSize = addr->ai_addrlen; + 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; + } } - SocketAddress(const struct sockaddr* addr, size_t size, - int type = SOCK_STREAM) + /** + * 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) { - mType = type; - memcpy(&mAddress.sa, addr, size); - mSize = size; + 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); - char name[128] = {'\0'}; - inet_ntop(addr->sa_family, (struct sockaddr_in*)addr, - name, sizeof(name)); - mName = name; + 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(mAddress.in4.sin_port); + 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 mAddress.sa.sa_family; + return mAddr.sa.sa_family; } + /** + * Get the sockaddr structure of the address. + * \return The sockaddr structure. + */ const struct sockaddr* address() const { - return &mAddress.sa; + 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; } - static int resolve(const std::string& name, const std::string& service, - int type, int family, std::vector& resolved) + /** + * 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); @@ -138,90 +423,528 @@ public: memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = type; - hints.ai_flags = AI_PASSIVE | AI_CANONNAME; + hints.ai_flags = flags; - struct addrinfo* list; - int status = getaddrinfo(name.length() > 0 ? name.c_str() : 0, - service.length() > 0 ? service.c_str() : 0, &hints, &list); - if (status != 0) return -1; - - resolved.clear(); + struct addrinfo* addr; + int status = getaddrinfo(node, service, &hints, &addr); - const char* canonicalName = list->ai_canonname; - struct addrinfo* addr = list; - while (addr != 0) + if (status == 0) { - resolved.push_back(SocketAddress(canonicalName, addr)); - addr = addr->ai_next; + return addr; + } + else + { + Mf::logWarning(gai_strerror(status)); + return 0; } - - freeaddrinfo(list); - 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; + } -private: union { - sockaddr sa; - sockaddr_in in4; - sockaddr_in6 in6; - } mAddress; + sockaddr sa; + sockaddr_in in; + sockaddr_storage storage; + } mAddr; size_t mSize; - std::string mName; int mType; + + std::string mName; + std::string mService; }; +/** + * The socket class represents a connection or between this node and a + * remote node. + */ class Socket { -public: - - Socket(const SocketAddress& address) : - mAddress(address) + struct Impl { - mFd = socket(address.family(), address.type(), 0); - } + 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. + */ + explicit 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(mFd); + close(); } + /** + * Get whether or not the socket is connected. + * \return True if the socket is connected, false otherwise. + */ bool isConnected() const { - return false; + return mImpl.isConnected; } + /** + * Get the address associated with the socket. + */ const SocketAddress& address() const { - return mAddress; + 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); } - void write(const Packet& packet) + /** + * Bind the socket to interface and port number specified in the + * address. + * \return 0 on success, -1 on failure. + */ + int bind() { - write(mAddress, packet); + return ::bind(mImpl.fd, + mImpl.address.address(), + mImpl.address.size()); } - void write(const SocketAddress& address, const Packet& packet) + /** + * 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) { - sendto(mFd, packet.bytes(), packet.size(), 0, - address.address(), address.size()); + return ::listen(mImpl.fd, backlog > 0 ? backlog : SOMAXCONN); } - Packet read() + /** + * 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) { - char buffer[1024]; - int size = recv(mFd, buffer, sizeof(buffer), 0); + Socket temp = Socket(mImpl.fd); + if (temp.mImpl.fd != -1) + { + socket = temp; + return socket.mImpl.fd; + } + return -1; + } + - return Packet(buffer, size); + /** + * 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(mImpl.fd, + level, + option, + reinterpret_cast(&value), + sizeof(value)); +#else + return setsockopt(mImpl.fd, level, option, &value, sizeof(value)); +#endif } - Packet read(SocketAddress& address) + /** + * 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); + +#if defined(_WIN32) + int result = getsockopt(mImpl.fd, + level, + option, + reinterpret_cast(&str), + &size); +#else + int result = getsockopt(mImpl.fd, level, option, &str, &size); +#endif + value.assign(str, size); + return result; + } + + + /** + * Set the socket IO mode to either blocking or non-blocking. + * \param isBlocking True if the socket blocks, false otherwise. + */ + void setBlocking(bool isBlocking) + { +#ifdef HAVE_FCNTL + int flags = fcntl(mImpl.fd, F_GETFL); + flags = isBlocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); + fcntl(mImpl.fd, F_SETFL, flags); +#elif defined(_WIN32) + u_long value = isBlocking; + ioctlsocket(mImpl.fd, FIONBIO, &value); +#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 isBlocking() const + { +#ifdef HAVE_FCNTL + int flags = fcntl(mImpl.fd, F_GETFL); + return !(flags & O_NONBLOCK); +#endif + return true; + } + + + /** + * 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(mImpl.fd, + reinterpret_cast(bytes), size, + flags); +#else + return send(mImpl.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 SocketAddress& address, + int flags = 0) + { +#if defined(_WIN32) + return sendto(mImpl.fd, + reinterpret_cast(bytes), size, + flags, + address.address(), address.size()); +#else + return sendto(mImpl.fd, bytes, size, flags, + address.address(), 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 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) + { +#if defined(_WIN32) + ssize_t result = recv(mImpl.fd, + reinterpret_cast(bytes), size, + flags); +#else + ssize_t result = recv(mImpl.fd, bytes, size, flags); +#endif + if (result == 0) mImpl.isConnected = 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, + SocketAddress& address, + int flags = 0) + { + union + { + sockaddr sa; + sockaddr_storage storage; + } addr; + socklen_t length = sizeof(addr); + +#if defined(_WIN32) + ssize_t result = recvfrom(mImpl.fd, + reinterpret_cast(bytes), size, + flags, + &addr.sa, &length); +#else + ssize_t result = recvfrom(mImpl.fd, bytes, size, flags, + &addr.sa, &length); +#endif + if (result != -1) + { + address = SocketAddress(&addr.sa, length, mImpl.address.type()); + } + else if (result == 0) + { + mImpl.isConnected = 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 = 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; @@ -229,46 +952,140 @@ public: } addr; socklen_t length = sizeof(addr); - char buffer[1024]; - int size = recvfrom(mFd, buffer, sizeof(buffer), 0, - &addr.sa, &length); + 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; + + explicit 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; + } + } + } + - address = SocketAddress(&addr.sa, length, SOCK_DGRAM); - return Packet(buffer, size); + int background() + { + return 0; } private: - int mFd; - SocketAddress mAddress; + Socket mSocket; + std::vector mProtocols; + Mutex mMutex; }; +/** + * An asynchronous task to resolve addresses. + */ class ResolverTask : public ThreadedTask { public: - ResolverTask(const std::string& name, const std::string& service, - int type = SOCK_STREAM, int family = AF_UNSPEC) : + /** + * 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, name, service, type, family); + 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 (!mThread) mThread = Mf::detachFunction(mFunction); + 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; @@ -277,11 +1094,14 @@ public: private: - int resolve(const std::string& name, const std::string& service, - int type, int family) + int resolve(const std::string& service, + const std::string& name, + int type, + int family) { - int status = SocketAddress::resolve(name, service, - type, family, mAddressList); + int status = SocketAddress::resolve(service, name, + type, family, + mAddressList); mIsDone = true; return status; } @@ -289,10 +1109,23 @@ private: std::vector mAddressList; bool mIsDone; - Function mFunction; + 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_