mirror of
https://github.com/mangosfour/server.git
synced 2025-12-17 07:37:03 +00:00
+ Got rid of zip lib requirement in G3D...
Still can re-enable code by defining _HAVE_ZIP...
+ Remove silly X11 lib dependency from G3D
Code doesn't seem to do anything yet anyway, and even if, we don't want it :p
+ Fix another weird G3D build problem...
+ Remove some __asm usage in g3d, which is not available on Win64
My editor also decided to remove a ton of trailing white spaces...tss...
+ Reapply G3D fixes for 64bit VC
+ not use SSE specific header when SSE not enabled in *nix
+ Updated project files
+ New vmap_assembler VC90/VC80 Project
+ vmap assembler binaries updates
NOTE: Old vmap fikes expected work (as tests show) with new library version.
But better use new generated versions. Its different in small parts to bad or good...
(based on Lynx3d's repo commit 44798d3)
Signed-off-by: VladimirMangos <vladimir@getmangos.com>
1362 lines
36 KiB
C++
1362 lines
36 KiB
C++
/**
|
|
@file NetworkDevice.cpp
|
|
|
|
@maintainer Morgan McGuire, morgan@cs.brown.edu
|
|
@created 2002-11-22
|
|
@edited 2006-02-24
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#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 <cstring>
|
|
|
|
#if defined(G3D_LINUX) || defined(G3D_OSX) || defined(G3D_FREEBSD)
|
|
# include <sys/types.h>
|
|
# include <sys/socket.h>
|
|
# include <ifaddrs.h>
|
|
# include <netinet/in.h>
|
|
# include <net/if.h>
|
|
# ifdef __linux__
|
|
# include <sys/ioctl.h>
|
|
# include <netinet/in.h>
|
|
# include <unistd.h>
|
|
# include <string.h>
|
|
// Match Linux to FreeBSD
|
|
# define AF_LINK AF_PACKET
|
|
# else
|
|
# include <net/if_dl.h>
|
|
# include <sys/sockio.h>
|
|
# endif
|
|
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <netinet/tcp.h>
|
|
#include <sys/ioctl.h>
|
|
#include <netinet/if_ether.h>
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#define _alloca alloca
|
|
|
|
/** Define an error code for non-windows platforms. */
|
|
int WSAGetLastError() {
|
|
return -1;
|
|
}
|
|
|
|
#define SOCKET_ERROR -1
|
|
|
|
static std::string socketErrorCode(int code) {
|
|
return G3D::format("CODE %d: %s\n", code, strerror(code));
|
|
}
|
|
|
|
static std::string socketErrorCode() {
|
|
return socketErrorCode(errno);
|
|
}
|
|
|
|
static const int WSAEWOULDBLOCK = -100;
|
|
|
|
typedef int SOCKET;
|
|
typedef struct sockaddr_in SOCKADDR_IN;
|
|
|
|
#else
|
|
|
|
// Windows
|
|
static std::string socketErrorCode(int code) {
|
|
LPTSTR formatMsg = NULL;
|
|
|
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS |
|
|
FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL,
|
|
code,
|
|
0,
|
|
(LPTSTR)&formatMsg,
|
|
0,
|
|
NULL);
|
|
|
|
return G3D::format("CODE %d: %s\n", code, formatMsg);
|
|
}
|
|
|
|
static std::string socketErrorCode() {
|
|
return socketErrorCode(GetLastError());
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#ifndef _SOCKLEN_T
|
|
# if defined(G3D_WIN32) || defined(G3D_OSX)
|
|
typedef int socklen_t;
|
|
# endif
|
|
#endif
|
|
|
|
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;
|
|
int ret;
|
|
|
|
ret = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&val, (socklen_t*)&sz);
|
|
logPrintf("SOL_SOCKET/SO_RCVBUF = %d\n", val);
|
|
|
|
ret = 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
|
|
ret = getsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&val, (socklen_t*)&sz);
|
|
logPrintf("SOL_SOCKET/SO_RCVTIMEO = %d\n", val);
|
|
|
|
ret = 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(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(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_WIN32
|
|
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_WIN32
|
|
|
|
bool NetworkDevice::init() {
|
|
debugAssert(! initialized);
|
|
|
|
logPrintf("Network Startup");
|
|
logPrintf("Starting WinSock networking.\n");
|
|
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.hasMore(); ++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);
|
|
|
|
logPrintf("Network Cleanup");
|
|
# ifdef G3D_WIN32
|
|
WSACleanup();
|
|
# endif
|
|
logPrintf("Network cleaned up.");
|
|
}
|
|
|
|
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_WIN32
|
|
::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[128];
|
|
|
|
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 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(), 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 - receiveBufferUsedSize;
|
|
int count = 0;
|
|
while ((ret != SOCKET_ERROR) && (left > 0) && (count < 100)) {
|
|
|
|
ret = recv(sock, ((char*)receiveBuffer) + 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 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(), 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 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 NULL;
|
|
}
|
|
|
|
Log::common()->printf("%s connected, transferred to socket %d.\n",
|
|
inet_ntoa(remote_addr.sin_addr), sClient);
|
|
|
|
#ifndef G3D_WIN32
|
|
return new ReliableConduit(sClient,
|
|
NetAddress(htonl(remote_addr.sin_addr.s_addr),
|
|
ntohs(remote_addr.sin_port)));
|
|
#else
|
|
return 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) {
|
|
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
|