Added LAN server discovery.

This commit is contained in:
hiker 2015-11-09 20:51:00 +11:00
parent 9d9b83059d
commit 453ce8cf60
16 changed files with 232 additions and 128 deletions

View File

@ -103,113 +103,59 @@ void Network::sendRawPacket(uint8_t* data, int length,
false);
} // sendRawPacket
// ----------------------------------------------------------------------------
/** \brief Receives a packet directly from the network interface.
* Receive a packet whithout ENet processing it and returns the
* sender's ip address and port in the TransportAddress structure.
* \param sender : Stores the transport address of the sender of the
* received packet.
* \return A string containing the data of the received packet.
*/
uint8_t* Network::receiveRawPacket(TransportAddress* sender)
{
const int LEN = 2048;
// max size needed normally (only used for stun)
uint8_t* buffer = new uint8_t[LEN];
memset(buffer, 0, LEN);
socklen_t from_len;
struct sockaddr_in addr;
from_len = sizeof(addr);
int len = recvfrom(m_host->socket, (char*)buffer, LEN, 0,
(struct sockaddr*)(&addr), &from_len );
int i = 0;
// wait to receive the message because enet sockets are non-blocking
while(len == -1) // nothing received
{
StkTime::sleep(1); // wait 1 millisecond between two checks
i++;
len = recvfrom(m_host->socket, (char*)buffer, LEN, 0,
(struct sockaddr*)(&addr), &from_len );
}
if (len == SOCKET_ERROR)
{
Log::error("STKHost",
"Problem with the socket. Please contact the dev team.");
}
// we received the data
sender->setIP( ntohl((uint32_t)(addr.sin_addr.s_addr)) );
sender->setPort( ntohs(addr.sin_port) );
if (addr.sin_family == AF_INET)
{
Log::info("STKHost", "IPv4 Address of the sender was %s",
sender->toString().c_str());
}
return buffer;
} // receiveRawPacket(TransportAddress* sender)
// ----------------------------------------------------------------------------
/** \brief Receives a packet directly from the network interface and
* filter its address.
* Receive a packet whithout ENet processing it. Checks that the
* sender of the packet is the one that corresponds to the sender
* parameter. Does not check the port right now.
* \param sender : Transport address of the original sender of the
* wanted packet.
* \param buffer A buffer to receive the data in.
* \param buf_len Length of the buffer.
* \param[out] sender : Transport address of the original sender of the
* wanted packet. If the ip address is 0, do not check
* the sender's ip address, otherwise wait till a message
* from the specified sender arrives. All other messages
* are discarded.
* \param max_tries : Number of times we try to read data from the
* socket. This is aproximately the time we wait in
* milliseconds. -1 means eternal tries.
* \return A string containing the data of the received packet
* matching the sender's ip address.
* \return Length of the received data, or -1 if no data was received.
*/
uint8_t* Network::receiveRawPacket(const TransportAddress& sender,
int max_tries)
int Network::receiveRawPacket(char *buffer, int buf_len,
TransportAddress *sender, int max_tries)
{
const int LEN = 2048;
uint8_t* buffer = new uint8_t[LEN];
memset(buffer, 0, LEN);
memset(buffer, 0, buf_len);
socklen_t from_len;
struct sockaddr_in addr;
socklen_t from_len = sizeof(addr);
from_len = sizeof(addr);
int len = recvfrom(m_host->socket, (char*)buffer, LEN, 0,
int len = recvfrom(m_host->socket, buffer, buf_len, 0,
(struct sockaddr*)(&addr), &from_len );
int count = 0;
// wait to receive the message because enet sockets are non-blocking
while(len < 0 || addr.sin_addr.s_addr == sender.getIP())
// wait to receive the message because enet sockets are non-blocking
while(len < 0 && (count<max_tries || max_tries==-1) )
{
count++;
if (len>=0)
{
Log::info("STKHost", "Message received but the ip address didn't "
"match the expected one.");
}
len = recvfrom(m_host->socket, (char*)buffer, LEN, 0,
(struct sockaddr*)(&addr), &from_len);
StkTime::sleep(1); // wait 1 millisecond between two checks
if (count >= max_tries && max_tries != -1)
{
TransportAddress a(m_host->address);
Log::verbose("STKHost", "No answer from the server on %s",
a.toString().c_str());
delete [] buffer;
return NULL;
}
len = recvfrom(m_host->socket, buffer, buf_len, 0,
(struct sockaddr*)(&addr), &from_len);
}
// No message received
if(len<0)
return -1;
Network::logPacket(NetworkString(std::string(buffer, len)), true);
sender->setIP(ntohl((uint32_t)(addr.sin_addr.s_addr)) );
sender->setPort( ntohs(addr.sin_port) );
if (addr.sin_family == AF_INET)
{
TransportAddress a(ntohl(addr.sin_addr.s_addr));
Log::info("STKHost", "IPv4 Address of the sender was %s",
a.toString(false).c_str());
Log::info("Network", "IPv4 Address of the sender was %s",
sender->toString().c_str());
}
Network::logPacket(NetworkString(std::string((char*)(buffer), len)), true);
return buffer;
} // receiveRawPacket(const TransportAddress& sender, int max_tries)
return len;
} // receiveRawPacket
// ----------------------------------------------------------------------------
/** \brief Broadcasts a packet to all peers.

View File

@ -62,9 +62,8 @@ public:
ENetPeer *connectTo(const TransportAddress &address);
void sendRawPacket(uint8_t* data, int length,
const TransportAddress& dst);
uint8_t* receiveRawPacket(TransportAddress* sender);
uint8_t* receiveRawPacket(const TransportAddress& sender,
int max_tries = -1);
int receiveRawPacket(char *buffer, int buf_len,
TransportAddress* sender, int max_tries = -1);
void broadcastPacket(const NetworkString& data,
bool reliable = true);
// ------------------------------------------------------------------------

View File

@ -77,6 +77,12 @@ public:
m_string = std::vector<uint8_t>(value.begin(), value.end());
} // NetworkString
// ------------------------------------------------------------------------
NetworkString(const char *p, int len)
{
m_string.resize(len);
memcpy(m_string.data(), p, len);
} // NetworkString(char*, int)
// ------------------------------------------------------------------------
NetworkString& add(const std::string &s)
{

View File

@ -473,7 +473,7 @@ void ProtocolManager::asynchronousUpdate()
}
pthread_mutex_unlock(&m_asynchronous_protocols_mutex);
// process queued events for protocols
// Process queued events for protocols
// these requests are asynchronous
pthread_mutex_lock(&m_requests_mutex);
while(m_requests.size()>0)

View File

@ -129,7 +129,7 @@ void ConnectToPeer::asynchronousUpdate()
m_state = CONNECTING;
break;
}
case CONNECTING: // waiting the peer to connect
case CONNECTING: // waiting for the peer to connect
break;
case CONNECTED:
{

View File

@ -357,11 +357,13 @@ void ConnectToServer::handleSameLAN()
TransportAddress sender;
// get the sender
const uint8_t* received_data = host->receiveRawPacket(&sender);
const int LEN=256;
char buffer[LEN];
int len = host->receiveRawPacket(buffer, LEN, &sender, 2000);
host->startListening(); // start listening again
const char data[] = "aloha_stk\0";
if (strcmp(data, (char*)(received_data)) == 0)
if (strcmp(data, buffer) == 0)
{
Log::info("ConnectToServer", "LAN Server found : %s",
sender.toString().c_str());

View File

@ -122,21 +122,24 @@ void GetPublicAddress::createStunRequest()
*/
std::string GetPublicAddress::parseStunResponse()
{
uint8_t* s = m_transaction_host
->receiveRawPacket(TransportAddress(m_stun_server_ip,
m_stun_server_port), 2000);
TransportAddress sender;
const int LEN = 2048;
char buffer[LEN];
int len = m_transaction_host->receiveRawPacket(buffer, LEN, &sender, 2000);
if (!s)
if(sender.getIP()!=m_stun_server_ip)
{
TransportAddress stun(m_stun_server_ip, m_stun_server_port);
Log::warn("GetPublicAddress",
"Received stun response from %s instead of %s.",
sender.toString().c_str(), stun.toString().c_str());
}
if (len<0)
return "STUN response contains no data at all";
// Convert to network string.
// FIXME: the length is not known (atm 2048 bytes are allocated in
// receiveRawPacket, and it looks like 32 are actually used in a normal
// stun reply
NetworkString datas(std::string((char*)s, 32));
// The received data has been copied and can now be deleted
delete s;
NetworkString datas(std::string(buffer, len));
// check that the stun response is a response, contains the magic cookie
// and the transaction ID

View File

@ -57,7 +57,10 @@ void ServerLobbyRoomProtocol::setup()
{
m_setup = STKHost::get()->setupNewGame();
m_next_id = 0;
m_state = NONE;
// In case of LAN we don't need our public address or register with the STK
// server, so we can directly go to the working state.
m_state = STKHost::isLAN() ? WORKING : NONE;
m_selection_enabled = false;
m_in_race = false;
m_current_protocol = NULL;

View File

@ -21,10 +21,11 @@
#include "config/user_config.hpp"
#include "io/file_manager.hpp"
#include "network/event.hpp"
#include "network/network_console.hpp"
#include "network/network_string.hpp"
#include "network/protocols/connect_to_server.hpp"
#include "network/protocols/server_lobby_room_protocol.hpp"
#include "network/protocol_manager.hpp"
#include "network/network_console.hpp"
#include "network/stk_peer.hpp"
#include "utils/log.hpp"
#include "utils/time.hpp"
@ -101,7 +102,7 @@ STKHost::NetworkType STKHost::m_network_type = STKHost::NETWORK_NONE;
* randomly), and then instantiates STKHost with the id of this server.
* STKHost then triggers:
* 1) ConnectToServer, which starts the following protocols:
* a) GetPublicAccess: Use STUN to discover the public ip address
* a) GetPublicAddress: Use STUN to discover the public ip address
* and port number of this host.
* b) Register the client with the STK host ('set' command, into the
* table 'client_sessions'). Its public ip address and port will
@ -157,6 +158,7 @@ STKHost::STKHost(const irr::core::stringw &server_name)
{
m_is_server = true;
init();
m_server_name = server_name;
ENetAddress addr;
addr.host = STKHost::HOST_ANY;
@ -408,8 +410,42 @@ void* STKHost::mainLoop(void* self)
ENetEvent event;
STKHost* myself = (STKHost*)(self);
ENetHost* host = myself->m_network->getENetHost();
// Separate network/socket connection in case of LAN server
// listening to broadcast.
Network *discovery_host = NULL;
if(myself->isServer() && STKHost::isLAN())
{
TransportAddress address(0, 2757);
ENetAddress eaddr = address.toEnetAddress();
discovery_host = new Network(1, 1, 0, 0, &eaddr);
}
const int LEN=2048;
char buffer[LEN];
while (!myself->mustStopListening())
{
if(discovery_host)
{
TransportAddress sender;
int len= discovery_host->receiveRawPacket(buffer, LEN, &sender, 1);
if(len>0 && std::string(buffer, len)=="stk-server")
{
Log::verbose("STKHost", "Received LAN server query");
std::string &name = StringUtils::wide_to_utf8(
myself->get()->getServerName().c_str());
if(name.size()>2)
name = name.substr(0, 255);
NetworkString s;
s.addUInt8((uint8_t)name.size());
s.addString(name.c_str());
s.addUInt8(getMaxPlayers());
s.addUInt8(1); // FIXME: current number of connected players
discovery_host->sendRawPacket(s.getBytes(), s.size(), sender);
} // if message is server-requested
} // if discovery host
while (enet_host_service(host, &event, 20) != 0)
{
if (event.type == ENET_EVENT_TYPE_NONE)

View File

@ -186,8 +186,6 @@ public:
uint32_t max_outgoing_bandwidth);
void startListening();
void stopListening();
uint8_t* receiveRawPacket(const TransportAddress& sender,
int max_tries = -1);
bool peerExists(const TransportAddress& peer_address);
void removePeer(const STKPeer* peer);
bool isConnectedTo(const TransportAddress& peer_address);
@ -201,9 +199,11 @@ public:
/** Returns the current game setup. */
GameSetup* getGameSetup() { return m_game_setup; }
// --------------------------------------------------------------------
uint8_t* receiveRawPacket(TransportAddress* sender)
int receiveRawPacket(char *buffer, int buffer_len,
TransportAddress* sender, int max_tries = -1)
{
return m_network->receiveRawPacket(sender);
return m_network->receiveRawPacket(buffer, buffer_len, sender,
max_tries);
} // receiveRawPacket
// --------------------------------------------------------------------

View File

@ -18,6 +18,7 @@
#include "online/servers_manager.hpp"
#include "config/user_config.hpp"
#include "network/stk_host.hpp"
#include "utils/translation.hpp"
#include "utils/time.hpp"
@ -73,7 +74,121 @@ void ServersManager::cleanUpServers()
} // cleanUpServers
// ------------------------------------------------------------------------
XMLRequest* ServersManager::refreshRequest(bool request_now) const
/** Returns a WAN update-list-of-servers request. It queries the
* STK server for an up-to-date list of servers.
*/
XMLRequest* ServersManager::getWANRefreshRequest() const
{
// ========================================================================
/** A small local class that triggers an update of the ServersManager
* when the request is finished. */
class WANRefreshRequest : public XMLRequest
{
public:
WANRefreshRequest() : XMLRequest(/*manage_memory*/false,
/*priority*/100) {}
// --------------------------------------------------------------------
virtual void callback()
{
ServersManager::get()->refresh(isSuccess(), getXMLData());
} // callback
// --------------------------------------------------------------------
}; // RefreshRequest
// ========================================================================
XMLRequest* request = new WANRefreshRequest();
request->setApiURL(API::SERVER_PATH, "get-all");
return request;
} // getWANRefreshRequest
// ------------------------------------------------------------------------
/** Returns a LAN update-list-of-servers request. It uses UDP broadcasts
* to find LAN servers, and waits for a certain amount of time fr
* answers.
*/
XMLRequest* ServersManager::getLANRefreshRequest() const
{
/** A simple class that uses LAN broadcasts to find local servers.
* It is based on XML request, but actually does not use any of the
* XML/HTTP based infrastructure, but implements the same interface.
* This way the already existing request thread can be used.
*/
class LANRefreshRequest : public XMLRequest
{
public:
/** High priority for this request. */
LANRefreshRequest() : XMLRequest(false, 100) {m_success = false;}
// --------------------------------------------------------------------
virtual ~LANRefreshRequest() {}
// --------------------------------------------------------------------
/** Get the downloaded XML tree.
* \pre request has to be executed.
* \return get the complete result from the request reply. */
virtual const XMLNode * getXMLData() const
{
assert(hasBeenExecuted());
return NULL;
} // getXMLData
// --------------------------------------------------------------------
virtual void prepareOperation() OVERRIDE
{
} // prepareOperation
// --------------------------------------------------------------------
virtual void operation()
{
Network *broadcast = new Network(1, 1, 0, 0);
NetworkString s(std::string("stk-server"));
TransportAddress broadcast_address(-1, 2757);
broadcast->sendRawPacket(s.getBytes(), s.size(), broadcast_address);
Log::info("ServersManager", "Sent broadcast message.");
const int LEN=2048;
char buffer[LEN];
// Wait for up to 0.5 seconds to receive an answer from
// any local servers.
double start_time = StkTime::getRealTime();
const double DURATION = 1.0;
while(StkTime::getRealTime() - start_time < DURATION)
{
TransportAddress sender;
int len = broadcast->receiveRawPacket(buffer, LEN, &sender, 1);
if(len>0)
{
NetworkString s(buffer, len);
uint8_t name_len = s.getUInt8(0);
std::string name = s.getString(1, name_len);
irr::core::stringw name_w =
StringUtils::utf8_to_wide(name.c_str());
uint8_t max_players = s.getUInt8(1+name_len );
uint8_t players = s.getUInt8(1+name.size()+1);
ServersManager::get()
->addServer(new Server(name_w, /*lan*/true,
max_players, players));
m_success = true;
} // if received_data
} // while still waiting
} // operation
// --------------------------------------------------------------------
/** This function is necessary, otherwise the XML- and HTTP-Request
* functions are called, which will cause a crash. */
virtual void afterOperation() OVERRIDE {}
// --------------------------------------------------------------------
}; // LANRefreshRequest
// ========================================================================
return new LANRefreshRequest();
} // getLANRefreshRequest
// ------------------------------------------------------------------------
/** Factory function to create either a LAN or a WAN update-of-server
* requests. The current list of servers is also cleared/
*/
XMLRequest* ServersManager::getRefreshRequest(bool request_now)
{
if (StkTime::getRealTime() - m_last_load_time.getAtomic()
< SERVER_REFRESH_INTERVAL)
@ -82,18 +197,9 @@ XMLRequest* ServersManager::refreshRequest(bool request_now) const
return NULL;
}
// ====================================================================
class RefreshRequest : public XMLRequest
{
virtual void callback()
{
ServersManager::get()->refresh(isSuccess(), getXMLData());
} // callback
}; // RefreshRequest
// ====================================================================
RefreshRequest* request = new RefreshRequest();
request->setApiURL(API::SERVER_PATH, "get-all");
cleanUpServers();
XMLRequest *request = STKHost::isWAN() ? getWANRefreshRequest()
: getLANRefreshRequest();
if (request_now)
RequestManager::get()->addRequest(request);
@ -115,7 +221,6 @@ void ServersManager::refresh(bool success, const XMLNode *input)
}
const XMLNode *servers_xml = input->getNode("servers");
cleanUpServers();
for (unsigned int i = 0; i < servers_xml->getNumNodes(); i++)
{
addServer(new Server(*servers_xml->getNode(i), /*is_lan*/false));

View File

@ -50,13 +50,15 @@ private:
Synchronised<float> m_last_load_time;
void refresh(bool success, const XMLNode * input);
void cleanUpServers();
XMLRequest * getWANRefreshRequest() const;
XMLRequest * getLANRefreshRequest() const;
public:
// Singleton
static ServersManager* get();
static void deallocate();
XMLRequest * refreshRequest(bool request_now = true) const;
XMLRequest * getRefreshRequest(bool request_now = true);
void setJoinedServer(uint32_t server_id);
void unsetJoinedServer();
void addServer(Server * server);

View File

@ -46,10 +46,10 @@ namespace Online
* message if a problem occurred). */
irr::core::stringw m_info;
protected:
/** True if the request was successful executed on the server. */
bool m_success;
protected:
virtual void afterOperation() OVERRIDE;
public :

