(svn r20938) -Codechange: make the code for listening on a socket (more) reusable

This commit is contained in:
rubidium 2010-10-15 21:56:06 +00:00
parent a0f7099a7d
commit b6799a23c4
9 changed files with 249 additions and 144 deletions

View File

@ -1041,6 +1041,7 @@
<ClInclude Include="..\src\network\core\tcp_game.h" />
<ClCompile Include="..\src\network\core\tcp_http.cpp" />
<ClInclude Include="..\src\network\core\tcp_http.h" />
<ClInclude Include="..\src\network\core\tcp_listen.h" />
<ClCompile Include="..\src\network\core\udp.cpp" />
<ClInclude Include="..\src\network\core\udp.h" />
<ClInclude Include="..\src\pathfinder\follow_track.hpp" />

View File

@ -2343,6 +2343,9 @@
<ClInclude Include="..\src\network\core\tcp_http.h">
<Filter>Network Core</Filter>
</ClInclude>
<ClInclude Include="..\src\network\core\tcp_listen.h">
<Filter>Network Core</Filter>
</ClInclude>
<ClCompile Include="..\src\network\core\udp.cpp">
<Filter>Network Core</Filter>
</ClCompile>

View File

@ -3518,6 +3518,10 @@
RelativePath=".\..\src\network\core\tcp_http.h"
>
</File>
<File
RelativePath=".\..\src\network\core\tcp_listen.h"
>
</File>
<File
RelativePath=".\..\src\network\core\udp.cpp"
>

View File

@ -3515,6 +3515,10 @@
RelativePath=".\..\src\network\core\tcp_http.h"
>
</File>
<File
RelativePath=".\..\src\network\core\tcp_listen.h"
>
</File>
<File
RelativePath=".\..\src\network\core\udp.cpp"
>

View File

@ -836,6 +836,7 @@ network/core/tcp_game.cpp
network/core/tcp_game.h
network/core/tcp_http.cpp
network/core/tcp_http.h
network/core/tcp_listen.h
network/core/udp.cpp
network/core/udp.h

View File

