328 lines
11 KiB
C++
Executable file
328 lines
11 KiB
C++
Executable file
/******************************************************************************\
|
|
* Copyright (c) 2004-2020
|
|
*
|
|
* Author(s):
|
|
* Volker Fischer
|
|
*
|
|
******************************************************************************
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation; either version 2 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* This program 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 General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
\******************************************************************************/
|
|
|
|
#include "socket.h"
|
|
#include "server.h"
|
|
|
|
|
|
/* Implementation *************************************************************/
|
|
void CSocket::Init ( const quint16 iPortNumber )
|
|
{
|
|
#ifdef _WIN32
|
|
// for the Windows socket usage we have to start it up first
|
|
|
|
// TODO check for error and exit application on error
|
|
|
|
WSADATA wsa;
|
|
WSAStartup ( MAKEWORD(1, 0), &wsa );
|
|
#endif
|
|
|
|
// create the UDP socket
|
|
UdpSocket = socket ( AF_INET, SOCK_DGRAM, 0 );
|
|
|
|
// allocate memory for network receive and send buffer in samples
|
|
vecbyRecBuf.Init ( MAX_SIZE_BYTES_NETW_BUF );
|
|
|
|
// preinitialize socket in address (only the port number is missing)
|
|
sockaddr_in UdpSocketInAddr;
|
|
UdpSocketInAddr.sin_family = AF_INET;
|
|
UdpSocketInAddr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
// initialize the listening socket
|
|
bool bSuccess;
|
|
|
|
if ( bIsClient )
|
|
{
|
|
// Per definition use the port number plus ten for the client to make
|
|
// it possible to run server and client on the same computer. If the
|
|
// port is not available, try "NUM_SOCKET_PORTS_TO_TRY" times with
|
|
// incremented port numbers
|
|
quint16 iClientPortIncrement = 10; // start value: port nubmer plus ten
|
|
bSuccess = false; // initialization for while loop
|
|
|
|
while ( !bSuccess &&
|
|
( iClientPortIncrement <= NUM_SOCKET_PORTS_TO_TRY ) )
|
|
{
|
|
UdpSocketInAddr.sin_port = htons ( iPortNumber + iClientPortIncrement );
|
|
|
|
bSuccess = ( ::bind ( UdpSocket ,
|
|
(sockaddr*) &UdpSocketInAddr,
|
|
sizeof ( sockaddr_in ) ) == 0 );
|
|
|
|
iClientPortIncrement++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// for the server, only try the given port number and do not try out
|
|
// other port numbers to bind since it is imporatant that the server
|
|
// gets the desired port number
|
|
UdpSocketInAddr.sin_port = htons ( iPortNumber );
|
|
|
|
bSuccess = ( ::bind ( UdpSocket ,
|
|
(sockaddr*) &UdpSocketInAddr,
|
|
sizeof ( sockaddr_in ) ) == 0 );
|
|
}
|
|
|
|
if ( !bSuccess )
|
|
{
|
|
// we cannot bind socket, throw error
|
|
throw CGenErr ( "Cannot bind the socket (maybe "
|
|
"the software is already running).", "Network Error" );
|
|
}
|
|
|
|
|
|
// Connections -------------------------------------------------------------
|
|
// it is important to do the following connections in this class since we
|
|
// have a thread transition
|
|
|
|
// we have different connections for client and server
|
|
if ( bIsClient )
|
|
{
|
|
// client connections:
|
|
|
|
QObject::connect ( this,
|
|
SIGNAL ( ProtcolMessageReceived ( int, int, CVector<uint8_t>, CHostAddress ) ),
|
|
pChannel, SLOT ( OnProtcolMessageReceived ( int, int, CVector<uint8_t>, CHostAddress ) ) );
|
|
|
|
QObject::connect ( this,
|
|
SIGNAL ( ProtcolCLMessageReceived ( int, CVector<uint8_t>, CHostAddress ) ),
|
|
pChannel, SLOT ( OnProtcolCLMessageReceived ( int, CVector<uint8_t>, CHostAddress ) ) );
|
|
|
|
QObject::connect ( this,
|
|
SIGNAL ( NewConnection() ),
|
|
pChannel, SLOT ( OnNewConnection() ) );
|
|
}
|
|
else
|
|
{
|
|
// server connections:
|
|
|
|
QObject::connect ( this,
|
|
SIGNAL ( ProtcolMessageReceived ( int, int, CVector<uint8_t>, CHostAddress ) ),
|
|
pServer, SLOT ( OnProtcolMessageReceived ( int, int, CVector<uint8_t>, CHostAddress ) ) );
|
|
|
|
QObject::connect ( this,
|
|
SIGNAL ( ProtcolCLMessageReceived ( int, CVector<uint8_t>, CHostAddress ) ),
|
|
pServer, SLOT ( OnProtcolCLMessageReceived ( int, CVector<uint8_t>, CHostAddress ) ) );
|
|
|
|
QObject::connect ( this,
|
|
SIGNAL ( NewConnection ( int, CHostAddress ) ),
|
|
pServer, SLOT ( OnNewConnection ( int, CHostAddress ) ) );
|
|
|
|
QObject::connect ( this,
|
|
SIGNAL ( ServerFull ( CHostAddress ) ),
|
|
pServer, SLOT ( OnServerFull ( CHostAddress ) ) );
|
|
}
|
|
}
|
|
|
|
void CSocket::Close()
|
|
{
|
|
#ifdef _WIN32
|
|
// closesocket will cause recvfrom to return with an error because the
|
|
// socket is closed -> then the thread can safely be shut down
|
|
closesocket ( UdpSocket );
|
|
#elif defined ( __APPLE__ ) || defined ( __MACOSX )
|
|
// on Mac the general close has the same effect as closesocket on Windows
|
|
close ( UdpSocket );
|
|
#else
|
|
// on Linux the shutdown call cancels the recvfrom
|
|
shutdown ( UdpSocket, SHUT_RDWR );
|
|
#endif
|
|
}
|
|
|
|
CSocket::~CSocket()
|
|
{
|
|
// cleanup the socket (on Windows the WSA cleanup must also be called)
|
|
#ifdef _WIN32
|
|
closesocket ( UdpSocket );
|
|
WSACleanup();
|
|
#else
|
|
close ( UdpSocket );
|
|
#endif
|
|
}
|
|
|
|
void CSocket::SendPacket ( const CVector<uint8_t>& vecbySendBuf,
|
|
const CHostAddress& HostAddr )
|
|
{
|
|
QMutexLocker locker ( &Mutex );
|
|
|
|
const int iVecSizeOut = vecbySendBuf.Size();
|
|
|
|
if ( iVecSizeOut > 0 )
|
|
{
|
|
// send packet through network (we have to convert the constant unsigned
|
|
// char vector in "const char*", for this we first convert the const
|
|
// uint8_t vector in a read/write uint8_t vector and then do the cast to
|
|
// const char*)
|
|
sockaddr_in UdpSocketOutAddr;
|
|
|
|
UdpSocketOutAddr.sin_family = AF_INET;
|
|
UdpSocketOutAddr.sin_port = htons ( HostAddr.iPort );
|
|
UdpSocketOutAddr.sin_addr.s_addr = htonl ( HostAddr.InetAddr.toIPv4Address() );
|
|
|
|
sendto ( UdpSocket,
|
|
(const char*) &( (CVector<uint8_t>) vecbySendBuf )[0],
|
|
iVecSizeOut,
|
|
0,
|
|
(sockaddr*) &UdpSocketOutAddr,
|
|
sizeof ( sockaddr_in ) );
|
|
}
|
|
}
|
|
|
|
bool CSocket::GetAndResetbJitterBufferOKFlag()
|
|
{
|
|
// check jitter buffer status
|
|
if ( !bJitterBufferOK )
|
|
{
|
|
// reset flag and return "not OK" status
|
|
bJitterBufferOK = true;
|
|
return false;
|
|
}
|
|
|
|
// the buffer was OK, we do not have to reset anything and just return the
|
|
// OK status
|
|
return true;
|
|
}
|
|
|
|
void CSocket::OnDataReceived()
|
|
{
|
|
/*
|
|
The strategy of this function is that only the "put audio" function is
|
|
called directly (i.e. the high thread priority is used) and all other less
|
|
important things like protocol parsing and acting on protocol messages is
|
|
done in the low priority thread. To get a thread transition, we have to
|
|
use the signal/slot mechanism (i.e. we use messages for that).
|
|
*/
|
|
|
|
// read block from network interface and query address of sender
|
|
sockaddr_in SenderAddr;
|
|
#ifdef _WIN32
|
|
int SenderAddrSize = sizeof ( sockaddr_in );
|
|
#else
|
|
socklen_t SenderAddrSize = sizeof ( sockaddr_in );
|
|
#endif
|
|
|
|
const long iNumBytesRead = recvfrom ( UdpSocket,
|
|
(char*) &vecbyRecBuf[0],
|
|
MAX_SIZE_BYTES_NETW_BUF,
|
|
0,
|
|
(sockaddr*) &SenderAddr,
|
|
&SenderAddrSize );
|
|
|
|
// check if an error occurred or no data could be read
|
|
if ( iNumBytesRead <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// convert address of client
|
|
RecHostAddr.InetAddr.setAddress ( ntohl ( SenderAddr.sin_addr.s_addr ) );
|
|
RecHostAddr.iPort = ntohs ( SenderAddr.sin_port );
|
|
|
|
|
|
// check if this is a protocol message
|
|
int iRecCounter;
|
|
int iRecID;
|
|
CVector<uint8_t> vecbyMesBodyData;
|
|
|
|
if ( !CProtocol::ParseMessageFrame ( vecbyRecBuf,
|
|
iNumBytesRead,
|
|
vecbyMesBodyData,
|
|
iRecCounter,
|
|
iRecID ) )
|
|
{
|
|
// this is a protocol message, check the type of the message
|
|
if ( CProtocol::IsConnectionLessMessageID ( iRecID ) )
|
|
{
|
|
|
|
// TODO a copy of the vector is used -> avoid malloc in real-time routine
|
|
|
|
emit ProtcolCLMessageReceived ( iRecID, vecbyMesBodyData, RecHostAddr );
|
|
}
|
|
else
|
|
{
|
|
|
|
// TODO a copy of the vector is used -> avoid malloc in real-time routine
|
|
|
|
emit ProtcolMessageReceived ( iRecCounter, iRecID, vecbyMesBodyData, RecHostAddr );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// this is most probably a regular audio packet
|
|
if ( bIsClient )
|
|
{
|
|
// client:
|
|
|
|
switch ( pChannel->PutAudioData ( vecbyRecBuf, iNumBytesRead, RecHostAddr ) )
|
|
{
|
|
case PS_AUDIO_ERR:
|
|
case PS_GEN_ERROR:
|
|
bJitterBufferOK = false;
|
|
break;
|
|
|
|
case PS_NEW_CONNECTION:
|
|
// inform other objects that new connection was established
|
|
emit NewConnection();
|
|
break;
|
|
|
|
case PS_AUDIO_INVALID:
|
|
// inform about received invalid packet by fireing an event
|
|
emit InvalidPacketReceived ( RecHostAddr );
|
|
break;
|
|
|
|
default:
|
|
// do nothing
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// server:
|
|
|
|
int iCurChanID;
|
|
|
|
if ( pServer->PutAudioData ( vecbyRecBuf, iNumBytesRead, RecHostAddr, iCurChanID ) )
|
|
{
|
|
// we have a new connection, emit a signal
|
|
emit NewConnection ( iCurChanID, RecHostAddr );
|
|
|
|
// this was an audio packet, start server if it is in sleep mode
|
|
if ( !pServer->IsRunning() )
|
|
{
|
|
// (note that Qt will delete the event object when done)
|
|
QCoreApplication::postEvent ( pServer,
|
|
new CCustomEvent ( MS_PACKET_RECEIVED, 0, 0 ) );
|
|
}
|
|
}
|
|
|
|
// check if no channel is available
|
|
if ( iCurChanID == INVALID_CHANNEL_ID )
|
|
{
|
|
// fire message for the state that no free channel is available
|
|
emit ServerFull ( RecHostAddr );
|
|
}
|
|
}
|
|
}
|
|
}
|