View File

@ -144,6 +144,9 @@ void CreateServerScreen::onUpdate(float delta)
}
//FIXME If we really want a gui, we need to decide what else to do here
// For now start the (wrong i.e. client) lobby, to prevent to create
// a server more than once.
NetworkingLobby::getInstance()->push();
} // onUpdate
// ----------------------------------------------------------------------------
@ -174,11 +177,10 @@ void CreateServerScreen::createServer()
// In case of a LAN game, we can create the new server object now
if (STKHost::isLAN())
{
// FIXME Is this actually necessary?? Only in case of WAN, or LAN and WAN?
Server *server = new Server(name, /*lan*/true, max_players,
/*current_player*/1);
ServersManager::get()->addServer(server);
new ServerInfoDialog(server->getServerId(), true);
return;
}
// In case of a WAN game, we register this server with the

View File

@ -183,7 +183,7 @@ void OnlineScreen::onUpdate(float delta)
void OnlineScreen::doQuickPlay()
{
// Refresh server list.
HTTPRequest* refresh_request = ServersManager::get()->refreshRequest(false);
HTTPRequest* refresh_request = ServersManager::get()->getRefreshRequest(false);
if (refresh_request != NULL) // consider request done
{
refresh_request->executeNow();

View File

@ -62,7 +62,7 @@ void ServerSelection::tearDown()
*/
void ServerSelection::refresh()
{
m_refresh_request = ServersManager::get()->refreshRequest();
m_refresh_request = ServersManager::get()->getRefreshRequest();
// If the request was created (i.e. no error, and not re-requested within
// 5 seconds), clear the list and display the waiting message:
if(m_refresh_request)