1282 lines
35 KiB
C++
1282 lines
35 KiB
C++
/**
|
|
@file NetworkDevice.cpp
|
|
|
|
@maintainer Morgan McGuire, morgan@cs.brown.edu
|
|
@created 2002-11-22
|
|
@edited 2006-02-24
|
|
*/
|
|
|
|
#include "G3D/platform.h"
|
|
#include "G3D/TextOutput.h"
|
|
#include "G3D/NetworkDevice.h"
|
|
#include "G3D/NetAddress.h"
|
|
#include "G3D/BinaryInput.h"
|
|
#include "G3D/BinaryOutput.h"
|
|
#include "G3D/Log.h"
|
|
#include "G3D/G3DGameUnits.h"
|
|
#include "G3D/stringutils.h"
|
|
#include "G3D/debug.h"
|
|
#include "G3D/networkHelpers.h"
|
|
|
|
|
|
namespace G3D {
|
|
|
|
NetworkDevice* NetworkDevice::s_instance = NULL;
|
|
|
|
std::ostream& operator<<(std::ostream& os, const NetAddress& a) {
|
|
return os << a.toString();
|
|
}
|
|
|
|
|
|
static void logSocketInfo(const SOCKET& sock) {
|
|
uint32 val;
|
|
socklen_t sz = 4;
|
|
|
|
getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&val, (socklen_t*)&sz);
|
|
logPrintf("SOL_SOCKET/SO_RCVBUF = %d\n", val);
|
|
|
|
getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&val, (socklen_t*)&sz);
|
|
logPrintf("SOL_SOCKET/SO_SNDBUF = %d\n", val);
|
|
|
|
// Note: timeout = 0 means no timeout
|
|
getsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&val, (socklen_t*)&sz);
|
|
logPrintf("SOL_SOCKET/SO_RCVTIMEO = %d\n", val);
|
|
|
|
getsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&val, (socklen_t*)&sz);
|
|
logPrintf("SOL_SOCKET/SO_SNDTIMEO = %d\n", val);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
/** Invokes select on one socket. Returns SOCKET_ERROR on error, 0 if
|
|
there is no read pending, sock if there a read pending. */
|
|
static int selectOneReadSocket(const SOCKET& sock) {
|
|
// 0 time timeout is specified to poll and return immediately
|
|
struct timeval timeout;
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
|
|
// Create a set that contains just this one socket
|
|
fd_set socketSet;
|
|
FD_ZERO(&socketSet);
|
|
FD_SET(sock, &socketSet);
|
|
|
|
int ret = select((int)sock + 1, &socketSet, NULL, NULL, &timeout);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/** Returns true if the socket has a read pending */
|
|
static bool readWaiting(const SOCKET& sock) {
|
|
int ret = selectOneReadSocket(sock);
|
|
|
|
switch (ret) {
|
|
case SOCKET_ERROR:
|
|
logPrintf("ERROR: selectOneReadSocket returned "
|
|
"SOCKET_ERROR in readWaiting(). %s", socketErrorCode().c_str());
|
|
// Return true so that we'll force an error on read and close
|
|
// the socket.
|
|
return true;
|
|
|
|
case 0:
|
|
return false;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
/** Invokes select on one socket. */
|
|
static int selectOneWriteSocket(const SOCKET& sock) {
|
|
// 0 time timeout is specified to poll and return immediately
|
|
struct timeval timeout;
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
|
|
// Create a set that contains just this one socket
|
|
fd_set socketSet;
|
|
FD_ZERO(&socketSet);
|
|
FD_SET(sock, &socketSet);
|
|
|
|
return select((int)sock + 1, NULL, &socketSet, NULL, &timeout);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
NetworkDevice* NetworkDevice::instance() {
|
|
if (s_instance == NULL) {
|
|
s_instance = new NetworkDevice();
|
|
if (! s_instance->init()) {
|
|
delete s_instance;
|
|
s_instance = NULL;
|
|
}
|
|
}
|
|
return s_instance;
|
|
}
|
|
|
|
|
|
void NetworkDevice::cleanup() {
|
|
if (s_instance) {
|
|
s_instance->_cleanup();
|
|
delete s_instance;
|
|
s_instance = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
NetworkDevice::NetworkDevice() {
|
|
initialized = false;
|
|
}
|
|
|
|
|
|
NetworkDevice::~NetworkDevice() {
|
|
}
|
|
|
|
|
|
std::string NetworkDevice::localHostName() const {
|
|
char ac[128];
|
|
if (gethostname(ac, sizeof(ac)) == -1) {
|
|
Log::common()->printf("Error while getting local host name\n");
|
|
return "localhost";
|
|
}
|
|
return gethostbyname(ac)->h_name;
|
|
}
|
|
|
|
#ifndef G3D_WINDOWS
|
|
const char* errnoToString() {
|
|
switch (errno) {
|
|
case EBADF:
|
|
return "file descriptor is invalid.";
|
|
|
|
case EINVAL:
|
|
return "Request or argp is not valid.";
|
|
|
|
case ENOTTY:
|
|
return
|
|
"file descriptor is not associated with a character special device OR "
|
|
"The specified request does not apply to the "
|
|
"kind of object that the descriptor fildes references.";
|
|
|
|
case EADDRNOTAVAIL:
|
|
return "Address not available.";
|
|
|
|
default:
|
|
{
|
|
static char buffer[20];
|
|
sprintf(buffer, "Error %d", errno);
|
|
return buffer;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
NetworkDevice::EthernetAdapter::EthernetAdapter() {
|
|
name = "";
|
|
ip = 0;
|
|
hostname = "";
|
|
subnet = 0;
|
|
broadcast = 0;
|
|
for (int i = 0; i < 6; ++i) {
|
|
mac[i] = 0;
|
|
}
|
|
}
|
|
|
|
void NetworkDevice::EthernetAdapter::describe(TextOutput& t) const {
|
|
t.writeSymbol("{");
|
|
t.pushIndent();
|
|
t.writeNewline();
|
|
|
|
t.writeSymbols("hostname", "=");
|
|
t.writeString(hostname + ";");
|
|
t.writeNewline();
|
|
|
|
t.writeSymbols("name", "=");
|
|
t.writeString(name + ";");
|
|
t.writeNewline();
|
|
|
|
t.writeSymbols("ip", "=");
|
|
t.writeSymbol("\"" + formatIP(ip) + "\";");
|
|
t.writeNewline();
|
|
|
|
t.writeSymbols("subnet", "=");
|
|
t.writeSymbol("\"" + formatIP(subnet) + "\";");
|
|
t.writeNewline();
|
|
|
|
t.writeSymbols("broadcast", "=");
|
|
t.writeSymbol("\"" + formatIP(broadcast) + "\";");
|
|
t.writeNewline();
|
|
|
|
t.writeSymbols("mac", "=");
|
|
t.writeSymbol("\"" + formatMAC(mac) + "\";");
|
|
t.writeNewline();
|
|
|
|
t.popIndent();
|
|
t.writeSymbol("};");
|
|
t.writeNewline();
|
|
}
|
|
|
|
|
|
void NetworkDevice::addAdapter(const EthernetAdapter& a) {
|
|
m_adapterArray.append(a);
|
|
if (a.broadcast != 0) {
|
|
int i = m_broadcastAddresses.findIndex(a.broadcast);
|
|
if (i == -1) {
|
|
m_broadcastAddresses.append(a.broadcast);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
std::string NetworkDevice::formatIP(uint32 addr) {
|
|
return format("%3d.%3d.%3d.%3d", (addr >> 24) & 0xFF, (addr >> 16) & 0xFF,
|
|
(addr >> 8) & 0xFF, addr & 0xFF);
|
|
}
|
|
|
|
|
|
std::string NetworkDevice::formatMAC(const uint8 MAC[6]) {
|
|
return format("%02x:%02x:%02x:%02x:%02x:%02x", MAC[0], MAC[1], MAC[2], MAC[3], MAC[4], MAC[5]);
|
|
}
|
|
|
|
|
|
#ifdef G3D_WINDOWS
|
|
|
|
bool NetworkDevice::init() {
|
|
debugAssert(! initialized);
|
|
|
|
logPrintf("Network Startup");
|
|
logPrintf("Starting WinSock networking.\n");
|
|
|
|
// G3D now initializes winsock through ENet
|
|
// WSADATA wsda;
|
|
// WSAStartup(MAKEWORD(G3D_WINSOCK_MAJOR_VERSION, G3D_WINSOCK_MINOR_VERSION), &wsda);
|
|
|
|
std::string hostname = "localhost";
|
|
{
|
|
char ac[128];
|
|
if (gethostname(ac, sizeof(ac)) == -1) {
|
|
logPrintf("Warning: Error while getting local host name\n");
|
|
} else {
|
|
hostname = gethostbyname(ac)->h_name;
|
|
}
|
|
}
|
|
|
|
EthernetAdapter a;
|
|
a.hostname = hostname;
|
|
a.name = "";
|
|
a.ip = NetAddress(hostname, 0).ip();
|
|
|
|
// TODO: Find subnet on Win32
|
|
a.subnet = 0x0000FFFF;
|
|
|
|
// TODO: Find broadcast on Win32
|
|
a.broadcast = 0xFFFFFFFF;
|
|
|
|
// TODO: find MAC on Win32
|
|
|
|
addAdapter(a);
|
|
|
|
std::string machine = localHostName();
|
|
std::string addr = NetAddress(machine, 0).ipString();
|
|
/*
|
|
logPrintf(
|
|
|
|
"Network:\n"
|
|
" Status: %s\n"
|
|
" Loaded winsock specification version %d (%d is "
|
|
"the highest available)\n"
|
|
" %d sockets available\n"
|
|
" Largest UDP datagram packet size is %d bytes\n\n",
|
|
wsda.szDescription,
|
|
wsda.szSystemStatus,
|
|
wsda.wVersion,
|
|
wsda.wHighVersion,
|
|
wsda.iMaxSockets,
|
|
wsda.iMaxUdpDg);
|
|
*/
|
|
|
|
// TODO: WSAIoctl for subnet and broadcast addresses
|
|
// http://msdn.microsoft.com/en-us/library/ms741621(VS.85).aspx
|
|
//
|
|
// TODO: SIO_GET_INTERFACE_LIST
|
|
|
|
initialized = true;
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if defined(G3D_LINUX) || defined(G3D_OSX) || defined(G3D_FREEBSD)
|
|
|
|
const sockaddr_in* castToIP4(const sockaddr* addr) {
|
|
if (addr == NULL) {
|
|
return NULL;
|
|
} else if (addr->sa_family == AF_INET) {
|
|
// An IPv4 address
|
|
return reinterpret_cast<const sockaddr_in*>(addr);
|
|
} else {
|
|
// Not an IPv4 address
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
uint32 getIP(const sockaddr_in* addr) {
|
|
if (addr != NULL) {
|
|
return ntohl(addr->sin_addr.s_addr);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
bool NetworkDevice::init() {
|
|
debugAssert(! initialized);
|
|
|
|
// Used for combining the MAC and ip information
|
|
typedef Table<std::string, EthernetAdapter> AdapterTable;
|
|
|
|
AdapterTable table;
|
|
|
|
// Head of a linked list of network interfaces on this machine
|
|
ifaddrs* ifap = NULL;
|
|
|
|
int r = getifaddrs(&ifap);
|
|
|
|
if (r != 0) {
|
|
logPrintf("ERROR: getifaddrs returned %d\n", r);
|
|
return false;
|
|
}
|
|
|
|
ifaddrs* current = ifap;
|
|
|
|
if (current == NULL) {
|
|
logPrintf("WARNING: No network interfaces found\n");
|
|
EthernetAdapter a;
|
|
a.name = "fallback";
|
|
a.hostname = "localhost";
|
|
a.ip = (127 << 24) | 1;
|
|
a.broadcast = 0xFFFFFFFF;
|
|
a.subnet = 0x000000FF;
|
|
addAdapter(a);
|
|
|
|
} else {
|
|
|
|
while (current != NULL) {
|
|
|
|
bool up = (current->ifa_flags & IFF_UP);
|
|
bool loopback = (current->ifa_flags & IFF_LOOPBACK);
|
|
|
|
if (! up || loopback) {
|
|
// Skip this adapter; it is offline or is a loopback
|
|
current = current->ifa_next;
|
|
continue;
|
|
}
|
|
|
|
if (! table.containsKey(current->ifa_name)) {
|
|
EthernetAdapter a;
|
|
a.name = current->ifa_name;
|
|
table.set(a.name, a);
|
|
}
|
|
|
|
// This adapter must exist because it was created above
|
|
EthernetAdapter& adapter = table[current->ifa_name];
|
|
|
|
const sockaddr_in* interfaceAddress = castToIP4(current->ifa_addr);
|
|
const sockaddr_in* broadcastAddress = castToIP4(current->ifa_dstaddr);
|
|
const sockaddr_in* subnetMask = castToIP4(current->ifa_netmask);
|
|
|
|
uint32 ip = getIP(interfaceAddress);
|
|
uint32 ba = getIP(broadcastAddress);
|
|
uint32 sn = getIP(subnetMask);
|
|
|
|
if (ip != 0) {
|
|
adapter.ip = ip;
|
|
}
|
|
|
|
if (ba != 0) {
|
|
adapter.broadcast = ba;
|
|
}
|
|
|
|
if (sn != 0) {
|
|
adapter.subnet = sn;
|
|
}
|
|
|
|
uint8_t* MAC = NULL;
|
|
// Extract MAC address
|
|
if ((current->ifa_addr != NULL) && (current->ifa_addr->sa_family == AF_LINK)) {
|
|
# ifdef __linux__
|
|
{
|
|
// Linux
|
|
struct ifreq ifr;
|
|
|
|
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
|
|
ifr.ifr_addr.sa_family = AF_INET;
|
|
strcpy(ifr.ifr_name, current->ifa_name);
|
|
ioctl(fd, SIOCGIFHWADDR, &ifr);
|
|
close(fd);
|
|
|
|
MAC = reinterpret_cast<uint8_t*>(ifr.ifr_hwaddr.sa_data);
|
|
}
|
|
# else
|
|
{
|
|
// The MAC address and the interfaceAddress come in as
|
|
// different interfaces with the same name.
|
|
|
|
// Posix/FreeBSD/Mac OS
|
|
sockaddr_dl* sdl = (struct sockaddr_dl *)current->ifa_addr;
|
|
MAC = reinterpret_cast<uint8_t*>(LLADDR(sdl));
|
|
}
|
|
# endif
|
|
|
|
// See if there was a MAC address
|
|
if (MAC != NULL) {
|
|
bool anyNonZero = false;
|
|
for (int i = 0; i < 6; ++i) {
|
|
anyNonZero = anyNonZero || (MAC[i] != 0);
|
|
}
|
|
if (anyNonZero) {
|
|
System::memcpy(adapter.mac, MAC, 6);
|
|
}
|
|
}
|
|
}
|
|
|
|
current = current->ifa_next;
|
|
}
|
|
|
|
freeifaddrs(ifap);
|
|
ifap = NULL;
|
|
}
|
|
|
|
// Extract all interesting adapters from the table
|
|
for (AdapterTable::Iterator it = table.begin(); it.isValid(); ++it) {
|
|
const EthernetAdapter& adapter = it->value;
|
|
|
|
// Only add adapters that have IP addresses
|
|
if (adapter.ip != 0) {
|
|
addAdapter(adapter);
|
|
} else {
|
|
logPrintf("NetworkDevice: Ignored adapter %s because ip = 0\n", adapter.name.c_str());
|
|
}
|
|
}
|
|
|
|
initialized = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
void NetworkDevice::_cleanup() {
|
|
debugAssert(initialized);
|
|
|
|
# ifdef G3D_WINDOWS
|
|
// Now handled through enet
|
|
// WSACleanup();
|
|
# endif
|
|
}
|
|
|
|
bool NetworkDevice::bind(SOCKET sock, const NetAddress& addr) const {
|
|
Log::common()->printf("Binding socket %d on port %d ",
|
|
sock, htons(addr.addr.sin_port));
|
|
if (::bind(sock, (struct sockaddr*)&(addr.addr), sizeof(addr.addr)) ==
|
|
SOCKET_ERROR) {
|
|
|
|
Log::common()->println("FAIL");
|
|
Log::common()->println(socketErrorCode());
|
|
closesocket(sock);
|
|
return false;
|
|
}
|
|
|
|
Log::common()->println("Ok");
|
|
return true;
|
|
}
|
|
|
|
|
|
void NetworkDevice::closesocket(SOCKET& sock) const {
|
|
if (sock != 0) {
|
|
#ifdef G3D_WINDOWS
|
|
::closesocket(sock);
|
|
#else
|
|
close(sock);
|
|
#endif
|
|
|
|
Log::common()->printf("Closed socket %d\n", sock);
|
|
sock = 0;
|
|
}
|
|
}
|
|
|
|
|
|
void NetworkDevice::localHostAddresses(Array<NetAddress>& array) const {
|
|
array.resize(0);
|
|
|
|
char ac[256];
|
|
|
|
if (gethostname(ac, sizeof(ac)) == SOCKET_ERROR) {
|
|
Log::common()->printf("Error while getting local host name\n");
|
|
return;
|
|
}
|
|
|
|
struct hostent* phe = gethostbyname(ac);
|
|
if (phe == 0) {
|
|
Log::common()->printf("Error while getting local host address\n");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; (phe->h_addr_list[i] != 0); ++i) {
|
|
struct in_addr addr;
|
|
memcpy(&addr, phe->h_addr_list[i], sizeof(struct in_addr));
|
|
array.append(NetAddress(addr));
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
Conduit::Conduit() : binaryOutput("<memory>", G3D_LITTLE_ENDIAN) {
|
|
sock = 0;
|
|
mSent = 0;
|
|
mReceived = 0;
|
|
bSent = 0;
|
|
bReceived = 0;
|
|
}
|
|
|
|
|
|
Conduit::~Conduit() {
|
|
NetworkDevice::instance()->closesocket(sock);
|
|
}
|
|
|
|
|
|
uint64 Conduit::bytesSent() const {
|
|
return bSent;
|
|
}
|
|
|
|
|
|
uint64 Conduit::bytesReceived() const {
|
|
return bReceived;
|
|
}
|
|
|
|
|
|
uint64 Conduit::messagesSent() const {
|
|
return mSent;
|
|
}
|
|
|
|
|
|
uint64 Conduit::messagesReceived() const {
|
|
return mReceived;
|
|
}
|
|
|
|
|
|
bool Conduit::ok() const {
|
|
return (sock != 0) && (sock != SOCKET_ERROR);
|
|
}
|
|
|
|
|
|
bool Conduit::messageWaiting() {
|
|
return readWaiting(sock);
|
|
}
|
|
|
|
|
|
/**
|
|
Increases the send and receive sizes of a socket to 2 MB from 8k
|
|
*/
|
|
static void increaseBufferSize(SOCKET sock) {
|
|
|
|
// Increase the buffer size; the default (8192) is too easy to
|
|
// overflow when the network latency is high.
|
|
{
|
|
uint32 val = 1024 * 1024 * 2;
|
|
if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
|
|
(char*)&val, sizeof(val)) == SOCKET_ERROR) {
|
|
Log::common()->printf("WARNING: Increasing socket "
|
|
"receive buffer to %d failed.\n", val);
|
|
Log::common()->println(socketErrorCode());
|
|
}
|
|
|
|
if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF,
|
|
(char*)&val, sizeof(val)) == SOCKET_ERROR) {
|
|
Log::common()->printf("WARNING: Increasing socket "
|
|
"send buffer to %d failed.\n", val);
|
|
Log::common()->println(socketErrorCode());
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
ReliableConduitRef ReliableConduit::create(const NetAddress& address) {
|
|
return ReliableConduitRef(new ReliableConduit(address));
|
|
}
|
|
|
|
|
|
ReliableConduit::ReliableConduit
|
|
(const NetAddress& _addr) :
|
|
state(NO_MESSAGE),
|
|
receiveBuffer(NULL),
|
|
receiveBufferTotalSize(0),
|
|
receiveBufferUsedSize(0) {
|
|
|
|
NetworkDevice* nd = NetworkDevice::instance();
|
|
|
|
messageType = 0;
|
|
|
|
addr = _addr;
|
|
Log::common()->print("Creating a TCP socket ");
|
|
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
|
|
|
if (sock == SOCKET_ERROR) {
|
|
Log::common()->println("FAIL");
|
|
Log::common()->println(socketErrorCode());
|
|
nd->closesocket(sock);
|
|
return;
|
|
}
|
|
|
|
Log::common()->println("Ok");
|
|
|
|
// Setup socket options (both constructors should set the same options)
|
|
|
|
// Disable Nagle's algorithm (we send lots of small packets)
|
|
const int T = true;
|
|
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
|
(const char*)&T, sizeof(T)) == SOCKET_ERROR) {
|
|
|
|
Log::common()->println("WARNING: Disabling Nagel's "
|
|
"algorithm failed.");
|
|
Log::common()->println(socketErrorCode());
|
|
} else {
|
|
Log::common()->println("Disabled Nagel's algorithm.");
|
|
}
|
|
|
|
// Set the NO LINGER option so the socket doesn't hang around if
|
|
// there is unsent data in the queue when it closes.
|
|
struct linger ling;
|
|
ling.l_onoff = 0;
|
|
ling.l_linger = 0;
|
|
if (setsockopt(sock, SOL_SOCKET, SO_LINGER,
|
|
(const char*)&ling, sizeof(ling)) == SOCKET_ERROR) {
|
|
|
|
Log::common()->println("WARNING: Setting socket no linger failed.");
|
|
Log::common()->println(socketErrorCode());
|
|
} else {
|
|
Log::common()->println("Set socket option no_linger.");
|
|
}
|
|
|
|
// Set reuse address so that a new server can start up soon after
|
|
// an old one has closed.
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
|
(const char*)&T, sizeof(T)) == SOCKET_ERROR) {
|
|
|
|
Log::common()->println("WARNING: Setting socket reuseaddr failed.");
|
|
Log::common()->println(socketErrorCode());
|
|
} else {
|
|
Log::common()->println("Set socket option reuseaddr.");
|
|
}
|
|
|
|
// Ideally, we'd like to specify IPTOS_LOWDELAY as well.
|
|
|
|
logSocketInfo(sock);
|
|
|
|
increaseBufferSize(sock);
|
|
|
|
Log::common()->printf("Created TCP socket %d\n", sock);
|
|
|
|
std::string x = addr.toString();
|
|
Log::common()->printf("Connecting to %s on TCP socket %d ", x.c_str(), sock);
|
|
|
|
int ret = connect(sock, (struct sockaddr *) &(addr.addr), sizeof(addr.addr));
|
|
|
|
if (ret == WSAEWOULDBLOCK) {
|
|
RealTime t = System::time() + 5.0;
|
|
// Non-blocking; we must wait until select returns non-zero
|
|
while ((selectOneWriteSocket(sock) == 0) && (System::time() < t)) {
|
|
System::sleep(0.02);
|
|
}
|
|
|
|
// TODO: check for failure on the select call
|
|
|
|
} else if (ret != 0) {
|
|
sock = (SOCKET)SOCKET_ERROR;
|
|
Log::common()->println("FAIL");
|
|
Log::common()->println(socketErrorCode());
|
|
return;
|
|
}
|
|
|
|
Log::common()->println("Ok");
|
|
}
|
|
|
|
|
|
ReliableConduit::ReliableConduit(
|
|
const SOCKET& _sock,
|
|
const NetAddress& _addr) :
|
|
state(NO_MESSAGE),
|
|
receiveBuffer(NULL),
|
|
receiveBufferTotalSize(0),
|
|
receiveBufferUsedSize(0) {
|
|
sock = _sock;
|
|
addr = _addr;
|
|
|
|
messageType = 0;
|
|
|
|
// Setup socket options (both constructors should set the same options)
|
|
|
|
// Disable Nagle's algorithm (we send lots of small packets)
|
|
const int T = true;
|
|
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
|
(const char*)&T, sizeof(T)) == SOCKET_ERROR) {
|
|
|
|
Log::common()->println("WARNING: Disabling Nagel's algorithm failed.");
|
|
Log::common()->println(socketErrorCode());
|
|
} else {
|
|
Log::common()->println("Disabled Nagel's algorithm.");
|
|
}
|
|
|
|
// Set the NO LINGER option so the socket doesn't hang around if
|
|
// there is unsent data in the queue when it closes.
|
|
struct linger ling;
|
|
ling.l_onoff = 0;
|
|
ling.l_linger = 0;
|
|
if (setsockopt(sock, SOL_SOCKET, SO_LINGER,
|
|
(const char*)&ling, sizeof(ling)) == SOCKET_ERROR) {
|
|
|
|
Log::common()->println("WARNING: Setting socket no linger failed.");
|
|
Log::common()->println(socketErrorCode());
|
|
} else {
|
|
Log::common()->println("Set socket option no_linger.");
|
|
}
|
|
|
|
// Set reuse address so that a new server can start up soon after
|
|
// an old one has closed.
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
|
(const char*)&T, sizeof(T)) == SOCKET_ERROR) {
|
|
|
|
Log::common()->println("WARNING: Setting socket reuseaddr failed.");
|
|
Log::common()->println(socketErrorCode());
|
|
} else {
|
|
Log::common()->println("Set socket option reuseaddr.");
|
|
}
|
|
|
|
// Ideally, we'd like to specify IPTOS_LOWDELAY as well.
|
|
|
|
logSocketInfo(sock);
|
|
}
|
|
|
|
|
|
ReliableConduit::~ReliableConduit() {
|
|
free(receiveBuffer);
|
|
receiveBuffer = NULL;
|
|
receiveBufferTotalSize = 0;
|
|
receiveBufferUsedSize = 0;
|
|
}
|
|
|
|
|
|
bool ReliableConduit::messageWaiting() {
|
|
switch (state) {
|
|
case HOLDING:
|
|
// We've already read the message and are waiting
|
|
// for a receive call.
|
|
return true;
|
|
|
|
case RECEIVING:
|
|
|
|
if (! ok()) {
|
|
return false;
|
|
}
|
|
// We're currently receiving the message. Read a little more.
|
|
receiveIntoBuffer();
|
|
|
|
if (messageSize == receiveBufferUsedSize) {
|
|
// We've read the whole mesage. Switch to holding state
|
|
// and return true.
|
|
state = HOLDING;
|
|
return true;
|
|
} else {
|
|
// There are more bytes left to read. We'll read them on
|
|
// the next call. Because the *entire* message is not ready,
|
|
// return false.
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case NO_MESSAGE:
|
|
if (Conduit::messageWaiting()) {
|
|
// Message incoming. Read the header.
|
|
|
|
state = RECEIVING;
|
|
receiveHeader();
|
|
|
|
// Loop back around now that we're in the receive state; we
|
|
// may be able to read the whole message before returning
|
|
// to the caller.
|
|
return messageWaiting();
|
|
} else {
|
|
// No message incoming.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
debugAssertM(false, "Should not reach this point");
|
|
return false;
|
|
}
|
|
|
|
|
|
uint32 ReliableConduit::waitingMessageType() {
|
|
// The messageWaiting call is what actually receives the message.
|
|
if (messageWaiting()) {
|
|
return messageType;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
void ReliableConduit::sendBuffer(const BinaryOutput& b) {
|
|
NetworkDevice* nd = NetworkDevice::instance();
|
|
int ret = ::send(sock, (const char*)b.getCArray(), (int)b.size(), 0);
|
|
|
|
if (ret == SOCKET_ERROR) {
|
|
Log::common()->println("Error occured while sending message.");
|
|
Log::common()->println(socketErrorCode());
|
|
nd->closesocket(sock);
|
|
return;
|
|
}
|
|
|
|
++mSent;
|
|
bSent += b.size();
|
|
|
|
// Verify the packet was actually sent
|
|
// Conversion to unsigned is safe because -1 is caught earlier
|
|
debugAssert(ret == b.size());
|
|
}
|
|
|
|
|
|
/** Null serializer. Used by reliable conduit::send(type) */
|
|
class Dummy {
|
|
public:
|
|
void serialize(BinaryOutput& b) const { (void)b; }
|
|
};
|
|
|
|
|
|
void ReliableConduit::send(uint32 type) {
|
|
static Dummy dummy;
|
|
send(type, dummy);
|
|
}
|
|
|
|
|
|
|
|
NetAddress ReliableConduit::address() const {
|
|
return addr;
|
|
}
|
|
|
|
|
|
void ReliableConduit::receiveHeader() {
|
|
NetworkDevice* nd = NetworkDevice::instance();
|
|
debugAssert(state == RECEIVING);
|
|
|
|
// Read the type
|
|
uint32 tmp;
|
|
int ret = recv(sock, (char*)&tmp, sizeof(tmp), 0);
|
|
|
|
// The type is the first four bytes. It is little endian.
|
|
if (System::machineEndian() == G3D_LITTLE_ENDIAN) {
|
|
messageType = tmp;
|
|
} else {
|
|
// Swap the byte order
|
|
for (int i = 0; i < 4; ++i) {
|
|
((char*)&messageType)[i] = ((char*)&tmp)[3 - i];
|
|
}
|
|
}
|
|
|
|
if ((ret == SOCKET_ERROR) || (ret != sizeof(messageType))) {
|
|
Log::common()->printf("Call to recv failed. ret = %d,"
|
|
" sizeof(messageType) = %d\n",
|
|
(int)ret, (int)sizeof(messageType));
|
|
Log::common()->println(socketErrorCode());
|
|
nd->closesocket(sock);
|
|
messageType = 0;
|
|
return;
|
|
}
|
|
|
|
// Read the size
|
|
ret = recv(sock, (char*)&messageSize, sizeof(messageSize), 0);
|
|
|
|
if ((ret == SOCKET_ERROR) || (ret != sizeof(messageSize))) {
|
|
Log::common()->printf("Call to recv failed. ret = %d,"
|
|
" sizeof(len) = %d\n", (int)ret,
|
|
(int)sizeof(messageSize));
|
|
Log::common()->println(socketErrorCode());
|
|
nd->closesocket(sock);
|
|
messageType = 0;
|
|
return;
|
|
}
|
|
|
|
messageSize = ntohl(messageSize);
|
|
debugAssert(messageSize < 6e7);
|
|
|
|
debugAssert(receiveBufferUsedSize == 0);
|
|
|
|
// Extend the size of the buffer.
|
|
if (messageSize > receiveBufferTotalSize) {
|
|
receiveBuffer = realloc(receiveBuffer, messageSize);
|
|
receiveBufferTotalSize = messageSize;
|
|
}
|
|
|
|
if (receiveBuffer == NULL) {
|
|
Log::common()->println("Could not allocate a memory buffer "
|
|
"during receivePacket.");
|
|
nd->closesocket(sock);
|
|
}
|
|
|
|
bReceived += 4;
|
|
}
|
|
|
|
|
|
void ReliableConduit::receiveIntoBuffer() {
|
|
NetworkDevice* nd = NetworkDevice::instance();
|
|
|
|
debugAssert(state == RECEIVING);
|
|
debugAssert(messageType != 0);
|
|
debugAssertM(receiveBufferUsedSize < messageSize, "Message already received.");
|
|
debugAssertM(messageSize >= receiveBufferUsedSize, "Message size overflow.");
|
|
|
|
// Read the data itself
|
|
int ret = 0;
|
|
uint32 left = messageSize - (uint32)receiveBufferUsedSize;
|
|
int count = 0;
|
|
while ((ret != SOCKET_ERROR) && (left > 0) && (count < 100)) {
|
|
|
|
ret = recv(sock, ((char*)receiveBuffer) + (uint32)receiveBufferUsedSize, left, 0);
|
|
|
|
if (ret > 0) {
|
|
left -= ret;
|
|
receiveBufferUsedSize += ret;
|
|
bReceived += ret;
|
|
|
|
if (left > 0) {
|
|
// There's still more. Give the machine a chance to read
|
|
// more data, but don't wait forever.
|
|
|
|
++count;
|
|
System::sleep(0.001);
|
|
}
|
|
} else {
|
|
// Something went wrong; our blocking read returned nothing.
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((ret == 0) || (ret == SOCKET_ERROR)) {
|
|
|
|
if (ret == SOCKET_ERROR) {
|
|
Log::common()->printf("Call to recv failed. ret = %d,"
|
|
" sizeof(messageSize) = %d\n", ret, messageSize);
|
|
Log::common()->println(socketErrorCode());
|
|
} else {
|
|
Log::common()->printf("recv returned 0\n");
|
|
}
|
|
nd->closesocket(sock);
|
|
return;
|
|
}
|
|
|
|
++mReceived;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
LightweightConduitRef LightweightConduit::create(
|
|
uint16 receivePort,
|
|
bool enableReceive,
|
|
bool enableBroadcast) {
|
|
|
|
return LightweightConduitRef(new LightweightConduit(receivePort, enableReceive, enableBroadcast));
|
|
}
|
|
|
|
|
|
LightweightConduit::LightweightConduit
|
|
(uint16 port,
|
|
bool enableReceive,
|
|
bool enableBroadcast) {
|
|
NetworkDevice* nd = NetworkDevice::instance();
|
|
|
|
Log::common()->print("Creating a UDP socket ");
|
|
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
|
|
if (sock == SOCKET_ERROR) {
|
|
sock = 0;
|
|
Log::common()->println("FAIL");
|
|
Log::common()->println(socketErrorCode());
|
|
return;
|
|
}
|
|
Log::common()->println("Ok");
|
|
|
|
if (enableReceive) {
|
|
debugAssert(port != 0);
|
|
if (! nd->bind(sock, NetAddress(0, port))) {
|
|
nd->closesocket(sock);
|
|
sock = (SOCKET)SOCKET_ERROR;
|
|
}
|
|
}
|
|
|
|
// Figuring out the MTU seems very complicated, so we just set it to 1000,
|
|
// which is likely to be safe. See IP_MTU for more information.
|
|
MTU = 1000;
|
|
|
|
increaseBufferSize(sock);
|
|
|
|
if (enableBroadcast) {
|
|
int TR = true;
|
|
if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST,
|
|
(const char*)&TR, sizeof(TR)) != 0) {
|
|
Log::common()->println("Call to setsockopt failed");
|
|
Log::common()->println(socketErrorCode());
|
|
nd->closesocket(sock);
|
|
sock = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
Log::common()->printf("Done creating UDP socket %d\n", sock);
|
|
|
|
alreadyReadMessage = false;
|
|
}
|
|
|
|
|
|
LightweightConduit::~LightweightConduit() {
|
|
}
|
|
|
|
|
|
bool LightweightConduit::receive(NetAddress& sender) {
|
|
// This both checks to ensure that a message was waiting and
|
|
// actively consumes the message from the network stream if
|
|
// it has not been read yet.
|
|
uint32 t = waitingMessageType();
|
|
if (t == 0) {
|
|
return false;
|
|
}
|
|
|
|
sender = messageSender;
|
|
alreadyReadMessage = false;
|
|
|
|
if (messageBuffer.size() < 4) {
|
|
// Something went wrong
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void LightweightConduit::sendBuffer(const NetAddress& a, BinaryOutput& b) {
|
|
NetworkDevice* nd = NetworkDevice::instance();
|
|
if (sendto(sock, (const char*)b.getCArray(), (int)b.size(), 0,
|
|
(struct sockaddr *) &(a.addr), sizeof(a.addr)) == SOCKET_ERROR) {
|
|
Log::common()->printf("Error occured while sending packet "
|
|
"to %s\n", inet_ntoa(a.addr.sin_addr));
|
|
Log::common()->println(socketErrorCode());
|
|
nd->closesocket(sock);
|
|
} else {
|
|
++mSent;
|
|
bSent += b.size();
|
|
}
|
|
}
|
|
|
|
|
|
bool LightweightConduit::messageWaiting() {
|
|
// We may have already pulled the message off the network stream
|
|
return alreadyReadMessage || Conduit::messageWaiting();
|
|
}
|
|
|
|
|
|
uint32 LightweightConduit::waitingMessageType() {
|
|
NetworkDevice* nd = NetworkDevice::instance();
|
|
if (! messageWaiting()) {
|
|
return 0;
|
|
}
|
|
|
|
if (! alreadyReadMessage) {
|
|
messageBuffer.resize(8192);
|
|
|
|
SOCKADDR_IN remote_addr;
|
|
int iRemoteAddrLen = sizeof(sockaddr);
|
|
|
|
int ret = recvfrom(sock, (char*)messageBuffer.getCArray(),
|
|
messageBuffer.size(), 0, (struct sockaddr *) &remote_addr,
|
|
(socklen_t*)&iRemoteAddrLen);
|
|
|
|
if (ret == SOCKET_ERROR) {
|
|
Log::common()->println("Error: recvfrom failed in "
|
|
"LightweightConduit::waitingMessageType().");
|
|
Log::common()->println(socketErrorCode());
|
|
nd->closesocket(sock);
|
|
messageBuffer.resize(0);
|
|
messageSender = NetAddress();
|
|
messageType = 0;
|
|
return 0;
|
|
}
|
|
|
|
messageSender = NetAddress(remote_addr);
|
|
|
|
++mReceived;
|
|
bReceived += ret;
|
|
|
|
messageBuffer.resize(ret, DONT_SHRINK_UNDERLYING_ARRAY);
|
|
|
|
// The type is the first four bytes. It is little endian.
|
|
if (System::machineEndian() == G3D_LITTLE_ENDIAN) {
|
|
messageType = *((uint32*)messageBuffer.getCArray());
|
|
} else {
|
|
// Swap the byte order
|
|
for (int i = 0; i < 4; ++i) {
|
|
((char*)&messageType)[i] = messageBuffer[3 - i];
|
|
}
|
|
}
|
|
|
|
alreadyReadMessage = true;
|
|
}
|
|
|
|
return messageType;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
NetListenerRef NetListener::create(const uint16 port) {
|
|
return NetListenerRef(new NetListener(port));
|
|
}
|
|
|
|
|
|
NetListener::NetListener(uint16 port) {
|
|
NetworkDevice* nd = NetworkDevice::instance();
|
|
|
|
// Start the listener socket
|
|
Log::common()->print("Creating a listener ");
|
|
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
|
|
|
if (sock == SOCKET_ERROR) {
|
|
Log::common()->printf("FAIL");
|
|
Log::common()->println(socketErrorCode());
|
|
return;
|
|
}
|
|
Log::common()->println("Ok");
|
|
|
|
const int T = true;
|
|
|
|
// Set reuse address so that a new server can start up soon after
|
|
// an old one has closed.
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
|
(const char*)&T, sizeof(T)) == SOCKET_ERROR) {
|
|
|
|
Log::common()->println("WARNING: Setting socket reuseaddr failed.");
|
|
Log::common()->println(socketErrorCode());
|
|
} else {
|
|
Log::common()->println("Set socket option reuseaddr.");
|
|
}
|
|
|
|
|
|
if (! nd->bind(sock, NetAddress(0, port))) {
|
|
Log::common()->printf("Unable to bind!\n");
|
|
nd->closesocket(sock);
|
|
sock = (SOCKET)SOCKET_ERROR;
|
|
return;
|
|
}
|
|
|
|
Log::common()->printf("Listening on port %5d ", port);
|
|
|
|
// listen is supposed to return 0 when there is no error.
|
|
// The 2nd argument is the number of connections to allow pending
|
|
// at any time.
|
|
int L = listen(sock, 100);
|
|
if (L == SOCKET_ERROR) {
|
|
Log::common()->println("FAIL");
|
|
Log::common()->println(socketErrorCode());
|
|
nd->closesocket(sock);
|
|
sock = (SOCKET)SOCKET_ERROR;
|
|
return;
|
|
}
|
|
Log::common()->println("Ok");
|
|
Log::common()->printf("Now listening on socket %d.\n\n", sock);
|
|
}
|
|
|
|
|
|
NetListener::~NetListener() {
|
|
NetworkDevice* nd = NetworkDevice::instance();
|
|
nd->closesocket(sock);
|
|
}
|
|
|
|
|
|
ReliableConduitRef NetListener::waitForConnection() {
|
|
NetworkDevice* nd = NetworkDevice::instance();
|
|
// The address of the connecting host
|
|
SOCKADDR_IN remote_addr;
|
|
int iAddrLen = sizeof(remote_addr);
|
|
|
|
Log::common()->println("Blocking in NetListener::waitForConnection().");
|
|
|
|
SOCKET sClient = accept(sock, (struct sockaddr*) &remote_addr,
|
|
(socklen_t*)&iAddrLen);
|
|
|
|
if (sClient == SOCKET_ERROR) {
|
|
Log::common()->println("Error in NetListener::acceptConnection.");
|
|
Log::common()->println(socketErrorCode());
|
|
nd->closesocket(sock);
|
|
return ReliableConduitRef();
|
|
}
|
|
|
|
Log::common()->printf("%s connected, transferred to socket %d.\n",
|
|
inet_ntoa(remote_addr.sin_addr), sClient);
|
|
|
|
#ifndef G3D_WINDOWS
|
|
return ReliableConduitRef(new ReliableConduit(sClient,
|
|
NetAddress(htonl(remote_addr.sin_addr.s_addr),
|
|
ntohs(remote_addr.sin_port))));
|
|
#else
|
|
return ReliableConduitRef(ReliableConduitRef(new ReliableConduit(sClient,
|
|
NetAddress(ntohl(remote_addr.sin_addr.S_un.S_addr),
|
|
ntohs(remote_addr.sin_port)))));
|
|
#endif
|
|
}
|
|
|
|
|
|
bool NetListener::ok() const {
|
|
return (sock != 0) && (sock != SOCKET_ERROR);
|
|
}
|
|
|
|
|
|
bool NetListener::clientWaiting() const {
|
|
return readWaiting(sock);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void NetworkDevice::describeSystem(
|
|
TextOutput& t) {
|
|
|
|
t.writeSymbols("Network", "=", "{");
|
|
t.writeNewline();
|
|
t.pushIndent();
|
|
|
|
for (int i = 0; i < m_adapterArray.size(); ++i) {
|
|
t.printf("Adapter%d =", i);
|
|
m_adapterArray[i].describe(t);
|
|
}
|
|
|
|
t.popIndent();
|
|
t.writeSymbols("};");
|
|
t.writeNewline();
|
|
t.writeNewline();
|
|
}
|
|
|
|
|
|
void NetworkDevice::describeSystem(
|
|
std::string& s) {
|
|
|
|
TextOutput t;
|
|
describeSystem(t);
|
|
t.commitString(s);
|
|
}
|
|
|
|
} // namespace
|