Initial handshake and network game start. Works with multiple clients. Not yet stable, because it is not thread-safe. GUI callbacks not complete yet (server dialog not updated).

This commit is contained in:
lotodore
2007-03-20 02:20:50 +00:00
parent 5f7ff8ca54
commit ef10602d5c
33 changed files with 731 additions and 113 deletions
+44 -5
View File
@@ -321,7 +321,7 @@ ClientStateStartSession::~ClientStateStartSession()
int
ClientStateStartSession::Process(ClientThread &client)
{
boost::shared_ptr<NetPacket> packet(new TestNetPacket(10));
boost::shared_ptr<NetPacket> packet(new NetPacketInit(10));
client.GetSender().Send(packet, client.GetContext().GetSocket());
client.SetState(ClientStateWaitSession::Instance());
@@ -356,15 +356,54 @@ ClientStateWaitSession::Process(ClientThread &client)
boost::shared_ptr<NetPacket> tmpPacket = client.GetReceiver().Recv(context.GetSocket());
if (tmpPacket.get())
if (tmpPacket.get() && tmpPacket->ToNetPacketInitAck())
{
client.SetState(ClientStateFinal::Instance());
client.SetState(ClientStateWaitGame::Instance());
retVal = MSG_SOCK_SESSION_DONE;
}
else
else // TODO: handle error packet
{
retVal = MSG_SOCK_INTERNAL_PENDING;
}
return retVal;
}
//-----------------------------------------------------------------------------
ClientStateWaitGame &
ClientStateWaitGame::Instance()
{
static ClientStateWaitGame state;
return state;
}
ClientStateWaitGame::ClientStateWaitGame()
{
}
ClientStateWaitGame::~ClientStateWaitGame()
{
}
int
ClientStateWaitGame::Process(ClientThread &client)
{
int retVal;
ClientContext &context = client.GetContext();
// delegate to receiver helper class
boost::shared_ptr<NetPacket> tmpPacket = client.GetReceiver().Recv(context.GetSocket());
if (tmpPacket.get() && tmpPacket->ToNetPacketGameStart())
{
client.SetState(ClientStateFinal::Instance());
retVal = MSG_SOCK_GAME_START;
}
else // TODO: handle error packet
{
retVal = MSG_SOCK_INTERNAL_PENDING;
Thread::Msleep(CLIENT_WAIT_TIMEOUT_MSEC);
}
return retVal;
+8 -4
View File
@@ -22,7 +22,6 @@
#include <net/clientcontext.h>
#include <net/senderthread.h>
#include <net/receiverhelper.h>
#include <net/clientcallback.h>
#include <net/clientexception.h>
#include <net/socket_msg.h>
@@ -43,7 +42,7 @@ public:
// For now, we ignore the socket.
// Just signal the error.
// We assume that the client thread will be terminated.
m_client.GetCallback().SignalNetError(errorID, osErrorID);
m_client.GetCallback().SignalNetClientError(errorID, osErrorID);
}
private:
@@ -97,11 +96,16 @@ ClientThread::Main()
{
int msg = GetState().Process(*this);
if (msg != MSG_SOCK_INTERNAL_PENDING)
GetCallback().SignalNetSuccess(msg);
{
if (msg <= MSG_SOCK_LIMIT_CONNECT)
GetCallback().SignalNetClientConnect(msg);
else
GetCallback().SignalNetClientGameInfo(msg);
}
}
} catch (const NetException &e)
{
GetCallback().SignalNetError(e.GetErrorId(), e.GetOsErrorCode());
GetCallback().SignalNetClientError(e.GetErrorId(), e.GetOsErrorCode());
}
GetSender().SignalTermination();
GetSender().Join(SENDER_THREAD_TERMINATE_TIMEOUT);
+8 -1
View File
@@ -22,7 +22,6 @@
ConnectData::ConnectData()
: m_sockfd(INVALID_SOCKET)
{
bzero(&m_sockaddr, sizeof(m_sockaddr));
}
ConnectData::~ConnectData()
@@ -31,3 +30,11 @@ ConnectData::~ConnectData()
CLOSESOCKET(m_sockfd);
}
SOCKET
ConnectData::ReleaseSocket()
{
SOCKET tmpSock = m_sockfd;
m_sockfd = INVALID_SOCKET;
return tmpSock;
}
+147 -15
View File
@@ -25,35 +25,61 @@ NetPacket::~NetPacket()
{
}
//-----------------------------------------------------------------------------
TestNetPacket::TestNetPacket()
const NetPacketInit *
NetPacket::ToNetPacketInit() const
{
return NULL;
}
TestNetPacket::TestNetPacket(u_int32_t value)
const NetPacketInitAck *
NetPacket::ToNetPacketInitAck() const
{
m_data.head.type = htons(NET_TYPE_TEST);
m_data.head.length = htons(sizeof(m_data));
return NULL;
}
const NetPacketGameStart *
NetPacket::ToNetPacketGameStart() const
{
return NULL;
}
//-----------------------------------------------------------------------------
NetPacketInit::NetPacketInit()
{
Init();
}
NetPacketInit::NetPacketInit(u_int32_t value)
{
Init();
m_data.test = htonl(value);
}
TestNetPacket::~TestNetPacket()
NetPacketInit::~NetPacketInit()
{
}
NetPacketHeader *
TestNetPacket::GetData()
{
return (NetPacketHeader *)&m_data;
}
void
TestNetPacket::SetData(const NetPacketHeader *p)
NetPacketInit::Init()
{
m_data.head.type = htons(NET_TYPE_INIT);
m_data.head.length = htons(sizeof(m_data));
m_data.test = htonl(0);
}
const NetPacketHeader *
NetPacketInit::GetData() const
{
return (const NetPacketHeader *)&m_data;
}
void
NetPacketInit::SetData(const NetPacketHeader *p)
{
u_int16_t tmpLen = ntohs(p->length);
if (tmpLen != sizeof(m_data)
|| ntohs(p->type) != NET_TYPE_TEST)
|| ntohs(p->type) != NET_TYPE_INIT)
{
throw NetException(ERR_SOCK_INTERNAL, 0);
}
@@ -61,3 +87,109 @@ TestNetPacket::SetData(const NetPacketHeader *p)
memcpy(&m_data, p, tmpLen);
}
const NetPacketInit *
NetPacketInit::ToNetPacketInit() const
{
return this;
}
//-----------------------------------------------------------------------------
NetPacketInitAck::NetPacketInitAck()
{
Init();
}
NetPacketInitAck::NetPacketInitAck(u_int32_t value)
{
Init();
m_data.test = htonl(value);
}
NetPacketInitAck::~NetPacketInitAck()
{
}
void
NetPacketInitAck::Init()
{
m_data.head.type = htons(NET_TYPE_INIT_ACK);
m_data.head.length = htons(sizeof(m_data));
m_data.test = htonl(0);
}
const NetPacketHeader *
NetPacketInitAck::GetData() const
{
return (const NetPacketHeader *)&m_data;
}
void
NetPacketInitAck::SetData(const NetPacketHeader *p)
{
u_int16_t tmpLen = ntohs(p->length);
if (tmpLen != sizeof(m_data)
|| ntohs(p->type) != NET_TYPE_INIT_ACK)
{
throw NetException(ERR_SOCK_INTERNAL, 0);
}
memcpy(&m_data, p, tmpLen);
}
const NetPacketInitAck *
NetPacketInitAck::ToNetPacketInitAck() const
{
return this;
}
//-----------------------------------------------------------------------------
NetPacketGameStart::NetPacketGameStart()
{
Init();
}
NetPacketGameStart::NetPacketGameStart(u_int32_t value)
{
Init();
m_data.test = htonl(value);
}
NetPacketGameStart::~NetPacketGameStart()
{
}
void
NetPacketGameStart::Init()
{
m_data.head.type = htons(NET_TYPE_GAME_START);
m_data.head.length = htons(sizeof(m_data));
m_data.test = htonl(0);
}
const NetPacketHeader *
NetPacketGameStart::GetData() const
{
return (NetPacketHeader *)&m_data;
}
void
NetPacketGameStart::SetData(const NetPacketHeader *p)
{
u_int16_t tmpLen = ntohs(p->length);
if (tmpLen != sizeof(m_data)
|| ntohs(p->type) != NET_TYPE_GAME_START)
{
throw NetException(ERR_SOCK_INTERNAL, 0);
}
memcpy(&m_data, p, tmpLen);
}
const NetPacketGameStart *
NetPacketGameStart::ToNetPacketGameStart() const
{
return this;
}
+18 -12
View File
@@ -24,7 +24,6 @@
using namespace std;
#define RECV_TIMEOUT_MSEC 50
ReceiverHelper::ReceiverHelper()
: m_socket(INVALID_SOCKET), m_tmpInBufSize(0)
@@ -124,18 +123,25 @@ ReceiverHelper::InternalCreateNetPacket(const NetPacketHeader *p)
{
boost::shared_ptr<NetPacket> tmpPacket;
switch(ntohs(p->type))
try
{
case NET_TYPE_TEST:
try
{
tmpPacket = boost::shared_ptr<NetPacket>(new TestNetPacket);
tmpPacket->SetData(p);
} catch (const NetException &)
{
tmpPacket.reset();
}
break;
switch(ntohs(p->type))
{
case NET_TYPE_INIT:
tmpPacket = boost::shared_ptr<NetPacket>(new NetPacketInit);
break;
case NET_TYPE_INIT_ACK:
tmpPacket = boost::shared_ptr<NetPacket>(new NetPacketInitAck);
break;
case NET_TYPE_GAME_START:
tmpPacket = boost::shared_ptr<NetPacket>(new NetPacketGameStart);
break;
}
if (tmpPacket.get())
tmpPacket->SetData(p);
} catch (const NetException &)
{
tmpPacket.reset();
}
return tmpPacket;
}
@@ -17,10 +17,10 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <net/netcallback.h>
#include <net/servercallback.h>
NetCallback::~NetCallback()
ServerCallback::~ServerCallback()
{
}
+69 -2
View File
@@ -19,6 +19,9 @@
#include <net/serverrecvstate.h>
#include <net/serverrecvthread.h>
#include <net/receiverhelper.h>
#include <net/senderthread.h>
#include <net/netpacket.h>
#include <net/socket_msg.h>
using namespace std;
@@ -48,14 +51,78 @@ ServerRecvStateInit::~ServerRecvStateInit()
}
void
ServerRecvStateInit::HandleNewConnection(ServerRecvThread &server, boost::shared_ptr<ConnectData> data)
ServerRecvStateInit::HandleNewConnection(ServerRecvThread &server, boost::shared_ptr<ConnectData> connData)
{
boost::shared_ptr<SessionData> sessionData(new SessionData);
server.AddSession(connData, sessionData);
}
int
ServerRecvStateInit::Process(ServerRecvThread &server)
{
Thread::Msleep(SERVER_WAIT_TIMEOUT_MSEC);
SOCKET recvSock = server.Select();
if (recvSock != INVALID_SOCKET)
{
boost::shared_ptr<NetPacket> packet = server.GetReceiver().Recv(recvSock);
boost::shared_ptr<SessionData> session = server.GetSession(recvSock);
// Ignore if no session / no packet.
if (packet.get() && session.get())
{
if (session->GetState() == SessionData::Init)
{
// Only accept init packets.
if (packet->ToNetPacketInit())
{
boost::shared_ptr<NetPacket> answer(new NetPacketInitAck);
server.GetSender().Send(answer, recvSock);
session->SetState(SessionData::Established);
}
else
{
// TODO send error message, invalid packet
}
}
else
{
// TODO send error message, invalid state
}
}
}
return MSG_SOCK_INIT_DONE;
}
//-----------------------------------------------------------------------------
ServerRecvStateStartGame &
ServerRecvStateStartGame::Instance()
{
static ServerRecvStateStartGame state;
return state;
}
ServerRecvStateStartGame::ServerRecvStateStartGame()
{
}
ServerRecvStateStartGame::~ServerRecvStateStartGame()
{
}
void
ServerRecvStateStartGame::HandleNewConnection(ServerRecvThread &server, boost::shared_ptr<ConnectData> connData)
{
// TODO: send error msg
}
int
ServerRecvStateStartGame::Process(ServerRecvThread &server)
{
boost::shared_ptr<NetPacket> answer(new NetPacketGameStart);
server.SendToAllClients(answer);
return MSG_SOCK_INIT_DONE;
}
+110
View File
@@ -50,6 +50,28 @@ ServerRecvThread::~ServerRecvThread()
{
}
void
ServerRecvThread::StartGame()
{
// TODO: not thread safe. use flag or something.
SetState(SERVER_START_GAME_STATE::Instance());
}
void
ServerRecvThread::SendToAllClients(boost::shared_ptr<NetPacket> packet)
{
// TODO: possible race condition if used in multithreading
SocketSessionMap::iterator i = m_sessions.begin();
SocketSessionMap::iterator end = m_sessions.end();
while (i != end)
{
// TODO: desparately need clone here, this is very dangerous.
GetSender().Send(packet, i->first);
++i;
}
}
void
ServerRecvThread::AddConnection(boost::shared_ptr<ConnectData> data)
{
@@ -91,6 +113,67 @@ ServerRecvThread::Main()
}
GetSender().SignalTermination();
GetSender().Join(SENDER_THREAD_TERMINATE_TIMEOUT);
// TODO: clear connection queue
}
SOCKET
ServerRecvThread::Select()
{
SOCKET retSock = INVALID_SOCKET;
if (m_sessions.empty())
{
Msleep(RECV_TIMEOUT_MSEC); // just sleep if there is no session
}
else
{
// wait for data
SOCKET maxSock = 0;
fd_set rdset;
FD_ZERO(&rdset);
{
SocketSessionMap::iterator i = m_sessions.begin();
SocketSessionMap::iterator end = m_sessions.end();
while (i != end)
{
SOCKET tmpSock = i->first;
FD_SET(tmpSock, &rdset);
if (tmpSock > maxSock)
maxSock = tmpSock;
++i;
}
}
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = RECV_TIMEOUT_MSEC * 1000;
int selectResult = select(maxSock + 1, &rdset, NULL, NULL, &timeout);
if (!IS_VALID_SELECT(selectResult))
{
throw ServerException(ERR_SOCK_SELECT_FAILED, SOCKET_ERRNO());
}
if (selectResult > 0) // one (or more) of the sockets is readable
{
// Check which socket is readable, return the first.
SocketSessionMap::iterator i = m_sessions.begin();
SocketSessionMap::iterator end = m_sessions.end();
while (i != end)
{
SOCKET tmpSock = i->first;
if (FD_ISSET(tmpSock, &rdset))
{
retSock = tmpSock;
break;
}
++i;
}
}
}
return retSock;
}
ServerRecvState &
@@ -106,6 +189,33 @@ ServerRecvThread::SetState(ServerRecvState &newState)
m_curState = &newState;
}
boost::shared_ptr<SessionData>
ServerRecvThread::GetSession(SOCKET sock)
{
boost::shared_ptr<SessionData> tmpSession;
SocketSessionMap::iterator pos = m_sessions.find(sock);
if (pos != m_sessions.end())
{
tmpSession = pos->second;
}
return tmpSession;
}
void
ServerRecvThread::AddSession(boost::shared_ptr<ConnectData> connData, boost::shared_ptr<SessionData> sessionData)
{
SocketSessionMap::iterator pos = m_sessions.lower_bound(connData->GetSocket());
// If pos points to a pair whose key is equivalent to the socket, this handle
// already exists within the list.
if (pos != m_sessions.end() && connData->GetSocket() == pos->first)
{
throw ServerException(ERR_SOCK_CONN_EXISTS, 0);
}
m_sessions.insert(pos, SocketSessionMap::value_type(connData->ReleaseSocket(), sessionData));
}
SenderThread &
ServerRecvThread::GetSender()
{
+21 -5
View File
@@ -29,7 +29,8 @@
#define NET_SERVER_LISTEN_BACKLOG 5
ServerThread::ServerThread()
ServerThread::ServerThread(ServerCallback &cb)
: m_callback(cb)
{
m_context.reset(new ServerContext);
}
@@ -51,6 +52,22 @@ ServerThread::Init(unsigned serverPort, bool ipv6, const std::string &pwd)
context.SetPassword(pwd);
}
void
ServerThread::StartGame()
{
if (!IsRunning())
return; // TODO: throw exception
// TODO: possible race condition
GetRecvThread().StartGame();
}
ServerCallback &
ServerThread::GetCallback()
{
return m_callback;
}
void
ServerThread::Main()
{
@@ -65,9 +82,9 @@ ServerThread::Main()
// The main server thread is simple. It only accepts connections.
AcceptLoop();
}
} catch (const NetException &)
} catch (const NetException &e)
{
// TODO: callback.
GetCallback().SignalNetServerError(e.GetErrorId(), e.GetOsErrorCode());
}
}
@@ -141,8 +158,7 @@ ServerThread::AcceptLoop()
if (selectResult > 0) // accept is possible
{
boost::shared_ptr<ConnectData> tmpData(new ConnectData);
socklen_t addrSize = sizeof(*tmpData->GetSockaddr());
tmpData->SetSocket(accept(context.GetSocket(), (struct sockaddr *)tmpData->GetSockaddr(), &addrSize));
tmpData->SetSocket(accept(context.GetSocket(), NULL, NULL));
if (!IS_VALID_SOCKET(tmpData->GetSocket()))
{
+30
View File
@@ -0,0 +1,30 @@
/***************************************************************************
* Copyright (C) 2007 by Lothar May *
* *
* 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 <net/sessiondata.h>
SessionData::SessionData()
: m_id(SESSION_ID_INIT), m_state(SessionData::Init)
{
}
SessionData::~SessionData()
{
}