@ -0,0 +1,178 @@
/* $Id: tcp.h 20933 2010-10-15 19:33:08Z rubidium $ */
/*
* This file is part of OpenTTD.
* OpenTTD 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, version 2.
* OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file tcp.h Basic functions to listen for TCP connections.
*/
#ifndef NETWORK_CORE_TCP_LISTEN_H
#define NETWORK_CORE_TCP_LISTEN_H
#include "tcp.h"
#include "../network.h"
#include "../../core/pool_type.hpp"
#include "../../debug.h"
#include "table/strings.h"
#ifdef ENABLE_NETWORK
/**
* Template for TCP listeners.
* @param Tsocket The class we create sockets for.
* @param Tfull_packet The packet type to return when we don't allow more sockets.
* @param Tban_packet The packet type to return when the client is banned.
*/
template <class Tsocket, PacketType Tfull_packet, PacketType Tban_packet>
class TCPListenHandler {
/** List of sockets we listen on. */
static SocketList sockets;
public:
/**
* Accepts clients from the sockets.
* @param ls Socket to accept clients from.
*/
static void AcceptClient(SOCKET ls)
{
for (;;) {
struct sockaddr_storage sin;
memset(&sin, 0, sizeof(sin));
socklen_t sin_len = sizeof(sin);
SOCKET s = accept(ls, (struct sockaddr*)&sin, &sin_len);
if (s == INVALID_SOCKET) return;
SetNonBlocking(s); // XXX error handling?
NetworkAddress address(sin, sin_len);
DEBUG(net, 1, "[%s] Client connected from %s on frame %d", Tsocket::GetName(), address.GetHostname(), _frame_counter);
SetNoDelay(s); // XXX error handling?
/* Check if the client is banned */
bool banned = false;
for (char **iter = _network_ban_list.Begin(); iter != _network_ban_list.End(); iter++) {
banned = address.IsInNetmask(*iter);
if (banned) {
Packet p(Tban_packet);
p.PrepareToSend();
DEBUG(net, 1, "[%s] Banned ip tried to join (%s), refused", Tsocket::GetName(), *iter);
send(s, (const char*)p.buffer, p.size, 0);
closesocket(s);
break;
}
}
/* If this client is banned, continue with next client */
if (banned) continue;
/* Can we handle a new client? */
if (!Tsocket::AllowConnection()) {
/* no more clients allowed?
* Send to the client that we are full! */
Packet p(Tfull_packet);
p.PrepareToSend();
send(s, (const char*)p.buffer, p.size, 0);
closesocket(s);
continue;
}
Tsocket::AcceptConnection(s, address);
}
}
/**
* Handle the receiving of packets.
* @return true if everything went okay.
*/
static bool Receive()
{
fd_set read_fd, write_fd;
struct timeval tv;
FD_ZERO(&read_fd);
FD_ZERO(&write_fd);
Tsocket *cs;
FOR_ALL_ITEMS_FROM(Tsocket, idx, cs, 0) {
FD_SET(cs->sock, &read_fd);
FD_SET(cs->sock, &write_fd);
}
/* take care of listener port */
for (SocketList::iterator s = sockets.Begin(); s != sockets.End(); s++) {
FD_SET(s->second, &read_fd);
}
tv.tv_sec = tv.tv_usec = 0; // don't block at all.
#if !defined(__MORPHOS__) && !defined(__AMIGA__)
select(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv);
#else
WaitSelect(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv, NULL);
#endif
/* accept clients.. */
for (SocketList::iterator s = sockets.Begin(); s != sockets.End(); s++) {
if (FD_ISSET(s->second, &read_fd)) AcceptClient(s->second);
}
/* read stuff from clients */
FOR_ALL_ITEMS_FROM(Tsocket, idx, cs, 0) {
cs->writable = !!FD_ISSET(cs->sock, &write_fd);
if (FD_ISSET(cs->sock, &read_fd)) {
cs->Recv_Packets();
}
}
return _networking;
}
/**
* Listen on a particular port.
* @param port The port to listen on.
* @return true if listening succeeded.
*/
static bool Listen(uint16 port)
{
assert(sockets.Length() == 0);
NetworkAddressList addresses;
GetBindAddresses(&addresses, port);
for (NetworkAddress *address = addresses.Begin(); address != addresses.End(); address++) {
address->Listen(SOCK_STREAM, &sockets);
}
if (sockets.Length() == 0) {
DEBUG(net, 0, "[server] could not start network: could not create listening socket");
NetworkError(STR_NETWORK_ERROR_SERVER_START);
return false;
}
return true;
}
/** Close the sockets we're listening on. */
static void CloseListeners()
{
for (SocketList::iterator s = sockets.Begin(); s != sockets.End(); s++) {
closesocket(s->second);
}
sockets.Clear();
DEBUG(net, 1, "[%s] closed listeners", Tsocket::GetName());
}
};
template <class Tsocket, PacketType Tfull_packet, PacketType Tban_packet> SocketList TCPListenHandler<Tsocket, Tfull_packet, Tban_packet>::sockets;
#endif /* ENABLE_NETWORK */
#endif /* NETWORK_CORE_TCP_LISTEN_H */

View File

