msdn article 737550 states that "the preferred method is to call the bind function to associate a socket with a local IP address and then join the multicast group. Although this order of operations is not mandatory, it is strongly recommended". Since binding to the multicast address seems to fail, let's try binding to INADDR_ANY and joint thereafter.
542 lines
12 KiB
C++
542 lines
12 KiB
C++
/*
|
|
simgear::Socket, adapted from PLIB Socket by James Turner
|
|
Copyright (C) 2010 James Turner
|
|
|
|
PLIB - A Suite of Portable Game Libraries
|
|
Copyright (C) 1998,2002 Steve Baker
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <simgear_config.h>
|
|
#endif
|
|
|
|
#include <simgear/compiler.h>
|
|
|
|
#include "sg_socket.hxx"
|
|
|
|
#if defined(_WIN32) && !defined(__CYGWIN__)
|
|
# define WINSOCK // use winsock convetions, otherwise standard POSIX
|
|
#endif
|
|
|
|
#include <cstdlib>
|
|
#include <ctime>
|
|
#include <cstring>
|
|
#include <cassert>
|
|
#include <cstdio> // for snprintf
|
|
|
|
#if defined(WINSOCK)
|
|
# include <winsock.h>
|
|
# include <stdarg.h>
|
|
#else
|
|
# include <sys/types.h>
|
|
# include <sys/socket.h>
|
|
# include <netinet/in.h>
|
|
# include <arpa/inet.h>
|
|
# include <sys/time.h>
|
|
# include <unistd.h>
|
|
# include <netdb.h>
|
|
# include <fcntl.h>
|
|
#endif
|
|
|
|
#if defined(_MSC_VER) && !defined(socklen_t)
|
|
#define socklen_t int
|
|
#endif
|
|
|
|
#include <simgear/debug/logstream.hxx>
|
|
#include <simgear/structure/exception.hxx>
|
|
|
|
namespace simgear
|
|
{
|
|
|
|
IPAddress::IPAddress ( const char* host, int port )
|
|
{
|
|
set ( host, port ) ;
|
|
}
|
|
|
|
|
|
void IPAddress::set ( const char* host, int port )
|
|
{
|
|
memset(this, 0, sizeof(IPAddress));
|
|
|
|
sin_family = AF_INET ;
|
|
sin_port = htons (port);
|
|
|
|
/* Convert a string specifying a host name or one of a few symbolic
|
|
** names to a numeric IP address. This usually calls gethostbyname()
|
|
** to do the work; the names "" and "<broadcast>" are special.
|
|
*/
|
|
|
|
if (!host || host[0] == '\0') {
|
|
sin_addr = INADDR_ANY;
|
|
return;
|
|
}
|
|
|
|
if (strcmp(host, "<broadcast>") == 0) {
|
|
sin_addr = INADDR_BROADCAST;
|
|
return;
|
|
}
|
|
|
|
sin_addr = inet_addr ( host ) ;
|
|
if (sin_addr != INADDR_NONE) {
|
|
return;
|
|
}
|
|
// finally, try gethostbyname
|
|
struct hostent *hp = gethostbyname ( host ) ;
|
|
if (!hp) {
|
|
SG_LOG(SG_IO, SG_WARN, "gethostbyname failed for " << host);
|
|
sin_addr = INADDR_ANY ;
|
|
return;
|
|
}
|
|
|
|
memcpy ( (char *) &sin_addr, hp->h_addr, hp->h_length ) ;
|
|
}
|
|
|
|
|
|
/* Create a string object representing an IP address.
|
|
This is always a string of the form 'dd.dd.dd.dd' (with variable
|
|
size numbers). */
|
|
|
|
const char* IPAddress::getHost () const
|
|
{
|
|
#if 0
|
|
const char* buf = inet_ntoa ( sin_addr ) ;
|
|
#else
|
|
static char buf [32];
|
|
long x = ntohl(sin_addr);
|
|
sprintf(buf, "%d.%d.%d.%d",
|
|
(int) (x>>24) & 0xff, (int) (x>>16) & 0xff,
|
|
(int) (x>> 8) & 0xff, (int) (x>> 0) & 0xff );
|
|
#endif
|
|
return buf;
|
|
}
|
|
|
|
unsigned int IPAddress::getIP () const
|
|
{
|
|
return sin_addr;
|
|
}
|
|
|
|
unsigned int IPAddress::getPort() const
|
|
{
|
|
return ntohs(sin_port);
|
|
}
|
|
|
|
unsigned int IPAddress::getFamily () const
|
|
{
|
|
return sin_family;
|
|
}
|
|
|
|
const char* IPAddress::getLocalHost ()
|
|
{
|
|
//gethostbyname(gethostname())
|
|
|
|
char buf[256];
|
|
memset(buf, 0, sizeof(buf));
|
|
gethostname(buf, sizeof(buf)-1);
|
|
const hostent *hp = gethostbyname(buf);
|
|
|
|
if (hp && *hp->h_addr_list)
|
|
{
|
|
in_addr addr = *((in_addr*)*hp->h_addr_list);
|
|
const char* host = inet_ntoa(addr);
|
|
|
|
if ( host )
|
|
return host ;
|
|
}
|
|
|
|
return "127.0.0.1" ;
|
|
}
|
|
|
|
|
|
bool IPAddress::getBroadcast () const
|
|
{
|
|
return sin_addr == INADDR_BROADCAST;
|
|
}
|
|
|
|
|
|
Socket::Socket ()
|
|
{
|
|
handle = -1 ;
|
|
}
|
|
|
|
|
|
Socket::~Socket ()
|
|
{
|
|
close () ;
|
|
}
|
|
|
|
|
|
void Socket::setHandle (int _handle)
|
|
{
|
|
close () ;
|
|
handle = _handle ;
|
|
}
|
|
|
|
|
|
bool Socket::open ( bool stream )
|
|
{
|
|
close () ;
|
|
handle = ::socket ( AF_INET, (stream? SOCK_STREAM: SOCK_DGRAM), 0 ) ;
|
|
|
|
// Jan 26, 2010: Patch to avoid the problem of the socket resource not
|
|
// yet being available if the program is restarted quickly after being
|
|
// killed.
|
|
//
|
|
// Reference: http://www.unixguide.net/network/socketfaq/4.5.shtml
|
|
// --
|
|
// Also required for joining multicast domains
|
|
if ( stream ) {
|
|
int opt_boolean = 1;
|
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
|
setsockopt( handle, SOL_SOCKET, SO_REUSEADDR, (char *)&opt_boolean,
|
|
sizeof(opt_boolean) );
|
|
#else
|
|
setsockopt( handle, SOL_SOCKET, SO_REUSEADDR, &opt_boolean,
|
|
sizeof(opt_boolean) );
|
|
#endif
|
|
}
|
|
|
|
return (handle != -1);
|
|
}
|
|
|
|
|
|
void Socket::setBlocking ( bool blocking )
|
|
{
|
|
assert ( handle != -1 ) ;
|
|
|
|
#if defined(WINSOCK)
|
|
u_long nblocking = blocking? 0: 1;
|
|
::ioctlsocket(handle, FIONBIO, &nblocking);
|
|
#else
|
|
|
|
int delay_flag = ::fcntl (handle, F_GETFL, 0);
|
|
|
|
if (blocking)
|
|
delay_flag &= (~O_NDELAY);
|
|
else
|
|
delay_flag |= O_NDELAY;
|
|
|
|
::fcntl (handle, F_SETFL, delay_flag);
|
|
#endif
|
|
}
|
|
|
|
|
|
void Socket::setBroadcast ( bool broadcast )
|
|
{
|
|
assert ( handle != -1 ) ;
|
|
int result;
|
|
if ( broadcast ) {
|
|
int one = 1;
|
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
|
result = ::setsockopt( handle, SOL_SOCKET, SO_BROADCAST, (char*)&one, sizeof(one) );
|
|
#else
|
|
result = ::setsockopt( handle, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one) );
|
|
#endif
|
|
} else {
|
|
result = ::setsockopt( handle, SOL_SOCKET, SO_BROADCAST, NULL, 0 );
|
|
}
|
|
|
|
if ( result < 0 ) {
|
|
throw sg_exception("Socket::setBroadcast failed");
|
|
}
|
|
}
|
|
|
|
|
|
int Socket::bind ( const char* host, int port )
|
|
{
|
|
int result;
|
|
assert ( handle != -1 ) ;
|
|
IPAddress addr ( host, port ) ;
|
|
|
|
#if !defined(WINSOCK)
|
|
if( (result = ::bind(handle,(const sockaddr*)&addr,sizeof(IPAddress))) < 0 ) {
|
|
SG_LOG(SG_IO, SG_ALERT, "bind(" << host << ":" << port << ") failed. Errno " << errno << " (" << strerror(errno) << ")");
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
// 224.0.0.0 - 239.255.255.255 are multicast
|
|
// Usage of 239.x.x.x is recommended for local scope
|
|
// Reference: http://tools.ietf.org/html/rfc5771
|
|
if( ntohl(addr.getIP()) >= 0xe0000000 && ntohl(addr.getIP()) <= 0xefffffff ) {
|
|
|
|
#if defined(WINSOCK)
|
|
struct sockaddr_in a;
|
|
a.sin_addr.S_un.S_addr = INADDR_ANY;
|
|
a.sin_family = AF_INET;
|
|
a.sin_port = htons(port);
|
|
|
|
if( (result = ::bind(handle,(const sockaddr*)&a,sizeof(a))) < 0 ) {
|
|
SG_LOG(SG_IO, SG_ALERT, "bind(any:" << port << ") failed. Errno " << errno << " (" << strerror(errno) << ")");
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
struct ip_mreq mreq;
|
|
mreq.imr_multiaddr.s_addr = addr.getIP();
|
|
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
|
if( (result=::setsockopt(getHandle(), IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&mreq, sizeof(mreq))) != 0 ) {
|
|
SG_LOG(SG_IO, SG_ALERT, "setsockopt(IP_ADD_MEMBERSHIP) failed. Errno " << errno << " (" << strerror(errno) << ")");
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int Socket::listen ( int backlog )
|
|
{
|
|
assert ( handle != -1 ) ;
|
|
return ::listen(handle,backlog);
|
|
}
|
|
|
|
|
|
int Socket::accept ( IPAddress* addr )
|
|
{
|
|
assert ( handle != -1 ) ;
|
|
|
|
if ( addr == NULL )
|
|
{
|
|
return ::accept(handle,NULL,NULL);
|
|
}
|
|
else
|
|
{
|
|
socklen_t addr_len = (socklen_t) sizeof(IPAddress) ;
|
|
return ::accept(handle,(sockaddr*)addr,&addr_len);
|
|
}
|
|
}
|
|
|
|
|
|
int Socket::connect ( const char* host, int port )
|
|
{
|
|
assert ( handle != -1 ) ;
|
|
IPAddress addr ( host, port ) ;
|
|
if ( addr.getBroadcast() ) {
|
|
setBroadcast( true );
|
|
}
|
|
return ::connect(handle,(const sockaddr*)&addr,sizeof(IPAddress));
|
|
}
|
|
|
|
|
|
int Socket::send (const void * buffer, int size, int flags)
|
|
{
|
|
assert ( handle != -1 ) ;
|
|
return ::send (handle, (const char*)buffer, size, flags);
|
|
}
|
|
|
|
|
|
int Socket::sendto ( const void * buffer, int size,
|
|
int flags, const IPAddress* to )
|
|
{
|
|
assert ( handle != -1 ) ;
|
|
return ::sendto(handle,(const char*)buffer,size,flags,
|
|
(const sockaddr*)to,sizeof(IPAddress));
|
|
}
|
|
|
|
|
|
int Socket::recv (void * buffer, int size, int flags)
|
|
{
|
|
assert ( handle != -1 ) ;
|
|
return ::recv (handle, (char*)buffer, size, flags);
|
|
}
|
|
|
|
|
|
int Socket::recvfrom ( void * buffer, int size,
|
|
int flags, IPAddress* from )
|
|
{
|
|
assert ( handle != -1 ) ;
|
|
socklen_t fromlen = (socklen_t) sizeof(IPAddress) ;
|
|
return ::recvfrom(handle,(char*)buffer,size,flags,(sockaddr*)from,&fromlen);
|
|
}
|
|
|
|
|
|
void Socket::close (void)
|
|
{
|
|
if ( handle != -1 )
|
|
{
|
|
#if defined(WINSOCK)
|
|
::closesocket( handle );
|
|
#else
|
|
::close( handle );
|
|
#endif
|
|
handle = -1 ;
|
|
}
|
|
}
|
|
|
|
|
|
bool Socket::isNonBlockingError ()
|
|
{
|
|
#if defined(WINSOCK)
|
|
int wsa_errno = WSAGetLastError();
|
|
if ( wsa_errno != 0 )
|
|
{
|
|
WSASetLastError(0);
|
|
SG_LOG(SG_IO, SG_WARN, "isNonBlockingError: WSAGetLastError():" << wsa_errno);
|
|
switch (wsa_errno) {
|
|
case WSAEWOULDBLOCK: // always == NET_EAGAIN?
|
|
case WSAEALREADY:
|
|
case WSAEINPROGRESS:
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
#else
|
|
switch (errno) {
|
|
case EWOULDBLOCK: // always == NET_EAGAIN?
|
|
case EALREADY:
|
|
case EINPROGRESS:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// modified version by os
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
int Socket::select ( Socket** reads, Socket** writes, int timeout )
|
|
{
|
|
fd_set r,w;
|
|
int retval;
|
|
|
|
FD_ZERO (&r);
|
|
FD_ZERO (&w);
|
|
|
|
int i, k ;
|
|
int num = 0 ;
|
|
|
|
if ( reads )
|
|
{
|
|
for ( i=0; reads[i]; i++ )
|
|
{
|
|
int fd = reads[i]->getHandle();
|
|
FD_SET (fd, &r);
|
|
num++;
|
|
}
|
|
}
|
|
|
|
if ( writes )
|
|
{
|
|
for ( i=0; writes[i]; i++ )
|
|
{
|
|
int fd = writes[i]->getHandle();
|
|
FD_SET (fd, &w);
|
|
num++;
|
|
}
|
|
}
|
|
|
|
if (!num)
|
|
return num ;
|
|
|
|
/* Set up the timeout */
|
|
struct timeval tv ;
|
|
tv.tv_sec = timeout/1000;
|
|
tv.tv_usec = (timeout%1000)*1000;
|
|
|
|
// It bothers me that select()'s first argument does not appear to
|
|
// work as advertised... [it hangs like this if called with
|
|
// anything less than FD_SETSIZE, which seems wasteful?]
|
|
|
|
// Note: we ignore the 'exception' fd_set - I have never had a
|
|
// need to use it. The name is somewhat misleading - the only
|
|
// thing I have ever seen it used for is to detect urgent data -
|
|
// which is an unportable feature anyway.
|
|
|
|
retval = ::select (FD_SETSIZE, &r, &w, 0, &tv);
|
|
|
|
//remove sockets that had no activity
|
|
|
|
num = 0 ;
|
|
|
|
if ( reads )
|
|
{
|
|
for ( k=i=0; reads[i]; i++ )
|
|
{
|
|
int fd = reads[i]->getHandle();
|
|
if ( FD_ISSET (fd, &r) )
|
|
{
|
|
reads[k++] = reads[i];
|
|
num++;
|
|
}
|
|
}
|
|
reads[k] = NULL ;
|
|
}
|
|
|
|
if ( writes )
|
|
{
|
|
for ( k=i=0; writes[i]; i++ )
|
|
{
|
|
int fd = writes[i]->getHandle();
|
|
if ( FD_ISSET (fd, &w) )
|
|
{
|
|
writes[k++] = writes[i];
|
|
num++;
|
|
}
|
|
}
|
|
writes[k] = NULL ;
|
|
}
|
|
|
|
if (retval == 0) // timeout
|
|
return (-2);
|
|
if (retval == -1)// error
|
|
return (-1);
|
|
|
|
return num ;
|
|
}
|
|
|
|
|
|
/* Init/Exit functions */
|
|
|
|
static void netExit ( void )
|
|
{
|
|
#if defined(WINSOCK)
|
|
/* Clean up windows networking */
|
|
if ( WSACleanup() == SOCKET_ERROR ) {
|
|
if ( WSAGetLastError() == WSAEINPROGRESS ) {
|
|
WSACancelBlockingCall();
|
|
WSACleanup();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int Socket::initSockets()
|
|
{
|
|
assert ( sizeof(sockaddr_in) == sizeof(IPAddress) ) ;
|
|
|
|
#if defined(WINSOCK)
|
|
/* Start up the windows networking */
|
|
WORD version_wanted = MAKEWORD(1,1);
|
|
WSADATA wsaData;
|
|
|
|
if ( WSAStartup(version_wanted, &wsaData) != 0 ) {
|
|
throw sg_exception("WinSock initialization failed");
|
|
}
|
|
#endif
|
|
|
|
atexit( netExit ) ;
|
|
return(0);
|
|
}
|
|
|
|
|
|
} // of namespace simgear
|
|
|