/****************************************************************************
*
* Copyright (C) 2000-2001 RealNetworks, Inc. All rights reserved.
*
* This program is free software. It may be distributed under the terms
* in the file LICENSE, found in the top level of the source distribution.
*
*/
#ifdef _UNIX
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>
#endif
#include "dbg.h"
#include "sock.h"
#include "thread.h"
#if defined(_UNIX)
#include <sys/ioctl.h>
#define SOCK_LAST_ERROR() errno
#define SOCKET_ERROR -1
#define closesocket close
#define ioctlsocket ioctl
typedef int sioctl_t;
#endif
#if defined(_WIN32)
#define SOCK_LAST_ERROR() ::WSAGetLastError()
typedef unsigned long sioctl_t;
#endif
#if defined(_WIN32) || defined(_SOLARIS)
int inet_aton( const char* cp, struct in_addr* inp )
{
UINT32 addr = inet_addr( cp );
if( addr == INADDR_NONE ) return 0;
inp->s_addr = addr;
return 1;
}
#endif
#if defined(_UNIX)
static void socket_nonblock( sockobj_t sock )
{
int tmp;
fcntl( sock, F_GETFL, &tmp );
tmp |= O_NONBLOCK;
fcntl( sock, F_SETFL, &tmp );
}
#endif
#if defined(_WIN32)
static void socket_nonblock( sockobj_t sock )
{
sioctl_t tmp = 1;
ioctlsocket( sock, FIONBIO, &tmp );
}
#endif
static void socket_reuseaddr( sockobj_t sock )
{
int tmp = 1;
setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&tmp, sizeof(tmp) );
}
/**************************************
*
* CInetAddr
*
**************************************/
CInetAddr::CInetAddr( void )
{
m_addr.sin_family = AF_INET;
m_addr.sin_addr.s_addr = INADDR_NONE;
m_addr.sin_port = 0;
}
CInetAddr::CInetAddr( const in_addr& host )
{
m_addr.sin_family = AF_INET;
m_addr.sin_addr = host;
m_addr.sin_port = 0;
}
CInetAddr::CInetAddr( CPCHAR szHost )
{
SetHost( szHost );
}
bool CInetAddr::IsValid( void ) const
{
return ( m_addr.sin_addr.s_addr != INADDR_NONE );
}
in_addr CInetAddr::GetHost( void ) const
{
return m_addr.sin_addr;
}
void CInetAddr::SetHost( const in_addr& host )
{
m_addr.sin_addr = host;
}
void CInetAddr::SetHost( CPCHAR szHost )
{
m_addr.sin_addr.s_addr = INADDR_NONE;
Resolve( szHost, &m_addr.sin_addr );
}
CInetAddr CInetAddr::Any( void )
{
CInetAddr addr;
addr.m_addr.sin_addr.s_addr = INADDR_ANY;
return addr;
}
CInetAddr CInetAddr::None( void )
{
CInetAddr addr;
addr.m_addr.sin_addr.s_addr = INADDR_NONE;
return addr;
}
bool CInetAddr::Resolve( CPCHAR szHost, in_addr* phost )
{
bool bRet = false;
bool bIsName = false;
if( NULL == szHost ) return false;
// Determine if this is a number or name
CPCHAR p = szHost;
while( *p )
{
if( (*p < '0' || *p > '9') && *p != '.' )
{
bIsName = true;
break;
}
p++;
}
if( bIsName )
{
hostent* phent;
phent = gethostbyname( szHost );
if( phent )
{
memcpy( phost, phent->h_addr, sizeof(in_addr) );
bRet = true;
}
}
else
{
bRet = ( 0 != inet_aton( szHost, phost ) );
}
return bRet;
}
/**************************************
*
* CSockAddr
*
**************************************/
CSockAddr::CSockAddr( void ) :
CInetAddr()
{
// Empty
}
CSockAddr::CSockAddr( const sockaddr_in& addr ) :
CInetAddr()
{
SetAddr( addr );
}
CSockAddr::CSockAddr( const in_addr& host, UINT16 port /* = 0 */ ) :
CInetAddr(host)
{
SetAddr( host, port );
}
CSockAddr::CSockAddr( CPCHAR szHost, UINT16 port /* = 0 */ ) :
CInetAddr()
{
SetAddr( szHost, port );
}
sockaddr_in CSockAddr::GetAddr( void ) const
{
return m_addr;
}
void CSockAddr::SetAddr( const sockaddr_in& addr )
{
m_addr = addr;
}
void CSockAddr::SetAddr( const in_addr& host, UINT16 port )
{
SetHost( host );
m_addr.sin_port = htons( port );
}
void CSockAddr::SetAddr( CPCHAR szHost, UINT16 port )
{
SetHost( szHost );
m_addr.sin_port = htons( port );
}
UINT16 CSockAddr::GetPort( void ) const
{
return ntohs( m_addr.sin_port );
}
void CSockAddr::SetPort( UINT16 port )
{
m_addr.sin_port = htons( port );
}
CSockAddr CSockAddr::Any( void )
{
CSockAddr addr;
addr.m_addr.sin_addr.s_addr = INADDR_ANY;
addr.m_addr.sin_port = 0;
return addr;
}
CSockAddr CSockAddr::None( void )
{
CSockAddr addr;
addr.m_addr.sin_addr.s_addr = INADDR_NONE;
addr.m_addr.sin_port = 0xFFFF;
return addr;
}
/**************************************
*
* CSocket
*
**************************************/
CSocket::CSocket( void ) :
CStream(),
m_sock(INVALID_SOCKET),
m_uSelectFlags(SF_NONE),
m_err(SOCKERR_NONE)
{
// Empty
}
CSocket::CSocket( CStreamResponse* pResponse ) :
CStream(pResponse),
m_sock(INVALID_SOCKET),
m_uSelectFlags(SF_NONE),
m_err(SOCKERR_NONE)
{
// Empty
}
CSocket::~CSocket( void )
{
Close();
}
bool CSocket::IsOpen( void )
{
return ( INVALID_SOCKET != m_sock );
}
void CSocket::Close( void )
{
if( IsOpen() )
{
Select( SF_NONE );
closesocket( m_sock );
m_sock = INVALID_SOCKET;
if( m_pResponse ) m_pResponse->OnClosed();
}
}
size_t CSocket::Read( PVOID pbuf, size_t nLen )
{
assert_or_retv( SOCKERR_EOF, (pbuf != NULL && IsOpen()) );
m_err = SOCKERR_NONE;
ssize_t n = recv( m_sock, (char*)pbuf, nLen, 0 );
// For TCP sockets...
// If recv() returns zero, the remote end closed gracefully
// If we get EPIPE/WSAECONNRESET, the remote end has closed, but there
// may be more data left to read
if( n == 0 )
{
n = SOCKERR_EOF;
}
else if( n == SOCKET_ERROR )
{
n = 0;
m_err = SOCK_LAST_ERROR();
if( m_err != SOCKERR_WOULDBLOCK )
{
n = SOCKERR_EOF;
}
}
return n;
}
size_t CSocket::Write( CPVOID pbuf, size_t nLen )
{
assert_or_retv( 0, (pbuf != NULL && IsOpen()) );
m_err = SOCKERR_NONE;
ssize_t n = send( m_sock, (const char*)pbuf, nLen, 0 );
if( n == SOCKET_ERROR )
{
n = 0;
m_err = SOCK_LAST_ERROR();
if( m_err != SOCKERR_WOULDBLOCK )
{
n = SOCKERR_EOF;
}
}
return n;
}
CSockAddr CSocket::GetLocalAddr( void )
{
CSockAddr addr;
sockaddr_in sa;
socklen_t salen;
salen = sizeof(sa);
if( getsockname( m_sock, (sockaddr*)&sa, &salen ) == 0 )
{
addr.SetAddr( sa );
}
return addr;
}
CSockAddr CSocket::GetPeerAddr( void )
{
CSockAddr addr;
sockaddr_in sa;
socklen_t salen;
salen = sizeof(sa);
if( getpeername( m_sock, (sockaddr*)&sa, &salen ) == 0 )
{
addr.SetAddr( sa );
}
return addr;
}
bool CSocket::Select( UINT32 nWhich )
{
assert( IsOpen() || SF_NONE == nWhich );
assert( m_pResponse || SF_NONE == nWhich || SF_ACCEPT == nWhich );
if( nWhich != m_uSelectFlags )
{
CEventThread* pSelf;
#ifdef NO_RTTI
pSelf = (CEventThread*)CThread::This(); //XXX: very bad, upgrade compiler
#else
pSelf = dynamic_cast<CEventThread*>(CThread::This());
#endif
assert_or_retv( false, pSelf );
if( SF_NONE == m_uSelectFlags )
{
if( !pSelf->AddStream( this ) ) return false;
}
m_uSelectFlags = nWhich;
pSelf->SetStreamSelect( this, nWhich );
if( SF_NONE == nWhich )
{
pSelf->DelStream( this );
}
}
return true;
}
sockerr_t CSocket::LastError( void )
{
return m_err;
}
/**************************************
*
* CListenSocket
*
**************************************/
CListenSocket::CListenSocket( void ) :
CSocket(),
m_pAcceptResponse(NULL)
{
// Empty
}
CListenSocket::CListenSocket( CListenSocketResponse* pResponse ) :
CSocket(),
m_pAcceptResponse(pResponse)
{
// Empty
}
CListenSocket::~CListenSocket( void )
{
// Empty
}
void CListenSocket::SetResponse( CListenSocketResponse* pResponse )
{
assert( pResponse || !IsOpen() );
m_pAcceptResponse = pResponse;
}
bool CListenSocket::Listen( const CSockAddr& addr )
{
assert_or_retv( false, (m_pAcceptResponse && !IsOpen()) );
m_err = SOCKERR_NONE;
m_sock = socket( AF_INET, SOCK_STREAM, IPPROTO_IP );
if( m_sock == INVALID_SOCKET )
{
m_err = SOCK_LAST_ERROR();
return false;
}
socket_nonblock( m_sock );
socket_reuseaddr( m_sock );
sockaddr_in bindaddr = addr.GetAddr();
if( 0 != bind( m_sock, (sockaddr*)&bindaddr, sizeof(bindaddr) ) )
{
m_err = SOCK_LAST_ERROR();
closesocket( m_sock );
m_sock = INVALID_SOCKET;
return false;
}
//XXX: Is there a performance penalty for SOMAXCONN?
if( 0 != listen( m_sock, SOMAXCONN ) )
{
m_err = SOCK_LAST_ERROR();
closesocket( m_sock );
m_sock = INVALID_SOCKET;
return false;
}
Select( SF_ACCEPT );
return true;
}
/**************************************
*
* CTcpSocket
*
**************************************/
CTcpSocket::CTcpSocket( void ) :
CSocket()
{
// Empty
}
CTcpSocket::CTcpSocket( CStreamResponse* pResponse ) :
CSocket(pResponse)
{
// Empty
}
CTcpSocket::~CTcpSocket( void )
{
// Empty
}
bool CTcpSocket::Connect( const CSockAddr& addr )
{
assert_or_retv( false, (m_pResponse && !IsOpen()) );
m_err = SOCKERR_NONE;
m_sock = socket( AF_INET, SOCK_STREAM, IPPROTO_IP );
if( m_sock == INVALID_SOCKET )
{
m_err = SOCK_LAST_ERROR();
return false;
}
socket_nonblock( m_sock );
socket_reuseaddr( m_sock );
sockaddr_in cnxaddr = addr.GetAddr();
if( 0 == connect( m_sock, (sockaddr*)&cnxaddr, sizeof(cnxaddr) ) )
{
m_pResponse->OnConnectDone( m_err );
return true;
}
int cnxerr = SOCK_LAST_ERROR();
if( cnxerr != SOCKERR_INPROGRESS && cnxerr != SOCKERR_WOULDBLOCK )
{
m_err = cnxerr;
closesocket( m_sock );
m_sock = INVALID_SOCKET;
return false;
}
Select( SF_CONNECT );
return true;
}
/**************************************
*
* CUdpSocket
*
**************************************/
CUdpSocket::CUdpSocket( void ) :
CSocket()
{
// Empty
}
CUdpSocket::CUdpSocket( CStreamResponse* pResponse ) :
CSocket(pResponse)
{
// Empty
}
CUdpSocket::~CUdpSocket( void )
{
// Empty
}
bool CUdpSocket::Bind( const CSockAddr& addr )
{
assert_or_retv( false, !IsOpen() );
m_err = SOCKERR_NONE;
m_sock = socket( AF_INET, SOCK_DGRAM, IPPROTO_IP );
if( m_sock == INVALID_SOCKET )
{
m_err = SOCK_LAST_ERROR();
return false;
}
socket_nonblock( m_sock );
sockaddr_in bindaddr = addr.GetAddr();
if( 0 != bind( m_sock, (sockaddr*)&bindaddr, sizeof(bindaddr) ) )
{
m_err = SOCK_LAST_ERROR();
closesocket( m_sock );
m_sock = INVALID_SOCKET;
return false;
}
return true;
}
bool CUdpSocket::Connect( const CSockAddr& addr )
{
assert_or_retv( false, IsOpen() );
m_err = SOCKERR_NONE;
sockaddr_in cnxaddr = addr.GetAddr();
if( 0 != connect( m_sock, (sockaddr*)&cnxaddr, sizeof(cnxaddr) ) )
{
m_err = SOCK_LAST_ERROR();
return false;
}
return true;
}
size_t CUdpSocket::RecvFrom( CSockAddr* paddr, PVOID pbuf, size_t nLen )
{
assert_or_retv( 0, (paddr != NULL && pbuf != NULL && IsOpen()) );
m_err = SOCKERR_NONE;
sockaddr_in recvaddr;
socklen_t salen = sizeof(recvaddr);
ssize_t n = recvfrom( m_sock, (char*)pbuf, nLen, 0, (sockaddr*)&recvaddr, &salen );
if( n > 0 )
{
paddr->SetAddr( recvaddr );
}
else if( n == 0 )
{
dbgout( "*** recvfrom() returned zero ***" );
n = SOCKERR_EOF;
}
else if( n == SOCKET_ERROR )
{
n = 0;
m_err = SOCK_LAST_ERROR();
if( m_err != SOCKERR_WOULDBLOCK )
{
n = SOCKERR_EOF;
}
}
return n;
}
size_t CUdpSocket::SendTo( const CSockAddr& addr, CPVOID pbuf, size_t nLen )
{
assert_or_retv( 0, (pbuf != NULL && IsOpen()) );
m_err = SOCKERR_NONE;
sockaddr_in sendaddr = addr.GetAddr();
ssize_t n = sendto( m_sock, (const char*)pbuf, nLen, 0, (sockaddr*)&sendaddr, sizeof(sendaddr) );
if( n == SOCKET_ERROR )
{
n = 0;
m_err = SOCK_LAST_ERROR();
if( m_err != SOCKERR_WOULDBLOCK )
{
n = SOCKERR_EOF;
}
}
return n;
}
syntax highlighted by Code2HTML, v. 0.9.1