@ -87,9 +87,6 @@ extern NetworkUDPSocketHandler *_udp_client_socket; ///< udp client socket
extern NetworkUDPSocketHandler *_udp_server_socket; ///< udp server socket
extern NetworkUDPSocketHandler *_udp_master_socket; ///< udp master socket
/* The listen socket for the server */
static SocketList _listensockets;
/* The amount of clients connected */
byte _network_clients_connected = 0;
@ -234,12 +231,6 @@ void NetworkError(StringID error_string)
_switch_mode_errorstr = error_string;
}
static void ServerStartError(const char *error)
{
DEBUG(net, 0, "[server] could not start network: %s",error);
NetworkError(STR_NETWORK_ERROR_SERVER_START);
}
/**
* Retrieve the string id of an internal error number
* @param err NetworkErrorCode
@ -428,82 +419,14 @@ void ParseConnectionString(const char **company, const char **port, char *connec
}
}
/* For the server, to accept new clients */
static void NetworkAcceptClients(SOCKET ls)
/* static */ void ServerNetworkGameSocketHandler::AcceptConnection(SOCKET s, const NetworkAddress &address)
{
for (;;) {
struct sockaddr_storage sin;
memset(&sin, 0, sizeof(sin));
socklen_t sin_len = sizeof(sin);
SOCKET s = accept(ls, (struct sockaddr*)&sin, &sin_len);
if (s == INVALID_SOCKET) return;
SetNonBlocking(s); // XXX error handling?
NetworkAddress address(sin, sin_len);
DEBUG(net, 1, "Client connected from %s on frame %d", address.GetHostname(), _frame_counter);
SetNoDelay(s); // XXX error handling?
/* Check if the client is banned */
bool banned = false;
for (char **iter = _network_ban_list.Begin(); iter != _network_ban_list.End(); iter++) {
banned = address.IsInNetmask(*iter);
if (banned) {
Packet p(PACKET_SERVER_BANNED);
p.PrepareToSend();
DEBUG(net, 1, "Banned ip tried to join (%s), refused", *iter);
send(s, (const char*)p.buffer, p.size, 0);
closesocket(s);
break;
}
}
/* If this client is banned, continue with next client */
if (banned) continue;
/* Can we handle a new client? */
if (_network_clients_connected >= MAX_CLIENTS ||
_network_game_info.clients_on >= _settings_client.network.max_clients) {
/* no more clients allowed?
* Send to the client that we are full! */
Packet p(PACKET_SERVER_FULL);
p.PrepareToSend();
send(s, (const char*)p.buffer, p.size, 0);
closesocket(s);
continue;
}
/* Register the login */
_network_clients_connected++;
SetWindowDirty(WC_CLIENT_LIST, 0);
ServerNetworkGameSocketHandler *cs = new ServerNetworkGameSocketHandler(s);
cs->GetInfo()->client_address = address; // Save the IP of the client
}
}
/* Set up the listen socket for the server */
static bool NetworkListen()
{
assert(_listensockets.Length() == 0);
NetworkAddressList addresses;
GetBindAddresses(&addresses, _settings_client.network.server_port);
for (NetworkAddress *address = addresses.Begin(); address != addresses.End(); address++) {
address->Listen(SOCK_STREAM, &_listensockets);
}
if (_listensockets.Length() == 0) {
ServerStartError("Could not create listening socket");
return false;
}
return true;
}
/** Resets both pools used for network clients */
@ -521,12 +444,7 @@ void NetworkClose()
FOR_ALL_CLIENT_SOCKETS(cs) {
cs->CloseConnection(NETWORK_RECV_STATUS_CONN_LOST);
}
/* We are a server, also close the listensocket */
for (SocketList::iterator s = _listensockets.Begin(); s != _listensockets.End(); s++) {
closesocket(s->second);
}
_listensockets.Clear();
DEBUG(net, 1, "[tcp] closed listeners");
ServerNetworkGameSocketHandler::CloseListeners();
} else if (MyClient::my_client != NULL) {
MyClient::SendQuit();
MyClient::my_client->Send_Packets();
@ -713,7 +631,7 @@ bool NetworkServerStart()
NetworkDisconnect();
NetworkInitialize();
if (!NetworkListen()) return false;
if (!ServerNetworkGameSocketHandler::Listen(_settings_client.network.server_port)) return false;
/* Try to start UDP-server */
_network_udp_server = _udp_server_socket->Listen();
@ -789,67 +707,20 @@ void NetworkDisconnect(bool blocking)
*/
static bool NetworkReceive()
{
if (!_network_server) {
if (_network_server) {
return ServerNetworkGameSocketHandler::Receive();
} else {
return ClientNetworkGameSocketHandler::Receive();
}
NetworkClientSocket *cs;
fd_set read_fd, write_fd;
struct timeval tv;
FD_ZERO(&read_fd);
FD_ZERO(&write_fd);
FOR_ALL_CLIENT_SOCKETS(cs) {
FD_SET(cs->sock, &read_fd);
FD_SET(cs->sock, &write_fd);
}
/* take care of listener port */
for (SocketList::iterator s = _listensockets.Begin(); s != _listensockets.End(); s++) {
FD_SET(s->second, &read_fd);
}
tv.tv_sec = tv.tv_usec = 0; // don't block at all.
#if !defined(__MORPHOS__) && !defined(__AMIGA__)
int n = select(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv);
#else
int n = WaitSelect(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv, NULL);
#endif
if (n == -1 && !_network_server) NetworkError(STR_NETWORK_ERROR_LOSTCONNECTION);
/* accept clients.. */
for (SocketList::iterator s = _listensockets.Begin(); s != _listensockets.End(); s++) {
if (FD_ISSET(s->second, &read_fd)) NetworkAcceptClients(s->second);
}
/* read stuff from clients */
FOR_ALL_CLIENT_SOCKETS(cs) {
cs->writable = !!FD_ISSET(cs->sock, &write_fd);
if (FD_ISSET(cs->sock, &read_fd)) {
cs->Recv_Packets();
}
}
return _networking;
}
/* This sends all buffered commands (if possible) */
static void NetworkSend()
{
if (!_network_server) {
if (_network_server) {
ServerNetworkGameSocketHandler::Send();
} else {
ClientNetworkGameSocketHandler::Send();
return;
}
NetworkClientSocket *cs;
FOR_ALL_CLIENT_SOCKETS(cs) {
if (cs->writable) {
cs->Send_Packets();
if (cs->status == STATUS_MAP) {
/* This client is in the middle of a map-send, call the function for that */
cs->SendMap();
}
}
}
}

View File

@ -49,6 +49,9 @@ assert_compile(NetworkClientSocketPool::MAX_SIZE == MAX_CLIENT_SLOTS);
NetworkClientSocketPool _networkclientsocket_pool("NetworkClientSocket");
INSTANTIATE_POOL_METHODS(NetworkClientSocket)
/** Instantiate the listen sockets. */
template SocketList TCPListenHandler<ServerNetworkGameSocketHandler, PACKET_SERVER_FULL, PACKET_SERVER_BANNED>::sockets;
/**
* Create a new socket for the server side of the game connection.
* @param s The socket to connect with.
@ -118,6 +121,32 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
return status;
}
/**
* Whether an connection is allowed or not at this moment.
* @return true if the connection is allowed.
*/
/* static */ bool ServerNetworkGameSocketHandler::AllowConnection()
{
extern byte _network_clients_connected;
return _network_clients_connected < MAX_CLIENTS && _network_game_info.clients_on < _settings_client.network.max_clients;
}
/** Send the packets for the server sockets. */
/* static */ void ServerNetworkGameSocketHandler::Send()
{
NetworkClientSocket *cs;
FOR_ALL_CLIENT_SOCKETS(cs) {
if (cs->writable) {
cs->Send_Packets();
if (cs->status == STATUS_MAP) {
/* This client is in the middle of a map-send, call the function for that */
cs->SendMap();
}
}
}
}
static void NetworkHandleCommandQueue(NetworkClientSocket *cs);
/***********

View File

@ -15,6 +15,7 @@
#ifdef ENABLE_NETWORK
#include "network_internal.h"
#include "core/tcp_listen.h"
class ServerNetworkGameSocketHandler;
typedef ServerNetworkGameSocketHandler NetworkClientSocket;
@ -22,7 +23,7 @@ typedef Pool<NetworkClientSocket, ClientIndex, 8, MAX_CLIENT_SLOTS> NetworkClien
extern NetworkClientSocketPool _networkclientsocket_pool;
/** Class for handling the server side of the game connection. */
class ServerNetworkGameSocketHandler : public NetworkClientSocketPool::PoolItem<&_networkclientsocket_pool>, public NetworkGameSocketHandler {
class ServerNetworkGameSocketHandler : public NetworkClientSocketPool::PoolItem<&_networkclientsocket_pool>, public NetworkGameSocketHandler, public TCPListenHandler<ServerNetworkGameSocketHandler, PACKET_SERVER_FULL, PACKET_SERVER_BANNED> {
protected:
DECLARE_GAME_RECEIVE_COMMAND(PACKET_CLIENT_JOIN);
DECLARE_GAME_RECEIVE_COMMAND(PACKET_CLIENT_COMPANY_INFO);
@ -76,6 +77,19 @@ public:
NetworkRecvStatus SendCommand(const CommandPacket *cp);
NetworkRecvStatus SendCompanyUpdate();
NetworkRecvStatus SendConfigUpdate();
static void Send();
static void AcceptConnection(SOCKET s, const NetworkAddress &address);
static bool AllowConnection();
/**
* Get the name used by the listener.
* @return the name to show in debug logs and the like.
*/
static const char *GetName()
{
return "server";
}
};
void NetworkServer_Tick(bool send_frame);