-
-
Notifications
You must be signed in to change notification settings - Fork 533
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
netplay: introduce abstractions for client/server-side sockets and co…
…nnection providers This patch introduces multiple high-level abstractions over raw low-level sockets, which are necessary for supporting network backends other than default legacy `TCP_DIRECT` implementation: 1. `WzConnectionProvider` - abstracts the way WZ establishes server-side and client-side connections. This thing effectively provides usable listen sockets and client connections to work with, hence the name. 2. `IListenSocket` - abstraction over listen sockets. 3. `IClientConnection` - abstraction over client-side sockets (and also server-side connections to the game clients). 4. `IConnectionPollGroup` - generalization of socket sets for polling multiple connections in one go. 5. `ConnectionProviderRegistry` - trivial singleton class providing storage for connection providers. 6. `ConnectionAddress` - opaque connection address object, aimed to replace direct uses of `addrinfo` and provide a bit more abstract way to represent connection credentials. Still looks like a crutch right now, but it's better than nothing, nonetheless. The existing implementation in `netplay/netsocket.h(.cpp)` has been moved to the `tcp` subfolder and wrapped entirely into the `tcp` namespace. The patch provides `TCP*`-prefixed implementations of the base interfaces mentioned above, which are implemented in terms of the old `netsocket` code. There's now a `ConnectionProviderType::TCP_DIRECT` enumeration descriptor for accessing the default connection provider. All uses in the high-level code (`netplay.cpp`, `joiningscreen.cpp`) are amended appropriately to use the all-new high-level abstractions instead of old low-level tcp-specific `Socket` and `SocketSet`. NOTE: there are still a few functions from the `tcp::` namespace used directly in the Discord RPC integration code, but these shouldn't pose any problem to either extract these into a more generic abstraction layer or to be rewritten not to use these functions at all, because they don't actually use any low-level stuff that's hard to refactor. Signed-off-by: Pavel Solodovnikov <[email protected]>
- Loading branch information
Showing
27 changed files
with
1,398 additions
and
247 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
This file is part of Warzone 2100. | ||
Copyright (C) 2024 Warzone 2100 Project | ||
Warzone 2100 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. | ||
Warzone 2100 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 Warzone 2100; if not, write to the Free Software | ||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <string> | ||
#include <stddef.h> | ||
|
||
#include "lib/framework/types.h" // bring in `ssize_t` for MSVC | ||
#include "lib/netplay/net_result.h" | ||
|
||
/// <summary> | ||
/// Basic abstraction over client connection sockets. | ||
/// | ||
/// These are capable of reading (`readAll` and `readNoInt`) and | ||
/// writing data (via `writeAll()` + `flush()` combination). | ||
/// | ||
/// The internal implementation may also implement advanced compression mechanisms | ||
/// on top of these connections by providing non-trivial `enableCompression()` overload. | ||
/// | ||
/// In this case, `writeAll()` should somehow accumulate the data into a write queue, | ||
/// compressing the outcoming data on-the-fly; and `flush()` should empty the write queue | ||
/// and actually post a message to the transmission queue, which, in turn, will be emptied | ||
/// by the internal connection interface in a timely manner, when there are enough messages | ||
/// to be sent over the network. | ||
/// </summary> | ||
class IClientConnection | ||
{ | ||
public: | ||
|
||
virtual ~IClientConnection() = default; | ||
|
||
/// <summary> | ||
/// Read exactly `size` bytes into `buf` buffer. | ||
/// Supports setting a timeout value in milliseconds. | ||
/// </summary> | ||
/// <param name="buf">Destination buffer to read the data into.</param> | ||
/// <param name="size">The size of data to be read in bytes.</param> | ||
/// <param name="timeout">Timeout value in milliseconds.</param> | ||
/// <returns>On success, returns the number of bytes read; | ||
/// On failure, returns an `std::error_code` (having `GenericSystemErrorCategory` error category) | ||
/// describing the actual error.</returns> | ||
virtual net::result<ssize_t> readAll(void* buf, size_t size, unsigned timeout) = 0; | ||
/// <summary> | ||
/// Reads at most `max_size` bytes into `buf` buffer. | ||
/// Raw count of bytes (after compression) is returned in `rawByteCount`. | ||
/// </summary> | ||
/// <param name="buf">Destination buffer to read the data into.</param> | ||
/// <param name="max_size">The maximum number of bytes to read from the client socket.</param> | ||
/// <param name="rawByteCount">Output parameter: Raw count of bytes (after compression).</param> | ||
/// <returns>On success, returns the number of bytes read; | ||
/// On failure, returns an `std::error_code` (having `GenericSystemErrorCategory` error category) | ||
/// describing the actual error.</returns> | ||
virtual net::result<ssize_t> readNoInt(void* buf, size_t max_size, size_t* rawByteCount) = 0; | ||
/// <summary> | ||
/// Nonblocking write of `size` bytes to the socket. The data will be written to a | ||
/// separate write queue in asynchronous manner, possibly by a separate thread. | ||
/// Raw count of bytes (after compression) will be returned in `rawByteCount`, which | ||
/// will often be 0 until the socket is flushed. | ||
/// | ||
/// The reason for this method to be async is that in some cases we want | ||
/// client connections to have compression mechanism enabled. This naturally | ||
/// introduces the 2-phase write process, which involves a write queue (accumulating | ||
/// the data for compression on-the-fly) and a submission (transmission) | ||
/// queue (for transmitting of compressed and assembled messages), | ||
/// which is managed by the network backend implementation. | ||
/// </summary> | ||
/// <param name="buf">Source buffer to read the data from.</param> | ||
/// <param name="size">The number of bytes to write to the socket.</param> | ||
/// <param name="rawByteCount">Output parameter: raw count of bytes (after compression) written.</param> | ||
/// <returns>The total number of bytes written.</returns> | ||
virtual net::result<ssize_t> writeAll(const void* buf, size_t size, size_t* rawByteCount) = 0; | ||
/// <summary> | ||
/// This method indicates whether the socket has some data ready to be read (i.e. | ||
/// whether the next `readAll/readNoInt` operation will execute without blocking or not). | ||
/// </summary> | ||
virtual bool readReady() const = 0; | ||
/// <summary> | ||
/// Actually sends the data written with `writeAll()`. Only useful with sockets | ||
/// which have compression enabled. | ||
/// Note that flushing too often makes compression less effective. | ||
/// Raw count of bytes (after compression) is returned in `rawByteCount`. | ||
/// </summary> | ||
/// <param name="rawByteCount">Raw count of bytes (after compression) as written | ||
/// to the submission queue by the flush operation.</param> | ||
virtual void flush(size_t* rawByteCount) = 0; | ||
/// <summary> | ||
/// Enables compression for the current socket. | ||
/// | ||
/// This makes all subsequent write operations asynchronous, plus | ||
/// the written data will need to be flushed explicitly at some point. | ||
/// </summary> | ||
virtual void enableCompression() = 0; | ||
/// <summary> | ||
/// Enables or disables the use of Nagle algorithm for the socket. | ||
/// | ||
/// For direct TCP connections this is equivalent to setting `TCP_NODELAY` to the | ||
/// appropriate value (i.e.: | ||
/// `enable == true` <=> `TCP_NODELAY == false`; | ||
/// `enable == false` <=> `TCP_NODELAY == true`). | ||
/// </summary> | ||
virtual void useNagleAlgorithm(bool enable) = 0; | ||
/// <summary> | ||
/// Returns textual representation of the socket's connection address. | ||
/// </summary> | ||
virtual std::string textAddress() const = 0; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/* | ||
This file is part of Warzone 2100. | ||
Copyright (C) 2024 Warzone 2100 Project | ||
Warzone 2100 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. | ||
Warzone 2100 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 Warzone 2100; if not, write to the Free Software | ||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
|
||
#include "lib/netplay/connection_address.h" | ||
#include "lib/netplay/tcp/netsocket.h" // for `resolveHost` | ||
|
||
#include "lib/framework/frame.h" // for `ASSERT` | ||
|
||
struct ConnectionAddress::Impl final | ||
{ | ||
explicit Impl(SocketAddress* addr) | ||
: mAddr_(addr) | ||
{} | ||
|
||
~Impl() | ||
{ | ||
ASSERT(mAddr_ != nullptr, "Invalid addrinfo stored in the connection address"); | ||
freeaddrinfo(mAddr_); | ||
} | ||
|
||
SocketAddress* mAddr_; | ||
}; | ||
|
||
ConnectionAddress::ConnectionAddress() = default; | ||
ConnectionAddress::ConnectionAddress(ConnectionAddress&&) = default; | ||
ConnectionAddress::~ConnectionAddress() = default; | ||
|
||
const SocketAddress* ConnectionAddress::asRawSocketAddress() const | ||
{ | ||
return mPimpl_->mAddr_; | ||
} | ||
|
||
|
||
net::result<ConnectionAddress> ConnectionAddress::parse(const char* hostname, uint16_t port) | ||
{ | ||
ConnectionAddress res; | ||
const auto addr = tcp::resolveHost(hostname, port); | ||
if (!addr.has_value()) | ||
{ | ||
return tl::make_unexpected(addr.error()); | ||
} | ||
res.mPimpl_ = std::make_unique<Impl>(addr.value()); | ||
return net::result<ConnectionAddress>{std::move(res)}; | ||
} | ||
|
||
net::result<ConnectionAddress> ConnectionAddress::parse(const std::string& hostname, uint16_t port) | ||
{ | ||
return parse(hostname.c_str(), port); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* | ||
This file is part of Warzone 2100. | ||
Copyright (C) 2024 Warzone 2100 Project | ||
Warzone 2100 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. | ||
Warzone 2100 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 Warzone 2100; if not, write to the Free Software | ||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <stdint.h> | ||
|
||
#include <memory> | ||
#include <string> | ||
|
||
#include "lib/netplay/net_result.h" | ||
|
||
#if defined WZ_OS_UNIX | ||
# include <netdb.h> | ||
#elif defined WZ_OS_WIN | ||
# include <ws2tcpip.h> | ||
#endif | ||
|
||
typedef struct addrinfo SocketAddress; | ||
|
||
/// <summary> | ||
/// Opaque class representing abstract connection address to use with various | ||
/// network backend implementations. The internal representation is made | ||
/// hidden on purpose since we don't want to actually leak internal data layout | ||
/// to clients. | ||
/// | ||
/// Instead, we would like to introduce "conversion routines" yielding | ||
/// various representations for convenient consumption with various network | ||
/// backends. | ||
/// | ||
/// NOTE: this class may or may not represent a chain of resolved network addresses | ||
/// instead of just a single one, much like a `addrinfo` structure. | ||
/// | ||
/// Currently, only knows how to convert itself to `addrinfo` struct, | ||
/// which is used with the `TCP_DIRECT` network backend. | ||
/// | ||
/// New conversion routines should be introduced for other network backends, | ||
/// if deemed necessary. | ||
/// </summary> | ||
class ConnectionAddress | ||
{ | ||
public: | ||
|
||
ConnectionAddress(); | ||
ConnectionAddress(ConnectionAddress&&); | ||
ConnectionAddress(const ConnectionAddress&) = delete; | ||
~ConnectionAddress(); | ||
|
||
static net::result<ConnectionAddress> parse(const char* hostname, uint16_t port); | ||
static net::result<ConnectionAddress> parse(const std::string& hostname, uint16_t port); | ||
|
||
// NOTE: The lifetime of the returned `addrinfo` struct is bounded by the parent object's lifetime! | ||
const SocketAddress* asRawSocketAddress() const; | ||
|
||
private: | ||
|
||
struct Impl; | ||
std::unique_ptr<Impl> mPimpl_; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
This file is part of Warzone 2100. | ||
Copyright (C) 2024 Warzone 2100 Project | ||
Warzone 2100 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. | ||
Warzone 2100 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 Warzone 2100; if not, write to the Free Software | ||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
|
||
#pragma once | ||
|
||
class IClientConnection; | ||
|
||
/// <summary> | ||
/// Abstract representation of a poll group comprised of several client connections. | ||
/// </summary> | ||
class IConnectionPollGroup | ||
{ | ||
public: | ||
|
||
virtual ~IConnectionPollGroup() = default; | ||
|
||
/// <summary> | ||
/// Polls the sockets in the poll group for updates. | ||
/// </summary> | ||
/// <param name="timeout">Timeout value after which the internal implementation should abandon | ||
/// polling the client connections and return.</param> | ||
/// <returns>On success, returns the number of connection descriptors in the poll group. | ||
/// On failure, `0` can returned if the timeout expired before any connection descriptors | ||
/// became ready, or `-1` if there was an error during the internal poll operation.</returns> | ||
virtual int checkSockets(unsigned timeout) = 0; | ||
|
||
virtual void add(IClientConnection* conn) = 0; | ||
virtual void remove(IClientConnection* conn) = 0; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
This file is part of Warzone 2100. | ||
Copyright (C) 2024 Warzone 2100 Project | ||
Warzone 2100 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. | ||
Warzone 2100 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 Warzone 2100; if not, write to the Free Software | ||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
|
||
#include <stdexcept> | ||
|
||
#include "lib/netplay/connection_provider_registry.h" | ||
|
||
ConnectionProviderRegistry& ConnectionProviderRegistry::Instance() | ||
{ | ||
static ConnectionProviderRegistry instance; | ||
return instance; | ||
} | ||
|
||
WzConnectionProvider& ConnectionProviderRegistry::Get(ConnectionProviderType pt) | ||
{ | ||
const auto it = registeredProviders_.find(pt); | ||
if (it == registeredProviders_.end()) | ||
{ | ||
throw std::runtime_error("Attempt to get nonexistent connection provider"); | ||
} | ||
return *it->second; | ||
} |
Oops, something went wrong.