2024-03-19 17:45:12 +08:00

557 lines
15 KiB
C++

/*
** Copyright 2003-2009, Ernest Laurentin (http://www.ernzo.com/)
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
**
** File: SocketHandle.cpp
** Version: 1.4 - IPv6 support
** 1.3 - Update for Asynchronous mode / Linux port
** 1.2 - Update interface for TCP remote connection
** 1.1 - Added multicast support
*/
#include <crtdbg.h>
#include "SocketHandle.h"
#ifndef BUFFER_SIZE
#define BUFFER_SIZE 64*1024
#endif
#ifndef SOCKHANDLE_TTL
#define SOCKHANDLE_TTL 5
#endif
#ifndef SOCKHANDLE_HOPS
#define SOCKHANDLE_HOPS 10
#endif
#define HOSTNAME_SIZE MAX_PATH
#define STRING_LENGTH 40
///////////////////////////////////////////////////////////////////////////////
// CSocketHandle
CSocketHandle::CSocketHandle()
: m_hSocket(INVALID_SOCKET)
{
}
CSocketHandle::~CSocketHandle()
{
Close();
}
///////////////////////////////////////////////////////////////////////////////
// IsOpen
bool CSocketHandle::IsOpen() const
{
return ( INVALID_SOCKET != m_hSocket );
}
///////////////////////////////////////////////////////////////////////////////
// GetSocket
SOCKET CSocketHandle::GetSocket() const
{
return m_hSocket;
}
///////////////////////////////////////////////////////////////////////////////
// Attach
bool CSocketHandle::Attach(SOCKET sock)
{
if ( INVALID_SOCKET == m_hSocket )
{
m_hSocket = sock;
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
// Detach
SOCKET CSocketHandle::Detach()
{
SOCKET sock = m_hSocket;
::InterlockedExchange(reinterpret_cast<long*>(&m_hSocket), INVALID_SOCKET);
return sock;
}
///////////////////////////////////////////////////////////////////////////////
// Close
void CSocketHandle::Close()
{
if ( IsOpen() )
{
ShutdownConnection(static_cast<SOCKET>(
::InterlockedExchange((LONG*)&m_hSocket, INVALID_SOCKET)
));
}
m_hSocket = INVALID_SOCKET;
}
///////////////////////////////////////////////////////////////////////////////
// CreateSocket
bool CSocketHandle::CreateSocket(LPCTSTR pszHostName, int port,
int nFamily, int nType, UINT uOptions /* = 0 */)
{
// Socket is already opened
if ( IsOpen() ) {
SetLastError(ERROR_ACCESS_DENIED);
return false;
}
// Create a Socket that is bound to a specific service provider
// nFamily: (AF_INET, AF_INET6)
// nType: (SOCK_STREAM, SOCK_DGRAM)
#ifdef SOCKHANDLE_USE_OVERLAPPED
SOCKET sock = WSASocket(nFamily, nType, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED);
#else
SOCKET sock = socket(nFamily, nType, IPPROTO_IP);
#endif
if (INVALID_SOCKET != sock)
{
if (uOptions & SO_REUSEADDR)
{
// Inform Windows Sockets provider that a bind on a socket should not be disallowed
// because the desired address is already in use by another socket
BOOL optval = TRUE;
if ( SOCKET_ERROR == setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, (char *) &optval, sizeof( BOOL ) ) )
{
SetLastError( WSAGetLastError() );
closesocket( sock );
return false;
}
}
if (nType == SOCK_DGRAM)
{
if ((uOptions & SO_BROADCAST) && (nFamily == AF_INET))
{
// Inform Windows Sockets provider that broadcast messages are allowed
BOOL optval = TRUE;
if ( SOCKET_ERROR == setsockopt( sock, SOL_SOCKET, SO_BROADCAST, (char *) &optval, sizeof( BOOL ) ) )
{
SetLastError( WSAGetLastError() );
closesocket( sock );
return false;
}
}
#ifdef SOCKHANDLE_CONFIGBUF
// configure buffer size
socklen_t rcvbuf = BUFFER_SIZE;
if ( SOCKET_ERROR == setsockopt( sock, SOL_SOCKET, SO_RCVBUF, (char *) &rcvbuf, sizeof( int ) ) )
{
SetLastError( WSAGetLastError() );
closesocket( sock );
return false;
}
#endif
}
// Associate a local address with the socket
SOCKADDR_IN sockAddr;
memset((char *)&sockAddr, 0,sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(port);
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (SOCKET_ERROR == bind(sock, (struct sockaddr *)&sockAddr, sizeof(sockAddr)))
{
SetLastError( WSAGetLastError() );
closesocket( sock );
return false;
}
// Listen to the socket, only valid for connection socket (TCP)
if (SOCK_STREAM == nType)
{
if ( SOCKET_ERROR == listen(sock, SOMAXCONN))
{
SetLastError( WSAGetLastError() );
closesocket( sock );
return false;
}
}
// Success, now we may save this socket
m_hSocket = sock;
}
return (INVALID_SOCKET != sock);
}
///////////////////////////////////////////////////////////////////////////////
// ConnectTo
bool CSocketHandle::ConnectTo(LPCTSTR pszRemote,int port, int nFamily, int nType,long timeout)
{
// Socket is already opened
if ( IsOpen() ) {
SetLastError(ERROR_ACCESS_DENIED);
return false;
}
SOCKET sock = socket(nFamily, nType, IPPROTO_TCP);
if (sock < 0) {
return false;
}
bool rel = false;
// Associate a local address with the socket but let provider assign a port number
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(port);
//serAddr.sin_addr.s_addr = inet_addr(pszRemote);
inet_pton(AF_INET, pszRemote, &serAddr.sin_addr);
memset(serAddr.sin_zero, 0x00, sizeof(serAddr.sin_zero));
int error = -1;
int len = sizeof(int);
timeval tm;
fd_set set;
unsigned long ul = 1;
ioctlsocket(sock, FIONBIO, &ul);
// try to connect - if fail, server not ready
if (SOCKET_ERROR == connect( sock, (sockaddr *)&serAddr, sizeof(serAddr)))
{
tm.tv_sec = timeout / 1000.0;
tm.tv_usec = (timeout % 1000) * 1000;
FD_ZERO(&set);
FD_SET(sock, &set);
if (select(sock + 1, NULL, &set, NULL, &tm) > 0)
{
getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, /*(socklen_t *)*/&len);
if (error == 0) {
rel=true;
}
else {
rel=false;
}
}
else {
rel=false;
}
}
else {
rel = true;
}
ul = 0;
ioctlsocket(sock, FIONBIO, &ul);
if (!rel) {
closesocket(sock);
return false;
}
else {
m_hSocket = sock;
return true;
}
}
///////////////////////////////////////////////////////////////////////////////
// Read
int CSocketHandle::Read(LPBYTE lpBuffer, int dwSize, LPSOCKADDR lpAddrIn,
long dwTimeout)
{
_ASSERTE( IsOpen() );
_ASSERTE( lpBuffer != NULL );
if (!IsOpen() || lpBuffer == NULL || dwSize < 1L)
return -1;
fd_set fdRead = { 0 };
TIMEVAL stTime;
TIMEVAL *pstTime = NULL;
if ( INFINITE != dwTimeout ) {
stTime.tv_sec = dwTimeout/1000;
stTime.tv_usec = (dwTimeout%1000)*1000;
pstTime = &stTime;
}
SOCKET s = GetSocket();
// Set Descriptor
FD_SET( s, &fdRead );
// Select function set read timeout
int dwBytesRead = 0;
int res = 1;
if ( pstTime != NULL )
res = select((int)s, &fdRead, NULL, NULL, pstTime );
if ( res > 0)
{
if (lpAddrIn)
{
// UDP
socklen_t fromlen = sizeof(SOCKADDR_STORAGE);
res = recvfrom(s, reinterpret_cast<LPSTR>(lpBuffer), dwSize, 0, lpAddrIn, &fromlen);
}
else
{
// TCP
res = recv(s, reinterpret_cast<LPSTR>(lpBuffer), dwSize, 0);
}
if ( res == 0 ) {
WSASetLastError(WSAECONNRESET);
res = SOCKET_ERROR;
}
}
if ( res == SOCKET_ERROR )
{
SetLastError( WSAGetLastError() );
}
dwBytesRead = ((res >= 0)?(res) : (-1));
return dwBytesRead;
}
#ifdef WIN32
///////////////////////////////////////////////////////////////////////////////
// ReadEx
DWORD CSocketHandle::ReadEx(LPBYTE lpBuffer, DWORD dwSize, LPSOCKADDR lpAddrIn,
LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
{
_ASSERTE( IsOpen() );
_ASSERTE( lpBuffer != NULL );
if (!IsOpen() || lpBuffer == NULL || dwSize < 1L)
return (DWORD)-1L;
SOCKET s = GetSocket();
// Send message to peer
WSABUF wsabuf;
wsabuf.buf = (char FAR*)lpBuffer;
wsabuf.len = dwSize;
// Select function set read timeout
DWORD dwBytesRead = 0L;
DWORD dwFlags = 0L;
int res = 0;
if (lpAddrIn)
{
// UDP
socklen_t fromlen = sizeof(SOCKADDR_STORAGE);
res = WSARecvFrom( s, &wsabuf, 1, &dwBytesRead, &dwFlags, lpAddrIn, &fromlen, lpOverlapped, lpCompletionRoutine);
}
else
{
// TCP
res = WSARecv( s, &wsabuf, 1, &dwBytesRead, &dwFlags, lpOverlapped, lpCompletionRoutine);
}
if ( res == SOCKET_ERROR )
{
res = WSAGetLastError();
if ( res != WSA_IO_PENDING )
{
dwBytesRead = (DWORD)-1L;
SetLastError( res );
}
}
return dwBytesRead;
}
#endif
///////////////////////////////////////////////////////////////////////////////
// Write
int CSocketHandle::Write(const LPBYTE lpBuffer, int dwCount,
const LPSOCKADDR lpAddrIn, long dwTimeout)
{
_ASSERTE( IsOpen() );
_ASSERTE( NULL != lpBuffer );
// validate params
if (!IsOpen() || NULL == lpBuffer)
return -1;
fd_set fdWrite = { 0 };
TIMEVAL stTime;
TIMEVAL *pstTime = NULL;
if ( INFINITE != dwTimeout ) {
stTime.tv_sec = dwTimeout/1000;
stTime.tv_usec = (dwTimeout%1000)*1000;
pstTime = &stTime;
}
SOCKET s = GetSocket();
// Set Descriptor
FD_SET( s, &fdWrite );
// Select function set write timeout
int dwBytesWritten = 0;
int res = 1;
if ( pstTime != NULL )
res = select((int)s, NULL, &fdWrite, NULL, pstTime );
if ( res > 0)
{
// Send message to peer
if (lpAddrIn)
{
// UDP
res = sendto( s, reinterpret_cast<LPCSTR>(lpBuffer), dwCount, 0, lpAddrIn, sizeof(SOCKADDR_STORAGE));
}
else
{
// TCP
res = send( s, reinterpret_cast<LPCSTR>(lpBuffer), dwCount, 0);
}
}
if ( res == SOCKET_ERROR )
{
SetLastError( WSAGetLastError() );
}
dwBytesWritten =((res >= 0)?(res) : (-1));
return dwBytesWritten;
}
#ifdef WIN32
///////////////////////////////////////////////////////////////////////////////
// WriteEx
DWORD CSocketHandle::WriteEx(const LPBYTE lpBuffer, DWORD dwCount,
const LPSOCKADDR lpAddrIn,
LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
{
_ASSERTE( IsOpen() );
_ASSERTE( NULL != lpBuffer );
// validate params
if (!IsOpen() || NULL == lpBuffer)
return (DWORD)-1L;
SOCKET s = GetSocket();
// Select function set write timeout
DWORD dwBytesWritten = 0L;
int res = 0;
// Send message to peer
WSABUF wsabuf;
wsabuf.buf = (char FAR*) lpBuffer;
wsabuf.len = dwCount;
if (lpAddrIn)
{
// UDP
res = WSASendTo( s, &wsabuf, 1, &dwBytesWritten, 0, lpAddrIn, sizeof(SOCKADDR_STORAGE),
lpOverlapped, lpCompletionRoutine);
}
else // TCP
res = WSASend( s, &wsabuf, 1, &dwBytesWritten, 0, lpOverlapped, lpCompletionRoutine);
if ( res == SOCKET_ERROR )
{
res = WSAGetLastError();
if ( res != WSA_IO_PENDING )
{
dwBytesWritten = (DWORD)-1L;
SetLastError( res );
}
}
return dwBytesWritten;
}
#endif
#ifdef WIN32
///////////////////////////////////////////////////////////////////////////////
// IOControl
bool CSocketHandle::IOControl(DWORD dwIoCode, LPBYTE lpInBuffer, DWORD cbInBuffer,
LPBYTE lpOutBuffer, DWORD cbOutBuffer,
LPDWORD lpcbBytesReturned, LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
{
_ASSERTE( IsOpen() );
// validate params
if ( !IsOpen() ) {
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
int res;
SOCKET s = GetSocket();
res = WSAIoctl(s, dwIoCode, lpInBuffer, cbInBuffer, lpOutBuffer, cbOutBuffer,
lpcbBytesReturned, lpOverlapped, lpCompletionRoutine);
if ( res == SOCKET_ERROR )
{
SetLastError( WSAGetLastError() );
}
return ( res != SOCKET_ERROR );
}
///////////////////////////////////////////////////////////////////////////////
// GetTransferOverlappedResult
bool CSocketHandle::GetTransferOverlappedResult(LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer,
bool bWait /*= true*/, LPDWORD lpdwFlags /*= NULL*/)
{
_ASSERTE( IsOpen() );
_ASSERTE( NULL != lpOverlapped );
// validate params
if (!IsOpen() || NULL == lpOverlapped) {
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
SOCKET s = GetSocket();
DWORD dwFlags = 0;
if ( lpdwFlags == NULL )
lpdwFlags = &dwFlags;
BOOL bRet = WSAGetOverlappedResult( s, lpOverlapped, lpcbTransfer, bWait, lpdwFlags );
if ( !bRet )
{
SetLastError( WSAGetLastError() );
}
return (bRet != FALSE);
}
#endif
///////////////////////////////////////////////////////////////////////////////
// Utility functions
///////////////////////////////////////////////////////////////////////////////
// InitLibrary
bool CSocketHandle::InitLibrary(WORD wVersion)
{
#ifdef WIN32
WSADATA WSAData = { 0 };
return ( 0 == WSAStartup( wVersion, &WSAData ) );
#else
return true;
#endif
}
///////////////////////////////////////////////////////////////////////////////
// ReleaseLibrary
bool CSocketHandle::ReleaseLibrary()
{
#ifdef WIN32
return ( 0 == WSACleanup() );
#else
return true;
#endif
}
///////////////////////////////////////////////////////////////////////////////
// WaitForConnection
SOCKET CSocketHandle::WaitForConnection(SOCKET sock)
{
return accept(sock, 0, 0);
}
///////////////////////////////////////////////////////////////////////////////
// ShutdownConnection
bool CSocketHandle::ShutdownConnection(SOCKET sock)
{
shutdown(sock, SD_BOTH);
return ( 0 == closesocket( sock ));
}