-
-
Notifications
You must be signed in to change notification settings - Fork 535
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
29 changed files
with
1,489 additions
and
265 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,50 @@ | ||
/* | ||
This file is part of Warzone 2100. | ||
Copyright (C) 1999-2004 Eidos Interactive | ||
Copyright (C) 2005-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/byteorder_funcs_wrapper.h" | ||
|
||
#include "lib/framework/wzglobal.h" | ||
|
||
// bring in the original `htonl`/`htons`/`ntohs`/`htohl` functions | ||
#if defined WZ_OS_WIN | ||
# include <winsock.h> | ||
#else // *NIX / *BSD variants | ||
# include <arpa/inet.h> | ||
#endif | ||
|
||
uint32_t wz_htonl(uint32_t hostlong) | ||
{ | ||
return htonl(hostlong); | ||
} | ||
|
||
uint16_t wz_htons(uint16_t hostshort) | ||
{ | ||
return htons(hostshort); | ||
} | ||
|
||
uint32_t wz_ntohl(uint32_t netlong) | ||
{ | ||
return ntohl(netlong); | ||
} | ||
|
||
uint16_t wz_ntohs(uint16_t netshort) | ||
{ | ||
return ntohs(netshort); | ||
} |
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,34 @@ | ||
/* | ||
This file is part of Warzone 2100. | ||
Copyright (C) 1999-2004 Eidos Interactive | ||
Copyright (C) 2005-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> | ||
|
||
/// | ||
/// byteorder functions wrappers for WZ just to avoid polluting all places, | ||
/// where these functions are needed, with conditional includes of <arpa/inet.h> | ||
/// and winsock headers. | ||
/// | ||
|
||
uint32_t wz_htonl(uint32_t hostlong); | ||
uint16_t wz_htons(uint16_t hostshort); | ||
uint32_t wz_ntohl(uint32_t netlong); | ||
uint16_t wz_ntohs(uint16_t netshort); |
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_; | ||
}; |
Oops, something went wrong.