*
**************************************************************************/
+/**
+ * \file Socket.hh
+ * Network-related classes, including a reinterpreted sockets API.
+ */
+
#ifndef _MOOF_SOCKET_HH_
#define _MOOF_SOCKET_HH_
#include <algorithm>
#include <cstring>
+#include <iostream>
#include <sstream>
#include <string>
#include <vector>
+#if HAVE_FCNTL_H
+#include <fcntl.h>
+#else
+#error No alternative to fcntl implemented yet.
+#endif
+
#if defined(_WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#include <wspiapi.h>
+#define SHUT_RD SD_RECEIVE
+#define SHUT_WR SD_SEND
+#define SHUT_RDWR SD_BOTH
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
-#if HAVE_FCNTL_H
-#include <fcntl.h>
-#else
-#include <sys/ioctl.h>
-#endif
#endif
#include <Moof/Log.hh>
#include <Moof/Thread.hh>
-#ifndef SO_NONBLOCK
-#define SO_NONBLOCK 1024
+#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:
+ /**
+ * Construct an unspecified address.
+ */
SocketAddress() :
mSize(0),
mType(0)
mAddr.in.sin_port = 0;
}
- SocketAddress(const std::string& service, const std::string& name,
- int type = SOCK_STREAM, int family = AF_UNSPEC)
+ /**
+ * 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);
}
- SocketAddress(const std::string& service,
- int type = SOCK_STREAM, int family = AF_UNSPEC)
+ /**
+ * 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);
}
- SocketAddress(const struct addrinfo* addr, const std::string& name)
+ /**
+ * 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)
{
- mType = addr->ai_socktype;
memcpy(&mAddr.sa, addr->ai_addr, addr->ai_addrlen);
- mName = name;
- mSize = addr->ai_addrlen;
+ getNameAndService(mName, mService);
}
- SocketAddress(const struct sockaddr* addr, size_t size,
- int type = SOCK_STREAM)
+ /**
+ * 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)
{
- mType = type;
memcpy(&mAddr.sa, addr, size);
- mSize = size;
- setNameFromAddress();
+ getNameAndService(mName, mService);
}
+ /**
+ * 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);
}
- void init(const std::string& service, const std::string& name = "",
- int type = SOCK_STREAM, int family = AF_UNSPEC)
+ /**
+ * 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)
{
- 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 = AI_PASSIVE;
-
- struct addrinfo* addr;
- int status = getaddrinfo(name.length() > 0 ? name.c_str() : 0,
- service.c_str(), &hints, &addr);
- if (status == 0)
+ 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);
- mSize = addr->ai_addrlen;
- if (name != "") mName = name;
- else setNameFromAddress();
+ mService = service;
+ mName = name;
freeaddrinfo(addr);
}
else
{
- Mf::logWarning(gai_strerror(status));
mType = 0;
mSize = 0;
mAddr.sa.sa_family = AF_UNSPEC;
}
}
+ /**
+ * 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)
+ int type = SOCK_STREAM,
+ int family = AF_UNSPEC)
{
- init(service, "", type, family);
+ 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;
+ }
}
- const std::string& name() const
+ /**
+ * 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 mName;
+ return mService;
}
- void setName(const std::string& name)
+ /**
+ * 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
{
- mName = name;
+ 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;
}
- static int resolve(const std::string& service, const std::string& name,
- int type, int family, std::vector<SocketAddress>& 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<SocketAddress>& 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<SocketAddress>& 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);
- resolved.clear();
-
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = type;
- hints.ai_flags = AI_PASSIVE;
+ hints.ai_flags = flags;
+
+ struct addrinfo* addr;
+ int status = getaddrinfo(node, service, &hints, &addr);
- 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)
{
- for (struct addrinfo* addr = list;
- addr != 0; addr = addr->ai_next)
- {
- resolved.push_back(SocketAddress(addr, name));
- }
-
- freeaddrinfo(list);
+ return addr;
}
else
{
Mf::logWarning(gai_strerror(status));
- return -1;
+ return 0;
}
-
- return 0;
}
+ static int collectAddresses(struct addrinfo* addresses,
+ std::vector<SocketAddress>& resolved)
+ {
+ if (addresses)
+ {
+ resolved.clear();
-private:
+ for (struct addrinfo* addr = addresses;
+ addr != 0;
+ addr = addr->ai_next)
+ {
+ resolved.push_back(SocketAddress(addr));
+ }
- void setNameFromAddress()
- {
-#if defined(_WIN32)
- // inet_ntop was introduced in Vista
- mName = inet_ntoa(mAddr.in.sin_addr);
-#else
- char name[INET6_ADDRSTRLEN] = {'\0'};
- inet_ntop(mAddr.sa.sa_family, &mAddr.sa, name, sizeof(name));
- mName = name;
-#endif
+ return 0;
+ }
+ else return -1;
}
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
{
struct Impl
{
+ SocketAddress address;
int fd;
bool isConnected;
- SocketAddress address;
- };
+ Impl() :
+ fd(-1),
+ isConnected(false) {}
-public:
-
- Socket() :
- mFd(-1),
- mIsConnected(false) {}
+ Impl(const SocketAddress& address, int flags = 0) :
+ address(address),
+ fd(::socket(address.family(), address.type(), flags)),
+ isConnected(false) {}
+ } mImpl;
- Socket(const SocketAddress& address) :
- mFd(-1),
- mIsConnected(false),
- mAddress(address) {}
- Socket(const std::string& service, const std::string& name,
- int type = SOCK_STREAM, int family = AF_UNSPEC) :
- mFd(-1),
- mIsConnected(false),
- mAddress(SocketAddress(service, name, type, family)) {}
+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,
- int type = SOCK_STREAM, int family = AF_UNSPEC) :
- mFd(-1),
- mIsConnected(false),
- mAddress(SocketAddress(service, type, family)) {}
-
-
- Socket(Socket& move) :
- mFd(move.mFd),
- mIsConnected(move.mIsConnected),
- mAddress(move.mAddress)
- {
- move.mFd = -1;
- move.mIsConnected = false;
- }
-
- Socket(Impl move) :
- mFd(move.fd),
- mIsConnected(move.isConnected),
- mAddress(move.address) {}
-
- Socket& operator=(Socket& move)
- {
-#if defined(_WIN32)
- if (mFd != -1) closesocket(mFd);
-#else
- if (mFd != -1) close(mFd);
-#endif
- mFd = move.mFd;
- mIsConnected = move.mIsConnected;
- mAddress = move.mAddress;
- move.mFd = -1;
- move.mIsConnected = false;
- return *this;
- }
-
- Socket& operator=(Impl move)
- {
-#if defined(_WIN32)
- if (mFd != -1) closesocket(mFd);
-#else
- if (mFd != -1) close(mFd);
-#endif
- mFd = move.fd;
- mIsConnected = move.isConnected;
- mAddress = move.address;
- return *this;
- }
-
- operator Impl()
- {
- Impl impl;
- impl.fd = mFd;
- impl.isConnected = mIsConnected;
- impl.address = mAddress;
- mFd = -1;
- mIsConnected = false;
- return impl;
- }
-
-
+ 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()
{
-#if defined(_WIN32)
- if (mFd != -1) closesocket(mFd);
-#else
- if (mFd != -1) close(mFd);
-#endif
+ close();
}
+ /**
+ * Get whether or not the socket is connected.
+ * \return True if the socket is connected, false otherwise.
+ */
bool isConnected() const
{
- return mIsConnected;
+ 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()
{
- if (mFd == -1) mFd = socket(mAddress.family(), mAddress.type(), 0);
- int result = ::connect(mFd, mAddress.address(), mAddress.size());
- mIsConnected = result != -1;
+ 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()
{
- if (mFd == -1) mFd = socket(mAddress.family(), mAddress.type(), 0);
- return ::bind(mFd, mAddress.address(), mAddress.size());
+ 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(mFd, backlog > 0 ? 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(mFd);
- if (temp.mFd != -1)
+ Socket temp = Socket(mImpl.fd);
+ if (temp.mImpl.fd != -1)
{
socket = temp;
- return socket.mFd;
+ return socket.mImpl.fd;
}
return -1;
}
- int set(int option, int value = 0)
+ /**
+ * 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 <class T>
+ int set(int option, const T& value, int level = SOL_SOCKET)
{
- if (mFd == -1) mFd = socket(mAddress.family(), mAddress.type(), 0);
- if (option == SO_NONBLOCK)
- {
-#ifdef HAVE_FCNTL
- int flags = fcntl(mFd, F_GETFL);
- return fcntl(mFd, F_SETFL, (value ? O_NONBLOCK : 0) | flags);
+#if defined(_WIN32)
+ return setsockopt(mImpl.fd,
+ level,
+ option,
+ reinterpret_cast<const char*>(&value),
+ sizeof(value));
#else
- return ioctl(mFd, FIONBIO, value);
+ return setsockopt(mImpl.fd, level, option, &value, sizeof(value));
#endif
- }
- return setsockopt(mFd, SOL_SOCKET, option, &value, sizeof(value));
}
- int set(int option, const std::string& 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)
{
- if (mFd == -1) mFd = socket(mAddress.family(), mAddress.type(), 0);
- return setsockopt(mFd, SOL_SOCKET, option,
- value.data(), value.length());
+ return setsockopt(mImpl.fd, level, option,
+ value.data(), value.length());
}
- int get(int option, int& value)
+ /**
+ * 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 <class T>
+ int get(int option, T& value, int level = SOL_SOCKET) const
{
- if (mFd == -1) mFd = socket(mAddress.family(), mAddress.type(), 0);
- if (option == SO_NONBLOCK)
- {
-#ifdef HAVE_FCNTL
- int flags = fcntl(mFd, F_GETFL);
- return flags & O_NONBLOCK;
+ 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<char*>(&str),
+ &size);
#else
- return ioctl(mFd, FIONBIO, &value);
+ int result = getsockopt(mImpl.fd, level, option, &str, &size);
#endif
- }
- socklen_t optlen = sizeof(value);
- return getsockopt(mFd, SOL_SOCKET, option, &value, &optlen);
+ value.assign(str, size);
+ return result;
}
- int get(int option, std::string& value)
+
+ /**
+ * Set the socket IO mode to either blocking or non-blocking.
+ * \param isBlocking True if the socket blocks, false otherwise.
+ */
+ void setBlocking(bool isBlocking)
{
- if (mFd == -1) mFd = socket(mAddress.family(), mAddress.type(), 0);
- char str[64] = {'\0'};
- socklen_t optlen = sizeof(str);
- int result = getsockopt(mFd, SOL_SOCKET, option, &str, &optlen);
- value = str;
- return result;
+#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;
+ }
- ssize_t write(const void* bytes, size_t size)
+
+ /**
+ * 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(mFd, bytes, size, 0);
+#if defined(_WIN32)
+ return send(mImpl.fd,
+ reinterpret_cast<const char *>(bytes), size,
+ flags);
+#else
+ return send(mImpl.fd, bytes, size, flags);
+#endif
}
- ssize_t write(const void* bytes, size_t size,
- const SocketAddress& address)
+
+ /**
+ * 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(mFd, bytes, size, 0,
- address.address(), address.size());
+#if defined(_WIN32)
+ return sendto(mImpl.fd,
+ reinterpret_cast<const char*>(bytes), size,
+ flags,
+ address.address(), address.size());
+#else
+ return sendto(mImpl.fd, bytes, size, flags,
+ address.address(), address.size());
+#endif
}
- ssize_t write(const Packet& packet)
+ /**
+ * 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());
+ return write(packet.bytes(), packet.size(), flags);
}
- ssize_t write(const Packet& packet, const SocketAddress& address)
+ /**
+ * 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);
+ return write(packet.bytes(), packet.size(), address, flags);
}
- ssize_t read(void* bytes, size_t size)
+ /**
+ * 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(mFd, bytes, size, 0);
+#if defined(_WIN32)
+ ssize_t result = recv(mImpl.fd,
+ reinterpret_cast<char*>(bytes), size,
+ flags);
+#else
+ ssize_t result = recv(mImpl.fd, bytes, size, flags);
+#endif
+ if (result == 0) mImpl.isConnected = false;
+ return result;
}
- ssize_t read(void* bytes, size_t size, SocketAddress& address)
+ /**
+ * 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
{
} addr;
socklen_t length = sizeof(addr);
- ssize_t result = recvfrom(mFd, bytes, size, 0, &addr.sa, &length);
+#if defined(_WIN32)
+ ssize_t result = recvfrom(mImpl.fd,
+ reinterpret_cast<char*>(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, mAddress.type());
+ address = SocketAddress(&addr.sa, length, mImpl.address.type());
+ }
+ else if (result == 0)
+ {
+ mImpl.isConnected = false;
}
return result;
}
- ssize_t read(Packet& packet)
+ /**
+ * 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));
+ ssize_t result = read(buffer, sizeof(buffer), flags);
if (result != -1) packet = Packet(buffer, result);
return result;
}
- ssize_t read(Packet& packet, SocketAddress& address)
+ /**
+ * 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);
+ 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)
} addr;
socklen_t length = sizeof(addr);
- mFd = ::accept(fd, &addr.sa, &length);
- if (mFd != -1)
+ 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<int(SocketMultiplexer&,
+ Packet&,
+ const SocketAddress&)> Function;
+
+ explicit SocketMultiplexer(Socket sock) :
+ mSocket(sock) {}
+
+
+ void setSocket(Socket sock)
+ {
+ Mutex::ScopedLock lock(mMutex);
+ mSocket = sock;
+ }
+
+ Socket& socket()
+ {
+ return mSocket;
+ }
+
+
+ std::vector<Function>& protocols()
+ {
+ return mProtocols;
+ }
+
+
+ void update(Scalar t, Scalar dt)
+ {
+ SocketAddress address;
+ Packet packet;
+ ssize_t bytes = mSocket.read(packet, address);
+
+ if (bytes > 0)
{
- mIsConnected = true;
- mAddress = SocketAddress(&addr.sa, length);
+ std::vector<Function>::iterator it;
+ for (it = mProtocols.begin(); it < mProtocols.end(); ++it)
+ {
+ packet.reset();
+ if ((*it)(*this, packet, address)) break;
+ }
}
}
- int mFd;
- bool mIsConnected;
- SocketAddress mAddress;
+ int background()
+ {
+ return 0;
+ }
+
+
+private:
+
+ Socket mSocket;
+ std::vector<Function> mProtocols;
+ Mutex mMutex;
};
+/**
+ * An asynchronous task to resolve addresses.
+ */
class ResolverTask : public ThreadedTask
{
public:
- ResolverTask(const std::string& service, const std::string& name,
- 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, service, name, 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<SocketAddress>& addresses() const
{
return mAddressList;
private:
- int resolve(const std::string& service, const std::string& name,
- int type, int family)
+ int resolve(const std::string& service,
+ const std::string& name,
+ int type,
+ int family)
{
int status = SocketAddress::resolve(service, name,
- type, family, mAddressList);
+ type, family,
+ mAddressList);
mIsDone = true;
return status;
}
std::vector<SocketAddress> 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_