Merge lp:~widelands-dev/widelands/net-relay into lp:widelands
- net-relay
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 8534 | ||||
Proposed branch: | lp:~widelands-dev/widelands/net-relay | ||||
Merge into: | lp:widelands | ||||
Prerequisite: | lp:~widelands-dev/widelands/nethost-split | ||||
Diff against target: |
2317 lines (+1652/-142) 25 files modified
src/network/CMakeLists.txt (+7/-0) src/network/gameclient.cc (+19/-7) src/network/gameclient.h (+2/-2) src/network/gamehost.cc (+36/-20) src/network/internet_gaming.cc (+32/-14) src/network/internet_gaming.h (+7/-0) src/network/internet_gaming_protocol.h (+33/-44) src/network/netclient.cc (+15/-11) src/network/netclient.h (+1/-1) src/network/netclient_interface.h (+3/-5) src/network/netclientproxy.cc (+165/-0) src/network/netclientproxy.h (+73/-0) src/network/nethost.cc (+16/-13) src/network/nethost.h (+1/-1) src/network/nethost_interface.h (+5/-5) src/network/nethostproxy.cc (+288/-0) src/network/nethostproxy.h (+103/-0) src/network/netrelayconnection.cc (+337/-0) src/network/netrelayconnection.h (+225/-0) src/network/network.cc (+2/-1) src/network/network.h (+1/-0) src/network/relay_protocol.h (+242/-0) src/ui_fsmenu/internet_lobby.cc (+37/-17) src/ui_fsmenu/internet_lobby.h (+1/-0) src/ui_fsmenu/multiplayer.cc (+1/-1) |
||||
To merge this branch: | bzr merge lp:~widelands-dev/widelands/net-relay | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
SirVer | Approve | ||
Review via email: mp+332386@code.launchpad.net |
This proposal supersedes a proposal from 2017-07-16.
Commit message
When running an Internet game, do not use a local server but relay all traffic over the metaserver.
Avoids problems with firewalls and NAT.
Description of the change
Finally: Ready for review!
When running an Internet game, do not use a local host but relay all traffic over the metaserver, avoiding problems with firewalls and routers/NAT.
Probably best start reviewing the code with src/network/
This change does not affect build19 clients. Those are still required to host a game locally.
Merge this together with deploying the metaserver version at:
https:/
This branch is not quite ready for merge yet: I am waiting for the net-uuid branch to continue the increasing protocol version numbers. Technically it would also be possible to merge this one first and adapt the version numbers in the other branch. Setting the version in this branch is required anyway.
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal | # |
GunChleoc (gunchleoc) wrote : Posted in a previous version of this proposal | # |
The protocol looks good to me.
There is no description of how the relay server get shut down yet, either in game end or when host and clients disappear without telling the relay about it.
I'd add a note to RLYCMD_TO_HOST that it's identical to RLYCMD_TO_CLIENT etc., to make the documentation easier to read.
There are also a bunch of commented out braces.
Notabilis (notabilis27) wrote : Posted in a previous version of this proposal | # |
Thanks for looking over it!
A command to shutdown the server is really missing, I haven't thought about that one. I think there should be a (long) timeout until automated shutdown when all players disconnect, just in case the shutdown-message does not arrive. Since the game can't work without the host, we could simply interpret the (planned) disconnect message of the host as a shutdown command.
With "identical" you mean the same enum-value? That is actually a bug, good catch. Is fixed in my local file now.
Assuming with the braces you mean the ///\{ and ///\} : This is intentional. With doxygen this leads to grouping of methods. The /** command */ in front of them is the description of the group.
SirVer (sirver) : Posted in a previous version of this proposal | # |
GunChleoc (gunchleoc) wrote : Posted in a previous version of this proposal | # |
1 comment regarding naming conventions.
I'd also have some minor phrasing/English language nits, but I'll keep those for the final code review when the branch is done.
SirVer (sirver) wrote : Posted in a previous version of this proposal | # |
Notabilis, could you give us an update on your work? I am quite curious. Are you blocked in any way?
Notabilis (notabilis27) wrote : Posted in a previous version of this proposal | # |
Sorry for the long silence, I should have written much earlier. My primary problem is lack of time, but when I can work on it its going well enough. I fear I won't be able to work on it in the next two weeks but hopefully I have more time afterwards.
Thanks for the comments regarding the protocol, it should be all fixed now. I also changed some more things while working on the implementation.
First (alpha-) versions of the relay server and the game connections have been implemented. The current state is pushed here and on GitHub. I haven't tested it yet but it compiles and the general design should be recognizable. If you want to comment on it, go ahead. My next step will probably be to try to run a game over the relay (and fix all the bugs that come up...).
As a high-level overview:
- The relay server is one process which creates a Game object for each running game. New connections are all to the same port. Based on their first message (containing the game name) they are distributed between the running games.
- I extracted a NetHostInterfac
What is currently missing is the integration with the metaserver. When a player wants to start a new game the metaserver has to order the relay to create the respective game and tell the host about ip/port/
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal | # |
Continuous integration builds have changed state:
Travis build 2557. State: passed. Details: https:/
Appveyor build 2269. State: success. Details: https:/
GunChleoc (gunchleoc) wrote : Posted in a previous version of this proposal | # |
Good to hear that it's going well whenever you have the time :)
SirVer (sirver) wrote : Posted in a previous version of this proposal | # |
Thanks for the update! I just was curious if you are blocked. Do you want a review now? Otherwise I'd defer reviewing until you have something mergable.
Also, if some of the refactorings you did can be done beforhand in a smaller merge request, that would be very useful.
Notabilis (notabilis27) wrote : Posted in a previous version of this proposal | # |
If you want to say something about the design, go ahead. Looking at the details isn't worthwhile yet.
Okay, I will try to create a smaller merge when I am done.
Notabilis (notabilis27) wrote : Posted in a previous version of this proposal | # |
I just pushed the first "working" version of the relay server. Working as in: Everything hardcoded (IPs, name of the game, ...), but relaying game packets works and I was able to run a game.
One question where I would like some opinion: Currently the metaserver tells the client where to connect to (i.e. the IP and port of the relay). Do we need/want this flexibility or do we simply use hardcoded values in the client? It will probably always (?) be the IP of the metaserver and some fixed port number.
bunnybot (widelandsofficial) wrote : Posted in a previous version of this proposal | # |
Continuous integration builds have changed state:
Travis build 2667. State: failed. Details: https:/
Appveyor build 2488. State: success. Details: https:/
GunChleoc (gunchleoc) wrote : Posted in a previous version of this proposal | # |
As a matter of principle, I am against hard coding where it can be avoided without too much trouble. There could conceivably be multiple game servers in the future.
Notabilis (notabilis27) wrote : Posted in a previous version of this proposal | # |
Okay, thanks. Than I will implement the dynamic version.
Notabilis (notabilis27) wrote : | # |
So much for: "[Prequisite branches] changes will not be shown in the diff."
The changes to NetHost, NetClient and the new interface classes are also at:
https:/
Maybe they will disappear from the diff here after the other branch is merged into trunk.
SirVer (sirver) wrote : | # |
This merge request tripped up bunnybot because the number of comments went down when it was reopened. The bug is fixed now, so other branches should now get merged as normal again.
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2765. State: passed. Details: https:/
Appveyor build 2577. State: success. Details: https:/
SirVer (sirver) wrote : | # |
Finally reviewed this! I left a few comments in the code using NOCOM as markers.
Really nice work. The structure is understandable and the feature is an absolute killer! Thanks for working on this.
GunChleoc (gunchleoc) wrote : | # |
@SirVer: You forgot to push
SirVer (sirver) wrote : | # |
indeed, done now.
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2832. State: failed. Details: https:/
Appveyor build 2642. State: success. Details: https:/
Notabilis (notabilis27) wrote : | # |
Thanks for the review. I have a few questions regarding your NOCOMs, though.
> You can and should use std::piecewise_
I guess you mean something like (not working):
clients_
So instead of the default constructor I will have one taking the state of the argument? That could also be achieved by
clients_
I don't really see why I should get rid of the default constructor, and I guess you mean something else.
> It would be nice to get syncstream data into this somehow to make it easier to debug desyncs going forward.
What exactly do you mean with this? Writing all transfered data to the syncstream? Only the commands/headers? Or just when errors occur? Desyncs are something which does not (or at least: should not) matter on this layer, the relay is happily transmitting even useless data.
> It would be nice to always get a kPong on a kPing ...
Can do so, no problem. Actually, that is what happening already (but pings are not always send). Currently, the pings are only used for the game host and not for the clients. Do you want to have pings for all participants? Kicking out slow clients is a feature I see as a responsibility of the "higher" layers, i.e., the GameHost class, and nothing done by the relay.
SirVer (sirver) wrote : | # |
I numbered the points for easier reference:
> 1) I changed the code to my suggestion. Please revert if you disagree.
> 2) It would be nice to get syncstream data into this somehow to make it easier to debug desyncs going forward.
Sorry, that was initial excitement while reviewing, it doesn't really belong in this code review. My thoughts on this topic are not coherent yet, so I have no clear design in mind. I pulled what I have so far out into a separate bug and removed the comment.
[1] https:/
> 3) Ping/Pong
I agree that the relay should not auto-kick users. But it is uniquely positioned to determine the actual ping values that affect gameplay. So I think it should regularly inform the players (or at least the host) how the ping to the different players is. For this a regular ping (once every 5-10 seconds) to all players would be valuable.
I pulled out a bug to design the feature: https:/
Notabilis (notabilis27) wrote : | # |
1) Thanks, looks good. Not that I really understand this construct.
2) Okay. Seems to be worth looking into that, but not in this branch.
3) I added a simple protocol for this. Please see relay_protocol.h (diff line 1918ff) for the design, it should us allow what we want to do. Metaserver support is missing for it, when you approve of the design I will add it there, too. Adding the RTT-display-
4) A still open NOCOM is regarding the move-constructor of RecvPacket. The problem is that this class extends the (old) class StreamRead, which removes the copy-constructor explicitly. We could remove this declaration but it is probably there for a reason. So I guess it is either move operations or ... no real idea.
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2891. State: errored. Details: https:/
Appveyor build 2700. State: success. Details: https:/
SirVer (sirver) wrote : | # |
I added a few more NOCOMs again, sorry :(.
3) That design looks sufficient for what I had in mind. It is also nicely minimal. Good job!
4) Here is my suggestion:
- Change bool try_receive(
- The various std::deque<
- You can delete the move operator/
This has also the advantage that you communicate clearly that a RecvPacket never changes. The move operator makes it look like you sometimes move partially read packets - but you never do as far as I can tell.
Notabilis (notabilis27) wrote : | # |
With such a great commit message you can give me as much NOCOMs as you want. ;)
4) I implemented your suggestion, but I am not completely happy with it. The move constructors are gone but there are still a few of the evil std::move in the code. Decide for yourself, I guess. I could get rid of them by storing raw pointers in the received_ queue but I am not such a fan of the idea.
5) I made the NOCOM regarding the duplicated code to a TODO note. Pulling out a method should be done but I don't know where to put it right now. I guess some place will present itself when the feature is added for real.
NOCOMs are all done (for now...).
SirVer (sirver) wrote : | # |
4) I disagree. I think it looks wonderful! You only need std::move() to signalize that you move the front() of your queue and leave an empty vector - and without it, the code would read like a copy at this site. So the move adds readability. All in all the code now looks like I would expect it to.
5) Pull the function to somewhere, it doesn't matter much. Either in the one of the units where it is used and include the header from the other. Or one that is used by both. Reducing duplicated code is more important than finding the right place for the function IMHO. Please, for me? Pretty please?
Notabilis (notabilis27) wrote : | # |
5) Since you asked that nicely, I did it, just for you. :)
On a side note: The last commits have not been tested yet, will do so when I am done with the metaserver.
SirVer (sirver) wrote : | # |
Had another look over the code just now. LGTM! As soon as the metaserver branch is ready, this can go in.
What a great change! Thanks for working on this Notabilis!
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2907. State: passed. Details: https:/
Appveyor build 2716. State: success. Details: https:/
Notabilis (notabilis27) wrote : | # |
It turns out that the kRoundTripTimeR
a) Only display the ping times at the GameHost. Should probably be fine since it is the only one who
is permitted to kick participants anyway.
b) Make the GameHost deliver the ping results to the GameClients. So the relay collects the data, the GameHost retrieves them and the GameClients can ask the GameHost about it. Kind of complicated but probably a cleaner design than c). The GameHost/GameClient exchange can be done in a later branch. (a and b result in the same code in this branch.)
c) Give the relay more information about the connected participants so it can use the GameHost/Client-IDs when returning ping results.
d) Do something great I haven't come up with yet.
Which one do you prefer?
SirVer (sirver) wrote : | # |
I am on business travel and therefore slow to respond.
> It is returning the player IDs as seen by the relay, which are something different than the player IDs in the GameHost/Client code.
I think these IDs should stay private to Widelands and the metaserver/relay respectively. Instead when they need to communicate about a particular player, they should send the player id as string - and map them internally to their ids.
Is this ready for another review round? What about the metaserver branch?
Notabilis (notabilis27) wrote : | # |
No problem, I am in no hurry.
Seems I was a bit unclear in my previous post, sorry.
These IDs are only used between the relay and the game host. The ID is created when a new client connects to the relay and announced to the host. Later messages of the client are prepended with this ID and passed on to the host. This way, we have multiple virtual network connections multiplexed about a single connection between host and relay. In the GameHost class the host can map between this "connections" (can be real connections in LAN games) and the state of the clients. The IDs are only used to number these connections, inside the GameHost class other IDs are used to number the participants of the game. So I guess this works as you want.
For the clients this mapping does not exist since they don't have to care about other clients; the game state is always send by the game host. Due to this the IDs that can be send by the relay are useless.
Currently the ID is transmitted as a single byte but I can change this to a string if you insist. (When I think about it: using a byte means that the relay has to restrict the number of participants in a game to 250. I have to check this.)
There are (at least) two problems with the pings currently:
1) Internet gaming: The relay/host IDs are useless for the client. For possible solutions see post above.
2) LAN gaming: We have no network protocol here, so no pings. When communicating with the relay, the game data packets are prepended with information regarding the intended receiver. In LAN games this is not required so the packets are send "raw". This means that we can't simply add pings on the level of the NetHost/NetClient connection but have to add them to the GameHost/GameClient layer. So for internet gaming we get our ping results from the NetHostProxy (and that from the relay) while in LANs the (higher layer) GameHost has to send the pings. I don't like it (mixing the layer responsibilities). However, if we do it that way, I am in favor of variant b) in the post above, since then we have no problems with the IDs and (at least for GameClients) the same ping-code for internet and LAN games.
Apart from this RTT/ID problem, both branches are ready for review.
SirVer (sirver) wrote : | # |
> For the clients this mapping does not exist since they don't have to care about other clients; the game state is always send by the game host.
Okay, now I got it. using a string as identifier for all packets is not wise, since packets can be small and would grow in size tremendously though this. So I think we stick with the current design and only the host knows the ping times.
> (When I think about it: using a byte means that the relay has to restrict the number of participants in a game to 250. I have to check this.)
This seems sufficient. We must keep in mind that we multiplex the bandwith of our net-relay too with the number of connections.
1) I understood now. Let's keep this information between host and net-relay for now then. We can always expand the protocol later to get this information to every connected entity.
2) I am of the opinion that the net-relay should be internet only and LAN play should not be touched. There is really no advantage in LAN for the relay, also LAN play is these days probably a rarely used feature - so much so that basically no commercial game supports it anymore.
I take that the code is now ready for a final pass. I will try to do this this weekend.
SirVer (sirver) wrote : | # |
Actually, that was easy. This code lgtm. Will review the metaserver soon.
Preview Diff
1 | === modified file 'src/network/CMakeLists.txt' |
2 | --- src/network/CMakeLists.txt 2017-11-25 20:29:26 +0000 |
3 | +++ src/network/CMakeLists.txt 2017-12-05 20:38:52 +0000 |
4 | @@ -15,9 +15,15 @@ |
5 | netclient_interface.h |
6 | netclient.cc |
7 | netclient.h |
8 | + netclientproxy.cc |
9 | + netclientproxy.h |
10 | nethost_interface.h |
11 | nethost.cc |
12 | nethost.h |
13 | + nethostproxy.cc |
14 | + nethostproxy.h |
15 | + netrelayconnection.cc |
16 | + netrelayconnection.h |
17 | network.cc |
18 | network.h |
19 | network_gaming_messages.cc |
20 | @@ -27,6 +33,7 @@ |
21 | network_player_settings_backend.cc |
22 | network_player_settings_backend.h |
23 | network_protocol.h |
24 | + relay_protocol.h |
25 | DEPENDS |
26 | ai |
27 | base_exceptions |
28 | |
29 | === modified file 'src/network/gameclient.cc' |
30 | --- src/network/gameclient.cc 2017-11-05 19:59:33 +0000 |
31 | +++ src/network/gameclient.cc 2017-12-05 20:38:52 +0000 |
32 | @@ -41,6 +41,8 @@ |
33 | #include "logic/playersmanager.h" |
34 | #include "map_io/widelands_map_loader.h" |
35 | #include "network/internet_gaming.h" |
36 | +#include "network/netclient.h" |
37 | +#include "network/netclientproxy.h" |
38 | #include "network/network_gaming_messages.h" |
39 | #include "network/network_protocol.h" |
40 | #include "scripting/lua_interface.h" |
41 | @@ -58,7 +60,7 @@ |
42 | |
43 | std::string localplayername; |
44 | |
45 | - std::unique_ptr<NetClient> net; |
46 | + std::unique_ptr<NetClientInterface> net; |
47 | |
48 | /// Currently active modal panel. Receives an end_modal on disconnect |
49 | UI::Panel* modal; |
50 | @@ -91,13 +93,22 @@ |
51 | |
52 | GameClient::GameClient(const std::pair<NetAddress, NetAddress>& host, |
53 | const std::string& playername, |
54 | - bool internet) |
55 | + bool internet, const std::string& gamename) |
56 | : d(new GameClientImpl), internet_(internet) { |
57 | |
58 | - d->net = NetClient::connect(host.first); |
59 | + if (internet) { |
60 | + assert(!gamename.empty()); |
61 | + d->net = NetClientProxy::connect(host.first, gamename); |
62 | + } else { |
63 | + d->net = NetClient::connect(host.first); |
64 | + } |
65 | if ((!d->net || !d->net->is_connected()) && host.second.is_valid()) { |
66 | // First IP did not work? Try the second IP |
67 | - d->net = NetClient::connect(host.second); |
68 | + if (internet) { |
69 | + d->net = NetClientProxy::connect(host.first, gamename); |
70 | + } else { |
71 | + d->net = NetClient::connect(host.second); |
72 | + } |
73 | } |
74 | if (!d->net || !d->net->is_connected()) { |
75 | throw WLWarning(_("Could not establish connection to host"), |
76 | @@ -877,9 +888,10 @@ |
77 | return; |
78 | } |
79 | // Process all available packets |
80 | - RecvPacket packet; |
81 | - while (d->net->try_receive(&packet)) { |
82 | - handle_packet(packet); |
83 | + std::unique_ptr<RecvPacket> packet = d->net->try_receive(); |
84 | + while (packet) { |
85 | + handle_packet(*packet); |
86 | + packet = d->net->try_receive(); |
87 | } |
88 | } catch (const DisconnectException& e) { |
89 | disconnect(e.what()); |
90 | |
91 | === modified file 'src/network/gameclient.h' |
92 | --- src/network/gameclient.h 2017-11-27 21:21:06 +0000 |
93 | +++ src/network/gameclient.h 2017-12-05 20:38:52 +0000 |
94 | @@ -24,7 +24,7 @@ |
95 | #include "logic/game_controller.h" |
96 | #include "logic/game_settings.h" |
97 | #include "logic/player_end_result.h" |
98 | -#include "network/netclient.h" |
99 | +#include "network/netclient_interface.h" |
100 | |
101 | struct GameClientImpl; |
102 | |
103 | @@ -39,7 +39,7 @@ |
104 | struct GameClient : public GameController, public GameSettingsProvider, public ChatProvider { |
105 | GameClient(const std::pair<NetAddress, NetAddress>& host, |
106 | const std::string& playername, |
107 | - bool internet = false); |
108 | + bool internet = false, const std::string& gamename = ""); |
109 | |
110 | ~GameClient() override; |
111 | |
112 | |
113 | === modified file 'src/network/gamehost.cc' |
114 | --- src/network/gamehost.cc 2017-11-30 07:11:36 +0000 |
115 | +++ src/network/gamehost.cc 2017-12-05 20:38:52 +0000 |
116 | @@ -52,6 +52,7 @@ |
117 | #include "network/constants.h" |
118 | #include "network/internet_gaming.h" |
119 | #include "network/nethost.h" |
120 | +#include "network/nethostproxy.h" |
121 | #include "network/network_gaming_messages.h" |
122 | #include "network/network_lan_promotion.h" |
123 | #include "network/network_player_settings_backend.h" |
124 | @@ -412,7 +413,7 @@ |
125 | HostGameSettingsProvider hp; |
126 | NetworkPlayerSettingsBackend npsb; |
127 | |
128 | - LanGamePromoter* promoter; |
129 | + std::unique_ptr<LanGamePromoter> promoter; |
130 | std::unique_ptr<NetHostInterface> net; |
131 | |
132 | /// List of connected clients. Note that clients are not in the same |
133 | @@ -459,7 +460,7 @@ |
134 | chat(h), |
135 | hp(h), |
136 | npsb(&hp), |
137 | - promoter(nullptr), |
138 | + promoter(), |
139 | net(), |
140 | game(nullptr), |
141 | pseudo_networktime(0), |
142 | @@ -480,21 +481,30 @@ |
143 | : d(new GameHostImpl(this)), internet_(internet), forced_pause_(false) { |
144 | log("[Host]: starting up.\n"); |
145 | |
146 | - if (internet) { |
147 | - InternetGaming::ref().open_game(); |
148 | - } |
149 | - |
150 | d->localplayername = playername; |
151 | |
152 | // create a listening socket |
153 | - d->net = NetHost::listen(kWidelandsLanPort); |
154 | - if (d->net == nullptr) { |
155 | - // This might happen when the widelands socket is already in use |
156 | - throw WLWarning(_("Failed to start the server!"), |
157 | - _("Widelands could not start a server.\n" |
158 | - "Probably some other process is already running a server on our port.")); |
159 | + if (internet) { |
160 | + // No real listening socket. Instead, connect to the relay server |
161 | + d->net = NetHostProxy::connect(InternetGaming::ref().ips(), |
162 | + InternetGaming::ref().get_local_servername(), InternetGaming::ref().relay_password()); |
163 | + if (d->net == nullptr) { |
164 | + // Some kind of problem with the relay server. Bad luck :( |
165 | + throw WLWarning(_("Failed to host the server!"), |
166 | + _("Widelands could not start hosting a server.\n" |
167 | + "This should not happen and there is most likely " |
168 | + "nothing you can do about it. Please report a bug.")); |
169 | + } |
170 | + } else { |
171 | + d->net = NetHost::listen(kWidelandsLanPort); |
172 | + if (d->net == nullptr) { |
173 | + // This might happen when the widelands socket is already in use |
174 | + throw WLWarning(_("Failed to start the server!"), |
175 | + _("Widelands could not start a server.\n" |
176 | + "Probably some other process is already running a server on our port.")); |
177 | + } |
178 | + d->promoter.reset(new LanGamePromoter()); |
179 | } |
180 | - d->promoter = new LanGamePromoter(); |
181 | d->game = nullptr; |
182 | d->pseudo_networktime = 0; |
183 | d->waiting = true; |
184 | @@ -525,7 +535,7 @@ |
185 | |
186 | // close all open sockets |
187 | d->net.reset(); |
188 | - delete d->promoter; |
189 | + d->promoter.reset(); |
190 | delete d; |
191 | delete file_; |
192 | } |
193 | @@ -941,8 +951,12 @@ |
194 | |
195 | // if there is one client that is currently receiving a file, we can not launch. |
196 | for (std::vector<Client>::iterator j = d->clients.begin(); j != d->clients.end(); ++j) { |
197 | - if (!d->settings.users[j->usernum].ready) |
198 | - return false; |
199 | + if (j->usernum == -1) { |
200 | + return false; |
201 | + } |
202 | + if (!d->settings.users[j->usernum].ready) { |
203 | + return false; |
204 | + } |
205 | } |
206 | |
207 | // all players must be connected to a controller (human/ai) or be closed. |
208 | @@ -1896,8 +1910,9 @@ |
209 | |
210 | void GameHost::handle_network() { |
211 | |
212 | - if (d->promoter != nullptr) |
213 | + if (d->promoter) { |
214 | d->promoter->run(); |
215 | + } |
216 | |
217 | // Check for new connections. |
218 | Client peer; |
219 | @@ -1929,11 +1944,12 @@ |
220 | } |
221 | |
222 | // Check if we hear anything from our clients |
223 | - RecvPacket packet; |
224 | for (size_t i = 0; i < d->clients.size(); ++i) { |
225 | try { |
226 | - while (d->net->try_receive(d->clients.at(i).sock_id, &packet)) { |
227 | - handle_packet(i, packet); |
228 | + std::unique_ptr<RecvPacket> packet = d->net->try_receive(d->clients.at(i).sock_id); |
229 | + while (packet) { |
230 | + handle_packet(i, *packet); |
231 | + packet = d->net->try_receive(d->clients.at(i).sock_id); |
232 | } |
233 | // Thrown by handle_packet() |
234 | } catch (const DisconnectException& e) { |
235 | |
236 | === modified file 'src/network/internet_gaming.cc' |
237 | --- src/network/internet_gaming.cc 2017-11-26 20:55:56 +0000 |
238 | +++ src/network/internet_gaming.cc 2017-12-05 20:38:52 +0000 |
239 | @@ -43,7 +43,7 @@ |
240 | : net(nullptr), |
241 | state_(OFFLINE), |
242 | reg_(false), |
243 | - port_(INTERNET_GAMING_PORT), |
244 | + port_(kInternetGamingPort), |
245 | clientrights_(INTERNET_CLIENT_UNREGISTERED), |
246 | gameips_(), |
247 | clientupdateonmetaserver_(true), |
248 | @@ -68,7 +68,7 @@ |
249 | authenticator_ = ""; |
250 | reg_ = false; |
251 | meta_ = INTERNET_GAMING_METASERVER; |
252 | - port_ = INTERNET_GAMING_PORT; |
253 | + port_ = kInternetGamingPort; |
254 | clientname_ = ""; |
255 | clientrights_ = INTERNET_CLIENT_UNREGISTERED; |
256 | gamename_ = ""; |
257 | @@ -152,7 +152,7 @@ |
258 | log("InternetGaming: Sending login request.\n"); |
259 | SendPacket s; |
260 | s.string(IGPCMD_LOGIN); |
261 | - s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION)); |
262 | + s.string(boost::lexical_cast<std::string>(kInternetGamingProtocolVersion)); |
263 | s.string(clientname_); |
264 | s.string(build_id()); |
265 | s.string(bool2str(reg_)); |
266 | @@ -162,7 +162,7 @@ |
267 | // Now let's see, whether the metaserver is answering |
268 | uint32_t const secs = time(nullptr); |
269 | state_ = CONNECTING; |
270 | - while (INTERNET_GAMING_TIMEOUT > time(nullptr) - secs) { |
271 | + while (kInternetGamingTimeout > time(nullptr) - secs) { |
272 | handle_metaserver_communication(); |
273 | // Check if we are a step further... if yes handle_packet has taken care about all the |
274 | // paperwork, so we put our feet up and just return. ;) |
275 | @@ -274,9 +274,9 @@ |
276 | return; |
277 | } |
278 | // Process all available packets |
279 | - RecvPacket packet; |
280 | - if (net->try_receive(&packet)) { |
281 | - handle_packet(packet); |
282 | + std::unique_ptr<RecvPacket> packet = net->try_receive(); |
283 | + if (packet) { |
284 | + handle_packet(*packet); |
285 | } else { |
286 | // Nothing more to receive |
287 | break; |
288 | @@ -356,7 +356,7 @@ |
289 | // Okay, we have a connection. Send the login message and terminate the connection |
290 | SendPacket s; |
291 | s.string(IGPCMD_TELL_IP); |
292 | - s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION)); |
293 | + s.string(boost::lexical_cast<std::string>(kInternetGamingProtocolVersion)); |
294 | s.string(clientname_); |
295 | s.string(authenticator_); |
296 | tmpNet->send(s); |
297 | @@ -581,6 +581,15 @@ |
298 | if (waitcmd_ == IGPCMD_GAME_OPEN) { |
299 | waitcmd_ = ""; |
300 | } |
301 | + // Save the received IP(s), so the client can connect to the game |
302 | + NetAddress::parse_ip(&gameips_.first, packet.string(), kInternetRelayPort); |
303 | + // If the next value is true, a secondary IP follows |
304 | + if (packet.string() == bool2str(true)) { |
305 | + NetAddress::parse_ip(&gameips_.second, packet.string(), kInternetRelayPort); |
306 | + } |
307 | + log("InternetGaming: Received ips of the relay to host: %s %s.\n", |
308 | + gameips_.first.ip.to_string().c_str(), gameips_.second.ip.to_string().c_str()); |
309 | + state_ = IN_GAME; |
310 | } |
311 | |
312 | else if (cmd == IGPCMD_GAME_CONNECT) { |
313 | @@ -588,10 +597,10 @@ |
314 | assert(waitcmd_ == IGPCMD_GAME_CONNECT); |
315 | waitcmd_ = ""; |
316 | // Save the received IP(s), so the client can connect to the game |
317 | - NetAddress::parse_ip(&gameips_.first, packet.string(), kWidelandsLanPort); |
318 | + NetAddress::parse_ip(&gameips_.first, packet.string(), kInternetRelayPort); |
319 | // If the next value is true, a secondary IP follows |
320 | if (packet.string() == bool2str(true)) { |
321 | - NetAddress::parse_ip(&gameips_.second, packet.string(), kWidelandsLanPort); |
322 | + NetAddress::parse_ip(&gameips_.second, packet.string(), kInternetRelayPort); |
323 | } |
324 | log("InternetGaming: Received ips of the game to join: %s %s.\n", |
325 | gameips_.first.ip.to_string().c_str(), gameips_.second.ip.to_string().c_str()); |
326 | @@ -648,11 +657,18 @@ |
327 | return gameips_; |
328 | } |
329 | |
330 | +const std::string InternetGaming::relay_password() { |
331 | + return authenticator_; |
332 | +} |
333 | + |
334 | /// called by a client to join the game \arg gamename |
335 | void InternetGaming::join_game(const std::string& gamename) { |
336 | if (!logged_in()) |
337 | return; |
338 | |
339 | + // Reset the game ips, we should receive new ones shortly |
340 | + gameips_ = std::make_pair(NetAddress(), NetAddress()); |
341 | + |
342 | SendPacket s; |
343 | s.string(IGPCMD_GAME_CONNECT); |
344 | s.string(gamename); |
345 | @@ -663,7 +679,7 @@ |
346 | |
347 | // From now on we wait for a reply from the metaserver |
348 | waitcmd_ = IGPCMD_GAME_CONNECT; |
349 | - waittimeout_ = time(nullptr) + INTERNET_GAMING_TIMEOUT; |
350 | + waittimeout_ = time(nullptr) + kInternetGamingTimeout; |
351 | } |
352 | |
353 | /// called by a client to open a new game with name gamename_ |
354 | @@ -671,17 +687,19 @@ |
355 | if (!logged_in()) |
356 | return; |
357 | |
358 | + // Reset the game ips, we should receive new ones shortly |
359 | + gameips_ = std::make_pair(NetAddress(), NetAddress()); |
360 | + |
361 | SendPacket s; |
362 | s.string(IGPCMD_GAME_OPEN); |
363 | s.string(gamename_); |
364 | s.string("1024"); // Used to be maxclients, no longer used. |
365 | net->send(s); |
366 | log("InternetGaming: Client opened a game with the name %s.\n", gamename_.c_str()); |
367 | - state_ = IN_GAME; |
368 | |
369 | // From now on we wait for a reply from the metaserver |
370 | waitcmd_ = IGPCMD_GAME_OPEN; |
371 | - waittimeout_ = time(nullptr) + INTERNET_GAMING_TIMEOUT; |
372 | + waittimeout_ = time(nullptr) + kInternetGamingTimeout; |
373 | } |
374 | |
375 | /// called by a client that is host of a game to inform the metaserver, that the game started |
376 | @@ -696,7 +714,7 @@ |
377 | |
378 | // From now on we wait for a reply from the metaserver |
379 | waitcmd_ = IGPCMD_GAME_START; |
380 | - waittimeout_ = time(nullptr) + INTERNET_GAMING_TIMEOUT; |
381 | + waittimeout_ = time(nullptr) + kInternetGamingTimeout; |
382 | } |
383 | |
384 | /// called by a client to inform the metaserver, that it left the game and is back in the lobby. |
385 | |
386 | === modified file 'src/network/internet_gaming.h' |
387 | --- src/network/internet_gaming.h 2017-11-26 20:55:56 +0000 |
388 | +++ src/network/internet_gaming.h 2017-12-05 20:38:52 +0000 |
389 | @@ -101,10 +101,17 @@ |
390 | * Contains two addresses when the host supports IPv4 and IPv6, one address when the host |
391 | * only supports one of the protocols, no addresses when no join-request was sent to |
392 | * the metaserver. "No address" means a default constructed address. |
393 | + * Also returns the IPs of the relay server when trying to host a game. |
394 | * Use NetAddress::is_valid() to check whether a NetAddress has been default constructed. |
395 | * @return The addresses. |
396 | */ |
397 | const std::pair<NetAddress, NetAddress>& ips(); |
398 | + |
399 | + /** |
400 | + * Returns the password required to connect to the relay server as host. |
401 | + */ |
402 | + const std::string relay_password(); |
403 | + |
404 | void join_game(const std::string& gamename); |
405 | void open_game(); |
406 | void set_game_playing(); |
407 | |
408 | === modified file 'src/network/internet_gaming_protocol.h' |
409 | --- src/network/internet_gaming_protocol.h 2017-11-26 20:55:56 +0000 |
410 | +++ src/network/internet_gaming_protocol.h 2017-12-05 20:38:52 +0000 |
411 | @@ -34,46 +34,34 @@ |
412 | * 0: Build 19 and before [stable, supported] |
413 | * 1: Between build 19 and build 20 - IPv6 support added |
414 | * 2: Between build 19 and build 20 - Added UUID to allow reconnect with same username after |
415 | - * crashes. |
416 | - * When logging twice with a registered account, the second |
417 | - * connection |
418 | - * gets a free username assigned. Dropping RELOGIN command. |
419 | - * [supported] |
420 | - */ |
421 | -#define INTERNET_GAMING_PROTOCOL_VERSION 2 |
422 | - |
423 | -/** |
424 | - * The default timeout time after which the client tries to resend a package or even finally closes |
425 | - * the |
426 | - * connection to the metaserver, if no answer to a previous package (which requires an answer) was |
427 | - * received. In case of a login or reconnect, this is the time to wait for the metaservers answer. |
428 | - * |
429 | - * value is in milliseconds |
430 | - */ |
431 | -// TODO(unknown): Should this be resettable by the user? |
432 | -#define INTERNET_GAMING_TIMEOUT 10 // 10 seconds |
433 | - |
434 | -/** |
435 | - * The default timeout time after which the client tries to resend a package or even finally closes |
436 | - * the |
437 | - * connection to the metaserver, if no answer to a previous package (which requires an answer) was |
438 | - * received. In case of a login or reconnect, this is the time to wait for the metaservers answer. |
439 | - * |
440 | - * value is in milliseconds |
441 | - */ |
442 | -// TODO(unknown): Should this be resettable by the user? |
443 | -#define INTERNET_GAMING_CLIENT_TIMEOUT 60 // 60 seconds - some time to reconnect |
444 | - |
445 | -/** |
446 | - * The default number of retries after a timeout after which the client finally closes the |
447 | - * connection to the metaserver. |
448 | - */ |
449 | -// TODO(unknown): Should this be resettable by the user? |
450 | -#define INTERNET_GAMING_RETRIES 3 |
451 | + * crashes. When logging twice with a registered account, the second connection gets a free |
452 | + * username assigned. Dropping RELOGIN command. |
453 | + * 3: Between build 19 and build 20 - Added network relay for internet games [supported] |
454 | + */ |
455 | +constexpr unsigned int kInternetGamingProtocolVersion = 3; |
456 | + |
457 | +/** |
458 | + * The default timeout time after which the client tries to resend a package or even finally closes |
459 | + * the |
460 | + * connection to the metaserver, if no answer to a previous package (which requires an answer) was |
461 | + * received. In case of a login or reconnect, this is the time to wait for the metaservers answer. |
462 | + * |
463 | + * value is in milliseconds |
464 | + */ |
465 | +// TODO(unknown): Should this be resettable by the user? |
466 | +constexpr time_t kInternetGamingTimeout = 10; // 10 seconds |
467 | |
468 | /// Metaserver connection details |
469 | static const std::string INTERNET_GAMING_METASERVER = "widelands.org"; |
470 | -#define INTERNET_GAMING_PORT 7395 |
471 | +// Default port for connecting to the metaserver |
472 | +constexpr uint16_t kInternetGamingPort = 7395; |
473 | +// Default port for connecting to the relay |
474 | +constexpr uint16_t kInternetRelayPort = 7397; |
475 | +// The following ones are only used between metaserver and relay |
476 | +// Port used by the metaserver to contact the relay |
477 | +// INTERNET_RELAY_RPC_PORT 7398 |
478 | +// Port used by the relay to contact the metaserver |
479 | +// INTERNET_GAMING_RPC_PORT 7399 |
480 | |
481 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
482 | * CLIENT RIGHTS * |
483 | @@ -183,7 +171,7 @@ |
484 | * used.) |
485 | * \li string: clients rights (see client rights section above) |
486 | * |
487 | - * If no answer is received in \ref INTERNET_GAMING_TIMEOUT s the client will again try to login |
488 | + * If no answer is received in \ref kInternetGamingTimeout s the client will again try to login |
489 | * \ref INTERNET_GAMING_RETRIES times until it finally bails out something like "server does not |
490 | * answer" |
491 | * |
492 | @@ -337,12 +325,13 @@ |
493 | * \li string: number of maximal clients |
494 | * \note build_id is not necessary, as this is in every way the build_id of the hosting client. |
495 | * |
496 | - * Sent by the metaserver to acknowledge the startup of a new game without payload. The metaserver |
497 | - * will |
498 | - * list the new game, but set it as not connectable and recheck the connectability for |
499 | - * INTERNET_GAMING_TIMEOUT ms. |
500 | - * If the game gets connectable in time, the metaserver lists the game as connectable, else it |
501 | - * removes the game from the list of games. |
502 | + * Sent by the metaserver to acknowledge the startup of a new game with the following payload: |
503 | + * \li string: primary ip of relay server for the game. |
504 | + * \li string: whether a secondary ip for the relay follows ("true" or "false" as string) |
505 | + * \li string: secondary ip of the relay - only valid if previous was true |
506 | + * The metaserver will list the new game, but set it as not connectable. |
507 | + * When the client connects to the relay within kInternetGamingTimeout milliseconds, |
508 | + * the metaserver lists the game as connectable, else it removes the game from the list of games. |
509 | */ |
510 | static const std::string IGPCMD_GAME_OPEN = "GAME_OPEN"; |
511 | |
512 | |
513 | === modified file 'src/network/netclient.cc' |
514 | --- src/network/netclient.cc 2017-07-22 22:09:20 +0000 |
515 | +++ src/network/netclient.cc 2017-12-05 20:38:52 +0000 |
516 | @@ -51,7 +51,7 @@ |
517 | socket_.close(ec); |
518 | } |
519 | |
520 | -bool NetClient::try_receive(RecvPacket* packet) { |
521 | +std::unique_ptr<RecvPacket> NetClient::try_receive() { |
522 | if (is_connected()) { |
523 | // If we are connected, try to receive some data |
524 | |
525 | @@ -72,7 +72,12 @@ |
526 | } |
527 | } |
528 | // Try to get one packet from the deserializer |
529 | - return deserializer_.write_packet(packet); |
530 | + std::unique_ptr<RecvPacket> packet(new RecvPacket); |
531 | + if (deserializer_.write_packet(packet.get())) { |
532 | + return packet; |
533 | + } else { |
534 | + return std::unique_ptr<RecvPacket>(); |
535 | + } |
536 | } |
537 | |
538 | void NetClient::send(const SendPacket& packet) { |
539 | @@ -81,21 +86,20 @@ |
540 | } |
541 | |
542 | boost::system::error_code ec; |
543 | -#ifdef NDEBUG |
544 | - boost::asio::write(socket_, boost::asio::buffer(packet.get_data(), packet.get_size()), ec); |
545 | -#else |
546 | size_t written = |
547 | boost::asio::write(socket_, boost::asio::buffer(packet.get_data(), packet.get_size()), ec); |
548 | -#endif |
549 | |
550 | - // TODO(Notabilis): This one is an assertion of mine, I am not sure if it will hold |
551 | - // If it doesn't, set the socket to blocking before writing |
552 | - // If it does, remove this comment after build 20 |
553 | - assert(ec != boost::asio::error::would_block); |
554 | - assert(written == packet.get_size() || ec); |
555 | + if (ec == boost::asio::error::would_block) { |
556 | + throw wexception("[NetClient] Socket connected to relay would block when writing"); |
557 | + } |
558 | if (ec) { |
559 | log("[NetClient] Error when trying to send some data: %s.\n", ec.message().c_str()); |
560 | close(); |
561 | + return; |
562 | + } |
563 | + if (written < packet.get_size()) { |
564 | + throw wexception("[NetClient] Unable to send complete packet to relay (only %lu bytes of %lu)", |
565 | + written, packet.get_size()); |
566 | } |
567 | } |
568 | |
569 | |
570 | === modified file 'src/network/netclient.h' |
571 | --- src/network/netclient.h 2017-11-27 21:21:06 +0000 |
572 | +++ src/network/netclient.h 2017-12-05 20:38:52 +0000 |
573 | @@ -53,7 +53,7 @@ |
574 | // Inherited from NetClientInterface |
575 | bool is_connected() const override; |
576 | void close() override; |
577 | - bool try_receive(RecvPacket* packet) override; |
578 | + std::unique_ptr<RecvPacket> try_receive() override; |
579 | void send(const SendPacket& packet) override; |
580 | |
581 | private: |
582 | |
583 | === modified file 'src/network/netclient_interface.h' |
584 | --- src/network/netclient_interface.h 2017-10-31 03:50:46 +0000 |
585 | +++ src/network/netclient_interface.h 2017-12-05 20:38:52 +0000 |
586 | @@ -54,12 +54,10 @@ |
587 | |
588 | /** |
589 | * Tries to receive a packet. |
590 | - * \param packet A packet that should be overwritten with the received data. |
591 | - * \return \c true if a packet is available, \c false otherwise. |
592 | - * The given packet is only modified when \c true is returned. |
593 | - * Calling this on a closed connection will return false. |
594 | + * \return A pointer to a packet if one packet is available, an invalid pointer otherwise. |
595 | + * Calling this on a closed connection will return an invalid pointer. |
596 | */ |
597 | - virtual bool try_receive(RecvPacket* packet) = 0; |
598 | + virtual std::unique_ptr<RecvPacket> try_receive() = 0; |
599 | |
600 | /** |
601 | * Sends a packet. |
602 | |
603 | === added file 'src/network/netclientproxy.cc' |
604 | --- src/network/netclientproxy.cc 1970-01-01 00:00:00 +0000 |
605 | +++ src/network/netclientproxy.cc 2017-12-05 20:38:52 +0000 |
606 | @@ -0,0 +1,165 @@ |
607 | +#include "network/netclientproxy.h" |
608 | + |
609 | +#include <memory> |
610 | + |
611 | +#include "base/log.h" |
612 | +#include "network/relay_protocol.h" |
613 | + |
614 | +std::unique_ptr<NetClientProxy> NetClientProxy::connect(const NetAddress& address, const std::string& name) { |
615 | + std::unique_ptr<NetClientProxy> ptr(new NetClientProxy(address, name)); |
616 | + if (ptr->conn_ == nullptr || !ptr->conn_->is_connected()) { |
617 | + ptr.reset(); |
618 | + } |
619 | + return ptr; |
620 | +} |
621 | + |
622 | +NetClientProxy::~NetClientProxy() { |
623 | + close(); |
624 | +} |
625 | + |
626 | + |
627 | +bool NetClientProxy::is_connected() const { |
628 | + return conn_ && conn_->is_connected(); |
629 | +} |
630 | + |
631 | +void NetClientProxy::close() { |
632 | + if (conn_ && conn_->is_connected()) { |
633 | + conn_->close(); |
634 | + } |
635 | +} |
636 | + |
637 | +std::unique_ptr<RecvPacket> NetClientProxy::try_receive() { |
638 | + receive_commands(); |
639 | + |
640 | + // Now check whether there is data |
641 | + if (received_.empty()) { |
642 | + return std::unique_ptr<RecvPacket>(); |
643 | + } |
644 | + |
645 | + std::unique_ptr<RecvPacket> packet = std::move(received_.front()); |
646 | + received_.pop(); |
647 | + return packet; |
648 | +} |
649 | + |
650 | +void NetClientProxy::send(const SendPacket& packet) { |
651 | + conn_->send(RelayCommand::kToHost); |
652 | + conn_->send(packet); |
653 | +} |
654 | + |
655 | +NetClientProxy::NetClientProxy(const NetAddress& address, const std::string& name) |
656 | + : conn_(NetRelayConnection::connect(address)) { |
657 | + if (conn_ == nullptr || !conn_->is_connected()) { |
658 | + return; |
659 | + } |
660 | + |
661 | + conn_->send(RelayCommand::kHello); |
662 | + conn_->send(kRelayProtocolVersion); |
663 | + conn_->send(name); |
664 | + conn_->send("client"); |
665 | + |
666 | + // Wait 10 seconds for an answer |
667 | + uint32_t endtime = time(nullptr) + 10; |
668 | + while (!NetRelayConnection::Peeker(conn_.get()).cmd()) { |
669 | + if (time(nullptr) > endtime) { |
670 | + // No message received in time |
671 | + conn_->close(); |
672 | + conn_.reset(); |
673 | + return; |
674 | + } |
675 | + } |
676 | + |
677 | + RelayCommand cmd; |
678 | + conn_->receive(&cmd); |
679 | + |
680 | + if (cmd != RelayCommand::kWelcome) { |
681 | + conn_->close(); |
682 | + conn_.reset(); |
683 | + return; |
684 | + } |
685 | + |
686 | + // Check version |
687 | + endtime = time(nullptr) + 10; |
688 | + while (!NetRelayConnection::Peeker(conn_.get()).uint8_t()) { |
689 | + if (time(nullptr) > endtime) { |
690 | + conn_->close(); |
691 | + conn_.reset(); |
692 | + return; |
693 | + } |
694 | + } |
695 | + uint8_t relay_proto_version; |
696 | + conn_->receive(&relay_proto_version); |
697 | + if (relay_proto_version != kRelayProtocolVersion) { |
698 | + conn_->close(); |
699 | + conn_.reset(); |
700 | + } |
701 | + |
702 | + // Check game name |
703 | + endtime = time(nullptr) + 10; |
704 | + while (!NetRelayConnection::Peeker(conn_.get()).string()) { |
705 | + if (time(nullptr) > endtime) { |
706 | + conn_->close(); |
707 | + conn_.reset(); |
708 | + return; |
709 | + } |
710 | + } |
711 | + std::string game_name; |
712 | + conn_->receive(&game_name); |
713 | + if (game_name != name) { |
714 | + conn_->close(); |
715 | + conn_.reset(); |
716 | + return; |
717 | + } |
718 | +} |
719 | + |
720 | +void NetClientProxy::receive_commands() { |
721 | + if (!conn_->is_connected()) { |
722 | + return; |
723 | + } |
724 | + |
725 | + // Receive all available commands |
726 | + RelayCommand cmd; |
727 | + NetRelayConnection::Peeker peek(conn_.get()); |
728 | + if (!peek.cmd(&cmd)) { |
729 | + // No command to receive |
730 | + return; |
731 | + } |
732 | + switch (cmd) { |
733 | + case RelayCommand::kDisconnect: |
734 | + if (peek.string()) { |
735 | + // Command is completely in the buffer, handle it |
736 | + conn_->receive(&cmd); |
737 | + std::string reason; |
738 | + conn_->receive(&reason); |
739 | + // TODO(Notabilis): Handle the reason for the disconnect |
740 | + conn_->close(); |
741 | + } |
742 | + break; |
743 | + case RelayCommand::kFromHost: |
744 | + if (peek.recvpacket()) { |
745 | + conn_->receive(&cmd); |
746 | + std::unique_ptr<RecvPacket> packet(new RecvPacket); |
747 | + conn_->receive(packet.get()); |
748 | + received_.push(std::move(packet)); |
749 | + } |
750 | + break; |
751 | + case RelayCommand::kPing: |
752 | + if (peek.uint8_t()) { |
753 | + conn_->receive(&cmd); |
754 | + uint8_t seq; |
755 | + conn_->receive(&seq); |
756 | + // Reply with a pong |
757 | + conn_->send(RelayCommand::kPong); |
758 | + conn_->send(seq); |
759 | + } |
760 | + break; |
761 | + case RelayCommand::kRoundTripTimeResponse: |
762 | + conn_->ignore_rtt_response(); |
763 | + break; |
764 | + default: |
765 | + // Other commands should not be possible. |
766 | + // Then is either something wrong with the protocol or there is an implementation mistake |
767 | + log("Received command code %i from relay server, do not know what to do with it\n", |
768 | + static_cast<uint8_t>(cmd)); |
769 | + NEVER_HERE(); |
770 | + } |
771 | +} |
772 | |
773 | === added file 'src/network/netclientproxy.h' |
774 | --- src/network/netclientproxy.h 1970-01-01 00:00:00 +0000 |
775 | +++ src/network/netclientproxy.h 2017-12-05 20:38:52 +0000 |
776 | @@ -0,0 +1,73 @@ |
777 | +/* |
778 | + * Copyright (C) 2008-2017 by the Widelands Development Team |
779 | + * |
780 | + * This program is free software; you can redistribute it and/or |
781 | + * modify it under the terms of the GNU General Public License |
782 | + * as published by the Free Software Foundation; either version 2 |
783 | + * of the License, or (at your option) any later version. |
784 | + * |
785 | + * This program is distributed in the hope that it will be useful, |
786 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
787 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
788 | + * GNU General Public License for more details. |
789 | + * |
790 | + * You should have received a copy of the GNU General Public License |
791 | + * along with this program; if not, write to the Free Software |
792 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
793 | + * |
794 | + */ |
795 | + |
796 | +#ifndef WL_NETWORK_NETCLIENTPROXY_H |
797 | +#define WL_NETWORK_NETCLIENTPROXY_H |
798 | + |
799 | +#include <map> |
800 | +#include <memory> |
801 | + |
802 | +#include "network/netclient_interface.h" |
803 | +#include "network/netrelayconnection.h" |
804 | + |
805 | +/** |
806 | + * Represents a client in-game, but talks through the 'wlnr' relay binary. |
807 | + */ |
808 | +class NetClientProxy : public NetClientInterface { |
809 | +public: |
810 | + |
811 | + /** |
812 | + * Tries to connect to the relay at the given address. |
813 | + * \param address The address to connect to. |
814 | + * \param name The name of the game. |
815 | + * \return A pointer to a ready \c NetClientProxy object or a nullptr if the connection failed. |
816 | + */ |
817 | + static std::unique_ptr<NetClientProxy> connect(const NetAddress& address, const std::string& name); |
818 | + |
819 | + /** |
820 | + * Closes the server. |
821 | + */ |
822 | + ~NetClientProxy() override; |
823 | + |
824 | + |
825 | + // Inherited from NetClientInterface |
826 | + bool is_connected() const override; |
827 | + void close() override; |
828 | + std::unique_ptr<RecvPacket> try_receive() override; |
829 | + void send(const SendPacket& packet) override; |
830 | + |
831 | +private: |
832 | + |
833 | + /** |
834 | + * Tries to connect to the relay at the given address. |
835 | + * If it fails, is_connected() will return \c false. |
836 | + * \param address The address to connect to. |
837 | + * \param name The name of the game. |
838 | + */ |
839 | + NetClientProxy(const NetAddress& address, const std::string& name); |
840 | + |
841 | + void receive_commands(); |
842 | + |
843 | + std::unique_ptr<NetRelayConnection> conn_; |
844 | + |
845 | + /// For each connected client, the packages that have been received from him. |
846 | + std::queue<std::unique_ptr<RecvPacket>> received_; |
847 | +}; |
848 | + |
849 | +#endif // end of include guard: WL_NETWORK_NETCLIENTPROXY_H |
850 | |
851 | === modified file 'src/network/nethost.cc' |
852 | --- src/network/nethost.cc 2017-10-17 19:08:59 +0000 |
853 | +++ src/network/nethost.cc 2017-12-05 20:38:52 +0000 |
854 | @@ -127,7 +127,7 @@ |
855 | return true; |
856 | } |
857 | |
858 | -bool NetHost::try_receive(const ConnectionId id, RecvPacket* packet) { |
859 | +std::unique_ptr<RecvPacket> NetHost::try_receive(const ConnectionId id) { |
860 | // Always read all available data into buffers |
861 | uint8_t buffer[kNetworkBufferSize]; |
862 | |
863 | @@ -161,32 +161,35 @@ |
864 | |
865 | // Now check whether there is data for the requested client |
866 | if (!is_connected(id)) |
867 | - return false; |
868 | + return std::unique_ptr<RecvPacket>(); |
869 | |
870 | // Try to get one packet from the deserializer |
871 | - return clients_.at(id).deserializer.write_packet(packet); |
872 | + std::unique_ptr<RecvPacket> packet(new RecvPacket); |
873 | + if (clients_.at(id).deserializer.write_packet(packet.get())) { |
874 | + return packet; |
875 | + } else { |
876 | + return std::unique_ptr<RecvPacket>(); |
877 | + } |
878 | } |
879 | |
880 | void NetHost::send(const ConnectionId id, const SendPacket& packet) { |
881 | boost::system::error_code ec; |
882 | if (is_connected(id)) { |
883 | -#ifdef NDEBUG |
884 | - boost::asio::write( |
885 | - clients_.at(id).socket, boost::asio::buffer(packet.get_data(), packet.get_size()), ec); |
886 | -#else |
887 | size_t written = boost::asio::write( |
888 | clients_.at(id).socket, boost::asio::buffer(packet.get_data(), packet.get_size()), ec); |
889 | -#endif |
890 | |
891 | - // TODO(Notabilis): This one is an assertion of mine, I am not sure if it will hold |
892 | - // If it doesn't, set the socket to blocking before writing |
893 | - // If it does, remove this comment after build 20 |
894 | - assert(ec != boost::asio::error::would_block); |
895 | - assert(written == packet.get_size() || ec); |
896 | + if (ec == boost::asio::error::would_block) { |
897 | + throw wexception("[NetHost] Socket connected to relay would block when writing"); |
898 | + } |
899 | if (ec) { |
900 | log("[NetHost] Error when sending to a client, closing connection: %s.\n", |
901 | ec.message().c_str()); |
902 | close(id); |
903 | + return; |
904 | + } |
905 | + if (written < packet.get_size()) { |
906 | + throw wexception("[NetHost] Unable to send complete packet to relay (only %lu bytes of %lu)", |
907 | + written, packet.get_size()); |
908 | } |
909 | } |
910 | } |
911 | |
912 | === modified file 'src/network/nethost.h' |
913 | --- src/network/nethost.h 2017-11-28 08:57:52 +0000 |
914 | +++ src/network/nethost.h 2017-12-05 20:38:52 +0000 |
915 | @@ -48,7 +48,7 @@ |
916 | bool is_connected(ConnectionId id) const override; |
917 | void close(ConnectionId id) override; |
918 | bool try_accept(ConnectionId* new_id) override; |
919 | - bool try_receive(ConnectionId id, RecvPacket* packet) override; |
920 | + std::unique_ptr<RecvPacket> try_receive(ConnectionId id) override; |
921 | void send(ConnectionId id, const SendPacket& packet) override; |
922 | void send(const std::vector<ConnectionId>& ids, const SendPacket& packet) override; |
923 | |
924 | |
925 | === modified file 'src/network/nethost_interface.h' |
926 | --- src/network/nethost_interface.h 2017-10-17 19:08:59 +0000 |
927 | +++ src/network/nethost_interface.h 2017-12-05 20:38:52 +0000 |
928 | @@ -20,6 +20,8 @@ |
929 | #ifndef WL_NETWORK_NETHOST_INTERFACE_H |
930 | #define WL_NETWORK_NETHOST_INTERFACE_H |
931 | |
932 | +#include <memory> |
933 | + |
934 | #include "network/network.h" |
935 | |
936 | /** |
937 | @@ -66,12 +68,10 @@ |
938 | /** |
939 | * Tries to receive a packet. |
940 | * \param id The connection id of the client that should be received. |
941 | - * \param packet A packet that should be overwritten with the received data. |
942 | - * \return \c true if a packet is available, \c false otherwise. |
943 | - * The given packet is only modified when \c true is returned. |
944 | - * Calling this on a closed connection will return false. |
945 | + * \return A pointer to a packet if one is available, an invalid pointer otherwise. |
946 | + * Calling this on a closed connection will return an invalid pointer. |
947 | */ |
948 | - virtual bool try_receive(ConnectionId id, RecvPacket* packet) = 0; |
949 | + virtual std::unique_ptr<RecvPacket> try_receive(ConnectionId id) = 0; |
950 | |
951 | /** |
952 | * Sends a packet. |
953 | |
954 | === added file 'src/network/nethostproxy.cc' |
955 | --- src/network/nethostproxy.cc 1970-01-01 00:00:00 +0000 |
956 | +++ src/network/nethostproxy.cc 2017-12-05 20:38:52 +0000 |
957 | @@ -0,0 +1,288 @@ |
958 | +#include "network/nethostproxy.h" |
959 | + |
960 | +#include <memory> |
961 | + |
962 | +#include "base/log.h" |
963 | +#include "network/relay_protocol.h" |
964 | + |
965 | +std::unique_ptr<NetHostProxy> NetHostProxy::connect(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password) { |
966 | + std::unique_ptr<NetHostProxy> ptr(new NetHostProxy(addresses, name, password)); |
967 | + if (ptr->conn_ == nullptr || !ptr->conn_->is_connected()) { |
968 | + ptr.reset(); |
969 | + } |
970 | + return ptr; |
971 | +} |
972 | + |
973 | +NetHostProxy::~NetHostProxy() { |
974 | + if (conn_ && conn_->is_connected()) { |
975 | + while (!clients_.empty()) { |
976 | + close(clients_.begin()->first); |
977 | + clients_.erase(clients_.begin()); |
978 | + } |
979 | + conn_->close(); |
980 | + } |
981 | +} |
982 | + |
983 | +bool NetHostProxy::is_connected(const ConnectionId id) const { |
984 | + return clients_.count(id) > 0 && clients_.at(id).state_ == Client::State::kConnected; |
985 | +} |
986 | + |
987 | +void NetHostProxy::close(const ConnectionId id) { |
988 | + auto iter_client = clients_.find(id); |
989 | + if (iter_client == clients_.end()) { |
990 | + // Not connected anyway |
991 | + return; |
992 | + } |
993 | + conn_->send(RelayCommand::kDisconnectClient); |
994 | + conn_->send(id); |
995 | + if (iter_client->second.received_.empty()) { |
996 | + // No pending messages, remove the client |
997 | + clients_.erase(iter_client); |
998 | + } else { |
999 | + // Still messages pending. Keep the structure so the host can receive them |
1000 | + iter_client->second.state_ = Client::State::kDisconnected; |
1001 | + } |
1002 | +} |
1003 | + |
1004 | +bool NetHostProxy::try_accept(ConnectionId* new_id) { |
1005 | + // Always read all available data into buffers |
1006 | + receive_commands(); |
1007 | + |
1008 | + for (auto& entry : clients_) { |
1009 | + if (entry.second.state_ == Client::State::kConnecting) { |
1010 | + *new_id = entry.first; |
1011 | + entry.second.state_ = Client::State::kConnected; |
1012 | + return true; |
1013 | + } |
1014 | + } |
1015 | + return false; |
1016 | +} |
1017 | + |
1018 | +std::unique_ptr<RecvPacket> NetHostProxy::try_receive(const ConnectionId id) { |
1019 | + receive_commands(); |
1020 | + |
1021 | + // Check whether client is not (yet) connected |
1022 | + if (clients_.count(id) == 0 || clients_.at(id).state_ == Client::State::kConnecting) |
1023 | + return std::unique_ptr<RecvPacket>(); |
1024 | + |
1025 | + std::queue<std::unique_ptr<RecvPacket>>& packet_list = clients_.at(id).received_; |
1026 | + |
1027 | + // Now check whether there is data for the requested client |
1028 | + if (packet_list.empty()) { |
1029 | + // If the client is already disconnected it should not be in the map anymore |
1030 | + assert(clients_.at(id).state_ == Client::State::kConnected); |
1031 | + return std::unique_ptr<RecvPacket>(); |
1032 | + } |
1033 | + |
1034 | + std::unique_ptr<RecvPacket> packet = std::move(packet_list.front()); |
1035 | + packet_list.pop(); |
1036 | + if (packet_list.empty() && clients_.at(id).state_ == Client::State::kDisconnected) { |
1037 | + // If the receive buffer is empty now, remove client |
1038 | + clients_.erase(id); |
1039 | + } |
1040 | + return packet; |
1041 | +} |
1042 | + |
1043 | +void NetHostProxy::send(const ConnectionId id, const SendPacket& packet) { |
1044 | + std::vector<ConnectionId> vec; |
1045 | + vec.push_back(id); |
1046 | + send(vec, packet); |
1047 | +} |
1048 | + |
1049 | +void NetHostProxy::send(const std::vector<ConnectionId>& ids, const SendPacket& packet) { |
1050 | + if (ids.empty()) { |
1051 | + return; |
1052 | + } |
1053 | + |
1054 | + receive_commands(); |
1055 | + |
1056 | + bool has_connected_client = false; |
1057 | + for (ConnectionId id : ids) { |
1058 | + if (is_connected(id)) { |
1059 | + // This should be but is not always the case. It can happen that we receive a client disconnect |
1060 | + // on receive_commands() above and the GameHost did not have the chance to react to it yet. |
1061 | + has_connected_client = true; |
1062 | + } |
1063 | + } |
1064 | + if (!has_connected_client) { |
1065 | + // Oops, no clients left to send to |
1066 | + return; |
1067 | + } |
1068 | + |
1069 | + conn_->send(RelayCommand::kToClients); |
1070 | + for (ConnectionId id : ids) { |
1071 | + if (is_connected(id)) { |
1072 | + conn_->send(id); |
1073 | + } |
1074 | + } |
1075 | + conn_->send(0); |
1076 | + conn_->send(packet); |
1077 | +} |
1078 | + |
1079 | +NetHostProxy::NetHostProxy(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password) |
1080 | + : conn_(NetRelayConnection::connect(addresses.first)) { |
1081 | + |
1082 | + if ((conn_ == nullptr || !conn_->is_connected()) && addresses.second.is_valid()) { |
1083 | + conn_ = NetRelayConnection::connect(addresses.second); |
1084 | + } |
1085 | + |
1086 | + if (conn_ == nullptr || !conn_->is_connected()) { |
1087 | + return; |
1088 | + } |
1089 | + |
1090 | + conn_->send(RelayCommand::kHello); |
1091 | + conn_->send(kRelayProtocolVersion); |
1092 | + conn_->send(name); |
1093 | + conn_->send(password); |
1094 | + conn_->send(password); |
1095 | + |
1096 | + // Wait 10 seconds for an answer |
1097 | + uint32_t endtime = time(nullptr) + 10; |
1098 | + while (!NetRelayConnection::Peeker(conn_.get()).cmd()) { |
1099 | + if (time(nullptr) > endtime) { |
1100 | + // No message received in time |
1101 | + conn_->close(); |
1102 | + conn_.reset(); |
1103 | + return; |
1104 | + } |
1105 | + } |
1106 | + |
1107 | + RelayCommand cmd; |
1108 | + conn_->receive(&cmd); |
1109 | + |
1110 | + if (cmd != RelayCommand::kWelcome) { |
1111 | + conn_->close(); |
1112 | + conn_.reset(); |
1113 | + return; |
1114 | + } |
1115 | + |
1116 | + // Check version |
1117 | + endtime = time(nullptr) + 10; |
1118 | + while (!NetRelayConnection::Peeker(conn_.get()).uint8_t()) { |
1119 | + if (time(nullptr) > endtime) { |
1120 | + // No message received in time |
1121 | + conn_->close(); |
1122 | + conn_.reset(); |
1123 | + return; |
1124 | + } |
1125 | + } |
1126 | + uint8_t relay_proto_version; |
1127 | + conn_->receive(&relay_proto_version); |
1128 | + if (relay_proto_version != kRelayProtocolVersion) { |
1129 | + conn_->close(); |
1130 | + conn_.reset(); |
1131 | + return; |
1132 | + } |
1133 | + |
1134 | + // Check game name |
1135 | + endtime = time(nullptr) + 10; |
1136 | + while (!NetRelayConnection::Peeker(conn_.get()).string()) { |
1137 | + if (time(nullptr) > endtime) { |
1138 | + // No message received in time |
1139 | + conn_->close(); |
1140 | + conn_.reset(); |
1141 | + return; |
1142 | + } |
1143 | + } |
1144 | + std::string game_name; |
1145 | + conn_->receive(&game_name); |
1146 | + if (game_name != name) { |
1147 | + conn_->close(); |
1148 | + conn_.reset(); |
1149 | + return; |
1150 | + } |
1151 | +} |
1152 | + |
1153 | +void NetHostProxy::receive_commands() { |
1154 | + if (!conn_->is_connected()) { |
1155 | + // Seems the connection broke at some time. Set all clients to disconnected |
1156 | + for (auto iter_client = clients_.begin(); iter_client != clients_.end(); ) { |
1157 | + if (iter_client->second.received_.empty()) { |
1158 | + // No pending messages, remove the client |
1159 | + clients_.erase(iter_client++); |
1160 | + } else { |
1161 | + // Still messages pending. Keep the structure so the host can receive them |
1162 | + iter_client->second.state_ = Client::State::kDisconnected; |
1163 | + ++iter_client; |
1164 | + } |
1165 | + } |
1166 | + return; |
1167 | + } |
1168 | + |
1169 | + // Receive all available commands |
1170 | + RelayCommand cmd; |
1171 | + NetRelayConnection::Peeker peek(conn_.get()); |
1172 | + if (!peek.cmd(&cmd)) { |
1173 | + // No command to receive |
1174 | + return; |
1175 | + } |
1176 | + switch (cmd) { |
1177 | + case RelayCommand::kDisconnect: |
1178 | + if (peek.string()) { |
1179 | + // Command is completely in the buffer, handle it |
1180 | + conn_->receive(&cmd); |
1181 | + std::string reason; |
1182 | + conn_->receive(&reason); |
1183 | + conn_->close(); |
1184 | + // Set all clients to offline |
1185 | + for (auto& entry : clients_) { |
1186 | + entry.second.state_ = Client::State::kDisconnected; |
1187 | + } |
1188 | + } |
1189 | + break; |
1190 | + case RelayCommand::kConnectClient: |
1191 | + if (peek.uint8_t()) { |
1192 | + conn_->receive(&cmd); |
1193 | + uint8_t id; |
1194 | + conn_->receive(&id); |
1195 | +#ifndef NDEBUG |
1196 | + auto result = clients_.emplace( |
1197 | + std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple()); |
1198 | + assert(result.second); |
1199 | +#else |
1200 | + clients_.emplace( |
1201 | + std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple()); |
1202 | +#endif |
1203 | + } |
1204 | + break; |
1205 | + case RelayCommand::kDisconnectClient: |
1206 | + if (peek.uint8_t()) { |
1207 | + conn_->receive(&cmd); |
1208 | + uint8_t id; |
1209 | + conn_->receive(&id); |
1210 | + assert(clients_.count(id)); |
1211 | + clients_.at(id).state_ = Client::State::kDisconnected; |
1212 | + } |
1213 | + break; |
1214 | + case RelayCommand::kFromClient: |
1215 | + if (peek.uint8_t() && peek.recvpacket()) { |
1216 | + conn_->receive(&cmd); |
1217 | + uint8_t id; |
1218 | + conn_->receive(&id); |
1219 | + std::unique_ptr<RecvPacket> packet(new RecvPacket); |
1220 | + conn_->receive(packet.get()); |
1221 | + assert(clients_.count(id)); |
1222 | + clients_.at(id).received_.push(std::move(packet)); |
1223 | + } |
1224 | + break; |
1225 | + case RelayCommand::kPing: |
1226 | + if (peek.uint8_t()) { |
1227 | + conn_->receive(&cmd); |
1228 | + uint8_t seq; |
1229 | + conn_->receive(&seq); |
1230 | + // Reply with a pong |
1231 | + conn_->send(RelayCommand::kPong); |
1232 | + conn_->send(seq); |
1233 | + } |
1234 | + break; |
1235 | + case RelayCommand::kRoundTripTimeResponse: |
1236 | + conn_->ignore_rtt_response(); |
1237 | + break; |
1238 | + default: |
1239 | + // Other commands should not be possible. |
1240 | + // Then is either something wrong with the protocol or there is an implementation mistake |
1241 | + log("Received command code %i from relay server, do not know what to do with it\n", |
1242 | + static_cast<uint8_t>(cmd)); |
1243 | + NEVER_HERE(); |
1244 | + } |
1245 | +} |
1246 | |
1247 | === added file 'src/network/nethostproxy.h' |
1248 | --- src/network/nethostproxy.h 1970-01-01 00:00:00 +0000 |
1249 | +++ src/network/nethostproxy.h 2017-12-05 20:38:52 +0000 |
1250 | @@ -0,0 +1,103 @@ |
1251 | +/* |
1252 | + * Copyright (C) 2008-2017 by the Widelands Development Team |
1253 | + * |
1254 | + * This program is free software; you can redistribute it and/or |
1255 | + * modify it under the terms of the GNU General Public License |
1256 | + * as published by the Free Software Foundation; either version 2 |
1257 | + * of the License, or (at your option) any later version. |
1258 | + * |
1259 | + * This program is distributed in the hope that it will be useful, |
1260 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1261 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1262 | + * GNU General Public License for more details. |
1263 | + * |
1264 | + * You should have received a copy of the GNU General Public License |
1265 | + * along with this program; if not, write to the Free Software |
1266 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
1267 | + * |
1268 | + */ |
1269 | + |
1270 | +#ifndef WL_NETWORK_NETHOSTPROXY_H |
1271 | +#define WL_NETWORK_NETHOSTPROXY_H |
1272 | + |
1273 | +#include <map> |
1274 | +#include <memory> |
1275 | + |
1276 | +#include "network/nethost_interface.h" |
1277 | +#include "network/netrelayconnection.h" |
1278 | + |
1279 | +/** |
1280 | + * Represents a host in-game, but talks through the 'wlnr' relay binary. |
1281 | + */ |
1282 | +class NetHostProxy : public NetHostInterface { |
1283 | +public: |
1284 | + |
1285 | + /** |
1286 | + * Tries to connect to the relay at the given address. |
1287 | + * \param address The address to connect to. |
1288 | + * \param name The name of the game. |
1289 | + * \param password The password for connecting as host. |
1290 | + * \return A pointer to a ready \c NetHostProxy object or a nullptr if the connection failed. |
1291 | + */ |
1292 | + static std::unique_ptr<NetHostProxy> connect(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password); |
1293 | + |
1294 | + /** |
1295 | + * Closes the server. |
1296 | + */ |
1297 | + ~NetHostProxy() override; |
1298 | + |
1299 | + // Inherited from NetHostInterface |
1300 | + bool is_connected(ConnectionId id) const override; |
1301 | + void close(ConnectionId id) override; |
1302 | + bool try_accept(ConnectionId* new_id) override; |
1303 | + std::unique_ptr<RecvPacket> try_receive(ConnectionId id) override; |
1304 | + void send(ConnectionId id, const SendPacket& packet) override; |
1305 | + void send(const std::vector<ConnectionId>& ids, const SendPacket& packet) override; |
1306 | + |
1307 | +private: |
1308 | + |
1309 | + /** |
1310 | + * Tries to connect to the relay at the given address. |
1311 | + * If it fails, is_connected() will return \c false. |
1312 | + * \param addresses Two possible addresses to connect to. |
1313 | + * \param name The name of the game. |
1314 | + * \param password The password for connecting as host. |
1315 | + */ |
1316 | + NetHostProxy(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password); |
1317 | + |
1318 | + void receive_commands(); |
1319 | + |
1320 | + std::unique_ptr<NetRelayConnection> conn_; |
1321 | + |
1322 | + /// A list of clients which want to connect. |
1323 | + std::queue<ConnectionId> accept_; |
1324 | + |
1325 | + /// The clients connected through the relay |
1326 | + struct Client { |
1327 | + /// The state of the client |
1328 | + enum class State { |
1329 | + /// The relay introduced the client but try_accept() hasn't been called for it yet |
1330 | + kConnecting, |
1331 | + /// A normally connected client |
1332 | + kConnected, |
1333 | + /// The relay told us that the client disconnected but there are still packages in the buffer |
1334 | + kDisconnected |
1335 | + }; |
1336 | + |
1337 | + Client() |
1338 | + : state_(State::kConnecting), received_() { |
1339 | + } |
1340 | + |
1341 | + // deleted since RecvPacket does not offer a copy constructor |
1342 | + Client(const Client& other) = delete; |
1343 | + |
1344 | + /// The current connection state |
1345 | + State state_; |
1346 | + /// The packages that have been received |
1347 | + std::queue<std::unique_ptr<RecvPacket>> received_; |
1348 | + }; |
1349 | + /// The connected clients |
1350 | + std::map<ConnectionId, Client> clients_; |
1351 | +}; |
1352 | + |
1353 | +#endif // end of include guard: WL_NETWORK_NETHOSTPROXY_H |
1354 | |
1355 | === added file 'src/network/netrelayconnection.cc' |
1356 | --- src/network/netrelayconnection.cc 1970-01-01 00:00:00 +0000 |
1357 | +++ src/network/netrelayconnection.cc 2017-12-05 20:38:52 +0000 |
1358 | @@ -0,0 +1,337 @@ |
1359 | +#include "network/netrelayconnection.h" |
1360 | + |
1361 | +#include <memory> |
1362 | + |
1363 | +#include "base/log.h" |
1364 | + |
1365 | +// Not so great: Quite some duplicated code between this class and NetClient. |
1366 | + |
1367 | +NetRelayConnection::Peeker::Peeker(NetRelayConnection *conn) |
1368 | + : conn_(conn), peek_pointer_(0) { |
1369 | + assert(conn_); |
1370 | +} |
1371 | + |
1372 | +bool NetRelayConnection::Peeker::string() { |
1373 | + |
1374 | + // Simple validity check. Should always be true as long as the caller has not used any receive() method. |
1375 | + assert(conn_->buffer_.size() >= peek_pointer_); |
1376 | + |
1377 | + conn_->try_network_receive(); |
1378 | + |
1379 | + if (conn_->buffer_.size() < peek_pointer_ + 1) { |
1380 | + return false; |
1381 | + } |
1382 | + |
1383 | + // A string goes until the next \0 and might have a length of 0 |
1384 | + for (size_t i = peek_pointer_; i < conn_->buffer_.size(); ++i) { |
1385 | + if (conn_->buffer_[i] == '\0') { |
1386 | + peek_pointer_ = i + 1; |
1387 | + return true; |
1388 | + } |
1389 | + } |
1390 | + return false; |
1391 | +} |
1392 | + |
1393 | +bool NetRelayConnection::Peeker::cmd(RelayCommand *out) { |
1394 | + |
1395 | + assert(conn_->buffer_.size() >= peek_pointer_); |
1396 | + |
1397 | + conn_->try_network_receive(); |
1398 | + |
1399 | + if (conn_->buffer_.size() > peek_pointer_) { |
1400 | + if (out != nullptr) { |
1401 | + *out = static_cast<RelayCommand>(conn_->buffer_[peek_pointer_]); |
1402 | + } |
1403 | + peek_pointer_++; |
1404 | + return true; |
1405 | + } |
1406 | + return false; |
1407 | +} |
1408 | + |
1409 | +bool NetRelayConnection::Peeker::uint8_t(::uint8_t *out) { |
1410 | + |
1411 | + assert(conn_->buffer_.size() >= peek_pointer_); |
1412 | + |
1413 | + conn_->try_network_receive(); |
1414 | + |
1415 | + // If there is any byte available, we can read an uint8 |
1416 | + if (conn_->buffer_.size() > peek_pointer_) { |
1417 | + if (out != nullptr) { |
1418 | + *out = static_cast<::uint8_t>(conn_->buffer_[peek_pointer_]); |
1419 | + } |
1420 | + peek_pointer_++; |
1421 | + return true; |
1422 | + } |
1423 | + return false; |
1424 | +} |
1425 | + |
1426 | +bool NetRelayConnection::Peeker::recvpacket() { |
1427 | + |
1428 | + assert(conn_->buffer_.size() >= peek_pointer_); |
1429 | + |
1430 | + conn_->try_network_receive(); |
1431 | + |
1432 | + if (conn_->buffer_.size() < peek_pointer_ + 2) { |
1433 | + // Not even enough space for the size of the recvpacket |
1434 | + return false; |
1435 | + } |
1436 | + |
1437 | + // RecvPackets have their size coded in their first two bytes |
1438 | + const uint16_t size = conn_->buffer_[peek_pointer_ + 0] << 8 | conn_->buffer_[peek_pointer_ + 1]; |
1439 | + assert(size >= 2); |
1440 | + |
1441 | + if (conn_->buffer_.size() >= peek_pointer_ + size) { |
1442 | + peek_pointer_ += size; |
1443 | + return true; |
1444 | + } |
1445 | + return false; |
1446 | +} |
1447 | + |
1448 | +std::unique_ptr<NetRelayConnection> NetRelayConnection::connect(const NetAddress& host) { |
1449 | + std::unique_ptr<NetRelayConnection> ptr(new NetRelayConnection(host)); |
1450 | + if (!ptr->is_connected()) { |
1451 | + ptr.reset(); |
1452 | + } |
1453 | + return ptr; |
1454 | +} |
1455 | + |
1456 | +NetRelayConnection::~NetRelayConnection() { |
1457 | + if (is_connected()) { |
1458 | + close(); |
1459 | + } |
1460 | +} |
1461 | + |
1462 | +bool NetRelayConnection::is_connected() const { |
1463 | + return socket_.is_open(); |
1464 | +} |
1465 | + |
1466 | +void NetRelayConnection::close() { |
1467 | + if (!is_connected()) { |
1468 | + return; |
1469 | + } |
1470 | + boost::system::error_code ec; |
1471 | + boost::asio::ip::tcp::endpoint remote = socket_.remote_endpoint(ec); |
1472 | + if (!ec) { |
1473 | + log("[NetRelayConnection] Closing network socket connected to %s:%i.\n", |
1474 | + remote.address().to_string().c_str(), remote.port()); |
1475 | + } else { |
1476 | + log("[NetRelayConnection] Closing network socket.\n"); |
1477 | + } |
1478 | + socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); |
1479 | + socket_.close(ec); |
1480 | +} |
1481 | + |
1482 | +void NetRelayConnection::receive(std::string *str) { |
1483 | + // Try to receive something from the network. |
1484 | + try_network_receive(); |
1485 | + |
1486 | + #ifndef NDEBUG |
1487 | + // Check if we can read a complete string |
1488 | + assert(Peeker(this).string()); |
1489 | + #endif // NDEBUG |
1490 | + |
1491 | + // Read the string |
1492 | + str->clear(); |
1493 | + // No range check needed, peek_string() takes care of that |
1494 | + while (buffer_.front() != '\0') { |
1495 | + str->push_back(buffer_.front()); |
1496 | + buffer_.pop_front(); |
1497 | + } |
1498 | + // Pop the \0 |
1499 | + buffer_.pop_front(); |
1500 | +} |
1501 | + |
1502 | +void NetRelayConnection::receive(RelayCommand *out) { |
1503 | + try_network_receive(); |
1504 | + |
1505 | + // No complete peeker required here, we only want one byte |
1506 | + assert(!buffer_.empty()); |
1507 | + |
1508 | + uint8_t i; |
1509 | + receive(&i); |
1510 | + *out = static_cast<RelayCommand>(i); |
1511 | +} |
1512 | + |
1513 | +void NetRelayConnection::receive(uint8_t *out) { |
1514 | + try_network_receive(); |
1515 | + |
1516 | + assert(!buffer_.empty()); |
1517 | + |
1518 | + *out = buffer_.front(); |
1519 | + buffer_.pop_front(); |
1520 | +} |
1521 | + |
1522 | +void NetRelayConnection::receive(RecvPacket *packet) { |
1523 | + try_network_receive(); |
1524 | + |
1525 | + |
1526 | + #ifndef NDEBUG |
1527 | + assert(Peeker(this).recvpacket()); |
1528 | + #endif // NDEBUG |
1529 | + |
1530 | + // Read the packet (adapted copy from Deserializer) |
1531 | + const uint16_t size = buffer_[0] << 8 | buffer_[1]; |
1532 | + assert(size >= 2); |
1533 | + assert(buffer_.size() >= size); |
1534 | + |
1535 | + packet->buffer.clear(); |
1536 | + packet->buffer.insert(packet->buffer.end(), buffer_.begin() + 2, buffer_.begin() + size); |
1537 | + packet->index_ = 0; |
1538 | + |
1539 | + buffer_.erase(buffer_.begin(), buffer_.begin() + size); |
1540 | +} |
1541 | + |
1542 | +bool NetRelayConnection::try_network_receive() { |
1543 | + if (!is_connected()) { |
1544 | + return false; |
1545 | + } |
1546 | + |
1547 | + unsigned char buffer[kNetworkBufferSize]; |
1548 | + boost::system::error_code ec; |
1549 | + size_t length = socket_.read_some(boost::asio::buffer(buffer, kNetworkBufferSize), ec); |
1550 | + if (!ec) { |
1551 | + assert(length > 0); |
1552 | + assert(length <= kNetworkBufferSize); |
1553 | + // Has read something |
1554 | + for (size_t i = 0; i < length; ++i) { |
1555 | + buffer_.push_back(buffer[i]); |
1556 | + } |
1557 | + } |
1558 | + |
1559 | + if (ec && ec != boost::asio::error::would_block) { |
1560 | + // Connection closed or some error, close the socket |
1561 | + log("[NetRelayConnection] Error when trying to receive some data: %s.\n", ec.message().c_str()); |
1562 | + close(); |
1563 | + return false; |
1564 | + } |
1565 | + return true; |
1566 | +} |
1567 | + |
1568 | +void NetRelayConnection::send(const RelayCommand data) { |
1569 | + send(static_cast<uint8_t>(data)); |
1570 | +} |
1571 | + |
1572 | +void NetRelayConnection::send(const uint8_t data) { |
1573 | + if (!is_connected()) { |
1574 | + return; |
1575 | + } |
1576 | + |
1577 | + uint8_t buf[1]; |
1578 | + buf[0] = data; |
1579 | + |
1580 | + boost::system::error_code ec; |
1581 | + size_t written = boost::asio::write(socket_, boost::asio::buffer(buf, 1), ec); |
1582 | + |
1583 | + if (ec == boost::asio::error::would_block) { |
1584 | + throw wexception("[NetRelayConnection] Socket connected to relay would block when writing"); |
1585 | + } |
1586 | + if (written < 1) { |
1587 | + throw wexception("[NetRelayConnection] Unable to send byte to relay"); |
1588 | + } |
1589 | + if (ec) { |
1590 | + log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str()); |
1591 | + close(); |
1592 | + } |
1593 | +} |
1594 | + |
1595 | +void NetRelayConnection::send(const std::string& str) { |
1596 | + if (!is_connected()) { |
1597 | + return; |
1598 | + } |
1599 | + |
1600 | + // Append \0 |
1601 | + std::vector<boost::asio::const_buffer> buffers; |
1602 | + buffers.push_back(boost::asio::buffer(str)); |
1603 | + buffers.push_back(boost::asio::buffer("\0", 1)); |
1604 | + |
1605 | + boost::system::error_code ec; |
1606 | + size_t written = boost::asio::write(socket_, buffers, ec); |
1607 | + |
1608 | + if (ec == boost::asio::error::would_block) { |
1609 | + throw wexception("[NetRelayConnection] Socket connected to relay would block when writing"); |
1610 | + } |
1611 | + if (written < str.length() + 1) { |
1612 | + throw wexception( |
1613 | + "[NetRelayConnection] Unable to send complete string to relay (only %lu bytes of %lu)", |
1614 | + written, str.length() + 1); |
1615 | + } |
1616 | + if (ec) { |
1617 | + log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str()); |
1618 | + close(); |
1619 | + } |
1620 | +} |
1621 | + |
1622 | +void NetRelayConnection::send(const SendPacket& packet) { |
1623 | + if (!is_connected()) { |
1624 | + return; |
1625 | + } |
1626 | + |
1627 | + boost::system::error_code ec; |
1628 | + size_t written = |
1629 | + boost::asio::write(socket_, boost::asio::buffer(packet.get_data(), packet.get_size()), ec); |
1630 | + |
1631 | + if (ec == boost::asio::error::would_block) { |
1632 | + throw wexception("[NetRelayConnection] Socket connected to relay would block when writing"); |
1633 | + } |
1634 | + if (ec) { |
1635 | + log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str()); |
1636 | + close(); |
1637 | + return; |
1638 | + } |
1639 | + if (written < packet.get_size()) { |
1640 | + throw wexception( |
1641 | + "[NetRelayConnection] Unable to send complete packet to relay (only %lu bytes of %lu)", |
1642 | + written, packet.get_size()); |
1643 | + } |
1644 | +} |
1645 | + |
1646 | +NetRelayConnection::NetRelayConnection(const NetAddress& host) |
1647 | + : io_service_(), socket_(io_service_), buffer_() { |
1648 | + |
1649 | + assert(host.is_valid()); |
1650 | + const boost::asio::ip::tcp::endpoint destination(host.ip, host.port); |
1651 | + |
1652 | + log("[NetRelayConnection]: Trying to connect to %s:%u ... ", host.ip.to_string().c_str(), host.port); |
1653 | + boost::system::error_code ec; |
1654 | + socket_.connect(destination, ec); |
1655 | + if (!ec && is_connected()) { |
1656 | + log("success.\n"); |
1657 | + socket_.non_blocking(true); |
1658 | + } else { |
1659 | + log("failed.\n"); |
1660 | + socket_.close(); |
1661 | + assert(!is_connected()); |
1662 | + } |
1663 | +} |
1664 | + |
1665 | +void NetRelayConnection::ignore_rtt_response() { |
1666 | + |
1667 | + // TODO(Notabilis): Implement GUI with display of RTTs and possibility to kick lagging players |
1668 | + // See https://bugs.launchpad.net/widelands/+bug/1734673 |
1669 | + // TODO(Notabilis): Move this method somewhere where it makes sense. |
1670 | + |
1671 | + uint8_t length_list = 0; |
1672 | + RelayCommand cmd; |
1673 | + uint8_t tmp; |
1674 | + |
1675 | + Peeker peek(this); |
1676 | + peek.cmd(&cmd); |
1677 | + assert(cmd == RelayCommand::kRoundTripTimeResponse); |
1678 | + |
1679 | + bool data_complete = peek.uint8_t(&length_list); |
1680 | + // Each list element consists of three uint8_t |
1681 | + for (uint8_t i = 0; i < length_list * 3; i++) { |
1682 | + data_complete = data_complete && peek.uint8_t(); |
1683 | + } |
1684 | + if (!data_complete) { |
1685 | + // Some part of this packet is still missing. Try again later |
1686 | + return; |
1687 | + } |
1688 | + |
1689 | + // Packet completely in buffer, fetch it and ignore it |
1690 | + receive(&cmd); // Cmd |
1691 | + receive(&tmp); // Length |
1692 | + for (uint8_t i = 0; i < length_list * 3; i++) { |
1693 | + receive(&tmp); // Parts of the list. See relay_protocol.h |
1694 | + } |
1695 | +} |
1696 | |
1697 | === added file 'src/network/netrelayconnection.h' |
1698 | --- src/network/netrelayconnection.h 1970-01-01 00:00:00 +0000 |
1699 | +++ src/network/netrelayconnection.h 2017-12-05 20:38:52 +0000 |
1700 | @@ -0,0 +1,225 @@ |
1701 | +/* |
1702 | + * Copyright (C) 2008-2017 by the Widelands Development Team |
1703 | + * |
1704 | + * This program is free software; you can redistribute it and/or |
1705 | + * modify it under the terms of the GNU General Public License |
1706 | + * as published by the Free Software Foundation; either version 2 |
1707 | + * of the License, or (at your option) any later version. |
1708 | + * |
1709 | + * This program is distributed in the hope that it will be useful, |
1710 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1711 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1712 | + * GNU General Public License for more details. |
1713 | + * |
1714 | + * You should have received a copy of the GNU General Public License |
1715 | + * along with this program; if not, write to the Free Software |
1716 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
1717 | + * |
1718 | + */ |
1719 | + |
1720 | +#ifndef WL_NETWORK_NETRELAYCONNECTION_H |
1721 | +#define WL_NETWORK_NETRELAYCONNECTION_H |
1722 | + |
1723 | +#include <memory> |
1724 | + |
1725 | +#include "network/network.h" |
1726 | +#include "network/relay_protocol.h" |
1727 | + |
1728 | +/** |
1729 | + * A wrapper around a network connection to the 'wlnr' binary in the metaserver repo. |
1730 | + * Does not contain logic but provides a buffer to read |
1731 | + * uint8_t / std::string / SendPacket from the network stream. |
1732 | + * |
1733 | + * Use the Peeker class to check whether the required data has already been received |
1734 | + * before trying to read the data. |
1735 | + */ |
1736 | +class NetRelayConnection { |
1737 | +public: |
1738 | + |
1739 | + /** |
1740 | + * Allows to check whether the required data is completely in the buffer before starting to receive it. |
1741 | + * |
1742 | + * The idea of this methods is that the caller can check whether a complete relay command |
1743 | + * can be received before starting to remove data from the buffer. Otherwise, the caller would have to |
1744 | + * maintain their own buffer. |
1745 | + * |
1746 | + * The methods will not remove any bytes, but will check whether the required value can be read. |
1747 | + * After checking, an internal peek-pointer will be incremented. The next call of a method will then |
1748 | + * start reading after the previous value. Only a successful peek will move the pointer. |
1749 | + * |
1750 | + * Note that a successful peek does not mean that the received bytes really are of the requested type, |
1751 | + * it only means they could be interpreted that way. Whether the type matches is in the responsibility |
1752 | + * of the caller. |
1753 | + * |
1754 | + * Using any method of this class will trigger an internal read from the network socket inside the |
1755 | + * given NetRelayConnection. |
1756 | + * |
1757 | + * \warning Calling any receive() method on the given connection will invalidate the Peeker. |
1758 | + */ |
1759 | + class Peeker { |
1760 | + public: |
1761 | + |
1762 | + /** |
1763 | + * Creates a Peeker for the given connection. |
1764 | + * Calling any receive() method on the given connection will invalidate the Peeker. |
1765 | + * \param conn The connection which should be peeked into. |
1766 | + * \note The Peeker instance does not own the given connection. It is the responsible of the |
1767 | + * caller to make sure the given instance stays valid. |
1768 | + */ |
1769 | + Peeker(NetRelayConnection *conn); |
1770 | + |
1771 | + /** |
1772 | + * Checks whether a relay command can be read from the buffer. |
1773 | + * This method does not modify the buffer contents but increases the peek-pointer. |
1774 | + * \param out The command that will be returned next. It will not be removed from the input queue! |
1775 | + * If \c false is returned the contents are not modified. Can be nullptr. |
1776 | + * \return \c True if the value can be read, \c false if not enough data has been received. |
1777 | + */ |
1778 | + bool cmd(RelayCommand *out = nullptr); |
1779 | + |
1780 | + /** |
1781 | + * Checks whether an uint8_t can be read from the buffer. |
1782 | + * This method does not modify the buffer contents but increases the peek-pointer. |
1783 | + * \param out The uint8_t that will be returned next. It will not be removed from the input queue! |
1784 | + * If \c false is returned the contents are not modified. Can be nullptr. |
1785 | + * \return \c True if the value can be read, \c false if not enough data has been received. |
1786 | + */ |
1787 | + bool uint8_t(uint8_t *out = nullptr); |
1788 | + |
1789 | + /** |
1790 | + * Checks whether a std::string can be read from the buffer. |
1791 | + * This method does not modify the buffer contents but increases the peek-pointer. |
1792 | + * \return \c True if the value can be read, \c false if not enough data has been received. |
1793 | + */ |
1794 | + bool string(); |
1795 | + |
1796 | + /** |
1797 | + * Checks whether a RecvPacket can be read from the buffer. |
1798 | + * This method does not modify the buffer contents but increases the peek-pointer. |
1799 | + * \return \c True if the value can be read, \c false if not enough data has been received. |
1800 | + */ |
1801 | + bool recvpacket(); |
1802 | + |
1803 | + private: |
1804 | + |
1805 | + /// The connection to operate on. |
1806 | + NetRelayConnection *conn_; |
1807 | + |
1808 | + /// The position of the next peek. |
1809 | + size_t peek_pointer_; |
1810 | + }; |
1811 | + |
1812 | + /** |
1813 | + * Tries to establish a connection to the given relay. |
1814 | + * \param host The host to connect to. |
1815 | + * \return A pointer to a connected \c NetRelayConnection object or a \c nullptr. |
1816 | + */ |
1817 | + static std::unique_ptr<NetRelayConnection> connect(const NetAddress& host); |
1818 | + |
1819 | + /** |
1820 | + * Closes the connection. |
1821 | + * If you want to send a goodbye-message to the relay, do so before freeing the object. |
1822 | + */ |
1823 | + ~NetRelayConnection(); |
1824 | + |
1825 | + /** |
1826 | + * Returns whether the relay is connected. |
1827 | + * \return \c true if the connection is open, \c false otherwise. |
1828 | + */ |
1829 | + bool is_connected() const; |
1830 | + |
1831 | + /** |
1832 | + * Closes the connection. |
1833 | + * If you want to send a goodbye-message to the relay, do so before calling this. |
1834 | + */ |
1835 | + void close(); |
1836 | + |
1837 | + /** |
1838 | + * Receive a command. |
1839 | + * \warning Calling this method is only safe when peek_cmd() returned \c true. |
1840 | + * Otherwise the behavior of this method is undefined. |
1841 | + * \param out The variable to write the value to. |
1842 | + */ |
1843 | + void receive(RelayCommand *out); |
1844 | + |
1845 | + /** |
1846 | + * Receive an uint8_t. |
1847 | + * \warning Calling this method is only safe when peek_uint8_t() returned \c true. |
1848 | + * Otherwise the behavior of this method is undefined. |
1849 | + * \param out The variable to write the value to. |
1850 | + */ |
1851 | + void receive(uint8_t *out); |
1852 | + |
1853 | + /** |
1854 | + * Receive a string. |
1855 | + * \warning Calling this method is only safe when peek_string() returned \c true. |
1856 | + * Otherwise the behavior of this method is undefined. |
1857 | + * \param out The variable to write the value to. |
1858 | + */ |
1859 | + void receive(std::string *out); |
1860 | + |
1861 | + /** |
1862 | + * Receive a RecvPacket. |
1863 | + * \warning Calling this method is only safe when peek_recvpacket() returned \c true. |
1864 | + * Otherwise the behavior of this method is undefined. |
1865 | + * \param out The variable to write the value to. |
1866 | + */ |
1867 | + void receive(RecvPacket *out); |
1868 | + |
1869 | + /** |
1870 | + * Sends a relay command. |
1871 | + * Calling this on a closed connection will silently fail. |
1872 | + * \param data The data to send. |
1873 | + */ |
1874 | + void send(RelayCommand data); |
1875 | + |
1876 | + /** |
1877 | + * Sends an uint8_t. |
1878 | + * Calling this on a closed connection will silently fail. |
1879 | + * \param data The data to send. |
1880 | + */ |
1881 | + void send(uint8_t data); |
1882 | + |
1883 | + /** |
1884 | + * Sends a string. |
1885 | + * Calling this on a closed connection will silently fail. |
1886 | + * \param data The data to send. |
1887 | + */ |
1888 | + void send(const std::string& data); |
1889 | + |
1890 | + /** |
1891 | + * Sends a packet. |
1892 | + * Calling this on a closed connection will silently fail. |
1893 | + * \param data The data to send. |
1894 | + */ |
1895 | + void send(const SendPacket& data); |
1896 | + |
1897 | + // Temporary method, will be removed. |
1898 | + // Removes a message from type kRoundTripTimeResponse from the buffer. |
1899 | + void ignore_rtt_response(); |
1900 | + |
1901 | +private: |
1902 | + /** |
1903 | + * Tries to establish a connection to the given host. |
1904 | + * If the connection attempt failed, is_connected() will return \c false. |
1905 | + * \param host The host to connect to. |
1906 | + */ |
1907 | + explicit NetRelayConnection(const NetAddress& host); |
1908 | + |
1909 | + /** |
1910 | + * Reads data from network. |
1911 | + * \return \c False if an error occurred. |
1912 | + */ |
1913 | + bool try_network_receive(); |
1914 | + |
1915 | + /// An io_service needed by boost.asio. Primarily needed for asynchronous operations. |
1916 | + boost::asio::io_service io_service_; |
1917 | + |
1918 | + /// The socket that connects us to the relay. |
1919 | + boost::asio::ip::tcp::socket socket_; |
1920 | + |
1921 | + /// Buffer for arriving data. We need to store it until we have enough to return the required type. |
1922 | + std::deque<unsigned char> buffer_; |
1923 | +}; |
1924 | + |
1925 | +#endif // end of include guard: WL_NETWORK_NETRELAYCONNECTION_H |
1926 | |
1927 | === modified file 'src/network/network.cc' |
1928 | --- src/network/network.cc 2017-07-05 19:21:57 +0000 |
1929 | +++ src/network/network.cc 2017-12-05 20:38:52 +0000 |
1930 | @@ -171,6 +171,8 @@ |
1931 | if (buffer.empty()) { |
1932 | buffer.push_back(0); // this will finally be the length of the packet |
1933 | buffer.push_back(0); |
1934 | + // Attention! These bytes are also used by the network relay protocol. |
1935 | + // So if they are removed the protocol has to be updated |
1936 | } |
1937 | |
1938 | for (size_t idx = 0; idx < size; ++idx) |
1939 | @@ -199,7 +201,6 @@ |
1940 | } |
1941 | |
1942 | /*** class RecvPacket ***/ |
1943 | - |
1944 | size_t RecvPacket::data(void* const packet_data, size_t const bufsize) { |
1945 | if (index_ + bufsize > buffer.size()) |
1946 | throw wexception("Packet too short"); |
1947 | |
1948 | === modified file 'src/network/network.h' |
1949 | --- src/network/network.h 2017-08-09 17:55:34 +0000 |
1950 | +++ src/network/network.h 2017-12-05 20:38:52 +0000 |
1951 | @@ -171,6 +171,7 @@ |
1952 | |
1953 | private: |
1954 | friend class Deserializer; |
1955 | + friend class NetRelayConnection; |
1956 | std::vector<uint8_t> buffer; |
1957 | size_t index_ = 0U; |
1958 | }; |
1959 | |
1960 | === added file 'src/network/relay_protocol.h' |
1961 | --- src/network/relay_protocol.h 1970-01-01 00:00:00 +0000 |
1962 | +++ src/network/relay_protocol.h 2017-12-05 20:38:52 +0000 |
1963 | @@ -0,0 +1,242 @@ |
1964 | +/* |
1965 | + * Copyright (C) 2012-2017 by the Widelands Development Team |
1966 | + * |
1967 | + * This program is free software; you can redistribute it and/or |
1968 | + * modify it under the terms of the GNU General Public License |
1969 | + * as published by the Free Software Foundation; either version 2 |
1970 | + * of the License, or (at your option) any later version. |
1971 | + * |
1972 | + * This program is distributed in the hope that it will be useful, |
1973 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1974 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1975 | + * GNU General Public License for more details. |
1976 | + * |
1977 | + * You should have received a copy of the GNU General Public License |
1978 | + * along with this program; if not, write to the Free Software |
1979 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
1980 | + * |
1981 | + */ |
1982 | + |
1983 | +#ifndef WL_NETWORK_RELAY_PROTOCOL_H |
1984 | +#define WL_NETWORK_RELAY_PROTOCOL_H |
1985 | + |
1986 | +/** |
1987 | + * Before the internet gaming relay was added, (a) NetHost and (multiple) NetClient established a direct |
1988 | + * connection and exchanged data packets created and processed by the GameHost/GameClient. With the |
1989 | + * introduction of the relay this changed: GameHost/GameClient still create and process the data packets. |
1990 | + * For LAN games, they still pass them to the NetHost/NetClient. |
1991 | + * For internet games, the traffic is relayed by the wlnr binary in the metaserver repository. |
1992 | + * GameHost/GameClient pass their packets to the new classes NetHostProxy/NetClientProxy. |
1993 | + * Those no longer have a direct connection but are both connected to the relay on the metaserver. |
1994 | + * When they want to exchange messages, they send their packets to the relay which forwards them to the |
1995 | + * intended recipient. |
1996 | + * The relay only transport the packets, it does not run any game logic. The idea of this is that the |
1997 | + * relay runs on an globally reachable computer (i.e. the one of the metaserver) and simplifies |
1998 | + * connectivity for the users (i.e. no more port forwarding required). |
1999 | + * |
2000 | + * Below are the command codes used in the relay protocol. They are used on the connection |
2001 | + * between NetHostProxy/NetClientProxy and relay. |
2002 | + * |
2003 | + * An example of a typical session: |
2004 | + * 1) A user wants to start an internet game. The Widelands instance tells the metaserver about it. |
2005 | + * 2) A relay instance is started by the metaserver. On startup of the relay, the nonce of the player is set |
2006 | + * as password for the game-host position in the new game. Additionally, the name of the hosted game |
2007 | + * is stored. The IP address of the relay is send to the Widelands instance by the metaserver. |
2008 | + * 3) The Widelands instance of the user connects to the relay. |
2009 | + * 4) Now there is a reachable relay/game running somewhere. The open game can be listed in the lobby. |
2010 | + * 5) Clients get the address and port of the relay by the metaserver and can connect to the game. |
2011 | + * When enough clients have connected, the GameHost can start the game. |
2012 | + * 6) When a client wants to send a packet (e.g. user input) to the GameHost, its GameClient packs the packet, |
2013 | + * passes it to the local NetClientProxy which sends it to the relay. The relay relays the packet to the |
2014 | + * NetHostProxy which passes it to the GameHost. |
2015 | + * 7) When the GameHost wants to send a packet to one or all clients, it also packs it and passes it to its |
2016 | + * NetHostProxy which sends it to the relay, where it is forwarded to the client(s). |
2017 | + */ |
2018 | + |
2019 | + |
2020 | +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
2021 | + * CONSTANTS |
2022 | + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
2023 | + |
2024 | +/** |
2025 | + * The current version of the network protocol. |
2026 | + * Protocol versions must match on all systems. |
2027 | + * Used versions: |
2028 | + * 1: Initial version introduced between build 19 and build 20 |
2029 | + */ |
2030 | +constexpr uint8_t kRelayProtocolVersion = 1; |
2031 | + |
2032 | +/** |
2033 | + * The commands used by the relay. |
2034 | + * |
2035 | + * If a command is followed by parameters, they are listed in the description. |
2036 | + * Most of these commands do not require any reaction by the receiver. |
2037 | + * Exceptions are described at the respective commands. |
2038 | + * |
2039 | + * Parameter types: |
2040 | + * |
2041 | + * Strings are NULL-terminated. |
2042 | + * |
2043 | + * Transmitting data packets: |
2044 | + * |
2045 | + * The main work of the relay consists of passing opaque data packets between GameHost |
2046 | + * and GameClients. They send SendPacket's over the network and expect to receive RecvPacket's |
2047 | + * (see network.h). Those packets basically consists of an array of uint8_t with the first two bytes |
2048 | + * coding the length of the packet. |
2049 | + * When in the following documentation the \c packet datatype is listed as payload data, such a packet |
2050 | + * is meant. Since they are already containing their own length, no explicit length field or separator |
2051 | + * is required to pack them. When sending they are just written out. When receiving, the Deserializer |
2052 | + * has to read the length first and read the appropriate amount of bytes afterwards. |
2053 | + */ |
2054 | +/** |
2055 | + * This protocol is a binary protocol (as in: not human readable). The receiver reads only the first byte |
2056 | + * of the received data to determine which command has been send. Based on that, it knows whether to expect |
2057 | + * a string / an uint8 / a SendPacket. Based on this, it can read until NULL / one byte / as much |
2058 | + * as the next two bytes specify. When all parameters have been read the next command starts |
2059 | + * without any separator. |
2060 | + */ |
2061 | + |
2062 | +// If anyone removes a command: Please leave a comment which command with which value was removed |
2063 | +enum class RelayCommand : uint8_t { |
2064 | + // Value 0 should not be used |
2065 | + |
2066 | + /** |
2067 | + * Commands send by/to all participants. |
2068 | + */ |
2069 | + /// \{ |
2070 | + /** |
2071 | + * Sent by the NetHostProxy or NetClientProxy on connect to the relay. |
2072 | + * Has the following payload: |
2073 | + * \li unsigned_8: Protocol version. |
2074 | + * \li string: Game name. |
2075 | + * \li string: For the host: Password that was set on start of the relay. |
2076 | + * For clients/observers: String "client". |
2077 | + * |
2078 | + * Is answered by kWelcome or kDisconnect (if a parameter is wrong/unknown). |
2079 | + */ |
2080 | + kHello = 1, |
2081 | + |
2082 | + /** |
2083 | + * Send by the relay to answer a kHello command. |
2084 | + * Confirms the successful connection with the relay. In the case of a connecting NetClientProxy, |
2085 | + * this does not mean that it can participate on the game. This decision is up to the GameHost as |
2086 | + * soon as it learns about the new client. |
2087 | + * Payload: |
2088 | + * \li unsigned_8: The protocol version. Should be the same as the one send in the kHello. |
2089 | + * \li string: The name of the hosted game as shown in the internet lobby. |
2090 | + * The client might want to check this. |
2091 | + * |
2092 | + * This command and its parameters are not really necessary. But it might be nice to have at least some |
2093 | + * confirmation that we have a connection to a (and the right) relay and not to some other server. |
2094 | + */ |
2095 | + kWelcome = 2, |
2096 | + |
2097 | + /** |
2098 | + * Can be sent by any participant. |
2099 | + * Might be the result of a protocol violation, an invalid password on connect of the NetHostProxy |
2100 | + * or a regular disconnect (e.g. the game is over). |
2101 | + * After sending or receiving this command, the TCP connection should be closed. |
2102 | + * \note When the game host sends its kDisconnect message, the relay will shut down. |
2103 | + * Payload: |
2104 | + * \li string: An error code describing the reason of the disconnect. Valid values: |
2105 | + * NORMAL: Regular disconnect (game has ended, host leaves, client leaves, ...); |
2106 | + * PROTOCOL_VIOLATION: Some protocol error (unknown command, invalid parameters, ...); |
2107 | + * WRONG_VERSION: The version in the kHello packet is not supported; |
2108 | + * GAME_UNKNOWN: Game name provided in kHello packet is unknown; |
2109 | + * NO_HOST: No host is connected to the relay yet; |
2110 | + * INVALID_CLIENT: Host tried to send a message to a non-existing client |
2111 | + */ |
2112 | + kDisconnect = 3, |
2113 | + |
2114 | + /** |
2115 | + * The relay sends this message to check for presence and to measure the round-trip-time. |
2116 | + * Has to be answered by kPong immediately. |
2117 | + * Payload: |
2118 | + * \li unsigned_8: A sequence number for this ping request. |
2119 | + */ |
2120 | + kPing = 4, |
2121 | + |
2122 | + /** |
2123 | + * Send to the relay to answer a kPing message. |
2124 | + * Payload: |
2125 | + * \li unsigned_8: Should be the sequence number found in the ping request. |
2126 | + */ |
2127 | + kPong = 5, |
2128 | + |
2129 | + /** |
2130 | + * Send to the relay to request the newest ping results. |
2131 | + * No payload. |
2132 | + */ |
2133 | + kRoundTripTimeRequest = 6, |
2134 | + |
2135 | + /** |
2136 | + * Send by the relay as an answer to the kRoundTripTimeRequest with the following payload: |
2137 | + * \li unsigned_8: Length of the list. |
2138 | + * A list of |
2139 | + * \li unsigned_8: Id of the client. |
2140 | + * \li unsigned_8: The RTT in milliseconds. Capped to max. 255ms. |
2141 | + * \li unsigned_8: Seconds since the last kPong has been received by the relay. Capped to max. 255ms. |
2142 | + */ |
2143 | + kRoundTripTimeResponse = 7, |
2144 | + /// \} |
2145 | + |
2146 | + /** |
2147 | + * Communication between relay and NetHostProxy. |
2148 | + */ |
2149 | + /// \{ |
2150 | + |
2151 | + /** |
2152 | + * Send by the relay to the NetHostProxy to inform that a client established a connection to the relay. |
2153 | + * Payload: |
2154 | + * \li unsigned_8: An id to represent the new client. |
2155 | + */ |
2156 | + kConnectClient = 11, |
2157 | + |
2158 | + /** |
2159 | + * If send by the NetHostProxy, tells the relay to close the connection to a client. |
2160 | + * If send by the relay, informs the NetHostProxy that the connection to a client has been lost. |
2161 | + * Payload: |
2162 | + * \li unsigned_8: The id of the client. |
2163 | + */ |
2164 | + kDisconnectClient = 12, |
2165 | + |
2166 | + /** |
2167 | + * The NetHostProxy sends a message to a connected client over the relay. |
2168 | + * Payload: |
2169 | + * \li NULL terminated list of unsigned_8: The ids of the clients. |
2170 | + * \li packet: The SendPacket to relay. |
2171 | + */ |
2172 | + kToClients = 13, |
2173 | + |
2174 | + /** |
2175 | + * The relay transmits a packet from a client to the NetHostProxy. |
2176 | + * Payload: |
2177 | + * \li unsigned_8: The id of the client. |
2178 | + * \li packet: The SendPacket to relay. |
2179 | + */ |
2180 | + kFromClient = 14, |
2181 | + /// \} |
2182 | + |
2183 | + /** |
2184 | + * Communication between relay and NetHostProxy. |
2185 | + */ |
2186 | + /// \{ |
2187 | + |
2188 | + /** |
2189 | + * The NetClientProxy sends a message to the NetHostProxy over the relay. |
2190 | + * Direct communication between clients is not supported. |
2191 | + * Payload: |
2192 | + * \li packet: The SendPacket to relay. |
2193 | + */ |
2194 | + kToHost = 21, |
2195 | + |
2196 | + /** |
2197 | + * The relay transmits a packet from a NetHostProxy to the NetClientProxy. |
2198 | + * Payload: |
2199 | + * \li packet: The SendPacket to relay. |
2200 | + */ |
2201 | + kFromHost = 22 |
2202 | + /// \} |
2203 | +}; |
2204 | + |
2205 | +#endif // end of include guard: WL_NETWORK_RELAY_PROTOCOL_H |
2206 | |
2207 | === modified file 'src/ui_fsmenu/internet_lobby.cc' |
2208 | --- src/ui_fsmenu/internet_lobby.cc 2017-11-25 20:29:26 +0000 |
2209 | +++ src/ui_fsmenu/internet_lobby.cc 2017-12-05 20:38:52 +0000 |
2210 | @@ -193,7 +193,7 @@ |
2211 | void FullscreenMenuInternetLobby::connect_to_metaserver() { |
2212 | Section& s = g_options.pull_section("global"); |
2213 | const std::string& metaserver = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str()); |
2214 | - uint32_t port = s.get_natural("metaserverport", INTERNET_GAMING_PORT); |
2215 | + uint32_t port = s.get_natural("metaserverport", kInternetGamingPort); |
2216 | std::string auth = is_registered_ ? password_ : s.get_string("uuid"); |
2217 | assert(!auth.empty()); |
2218 | InternetGaming::ref().login(nickname_, auth, is_registered_, metaserver, port); |
2219 | @@ -352,30 +352,40 @@ |
2220 | } |
2221 | } |
2222 | |
2223 | +bool FullscreenMenuInternetLobby::wait_for_ip() { |
2224 | + // Wait until the metaserver provided us with an IP address |
2225 | + uint32_t const secs = time(nullptr); |
2226 | + while (!InternetGaming::ref().ips().first.is_valid()) { |
2227 | + InternetGaming::ref().handle_metaserver_communication(); |
2228 | + // give some time for the answer + for a relogin, if a problem occurs. |
2229 | + if ((kInternetGamingTimeout * 5 / 3) < time(nullptr) - secs) { |
2230 | + // Show a popup warning message |
2231 | + const std::string warning( |
2232 | + _("Widelands was unable to get the IP address of the server in time. " |
2233 | + "There seems to be a network problem, either on your side or on the side " |
2234 | + "of the server.\n")); |
2235 | + UI::WLMessageBox mmb(this, _("Connection timed out"), warning, |
2236 | + UI::WLMessageBox::MBoxType::kOk, UI::Align::kLeft); |
2237 | + mmb.run<UI::Panel::Returncodes>(); |
2238 | + InternetGaming::ref().set_error(); |
2239 | + return false; |
2240 | + } |
2241 | + } |
2242 | + return true; |
2243 | +} |
2244 | + |
2245 | /// called when the 'join game' button was clicked |
2246 | void FullscreenMenuInternetLobby::clicked_joingame() { |
2247 | if (opengames_list_.has_selection()) { |
2248 | InternetGaming::ref().join_game(opengames_list_.get_selected().name); |
2249 | |
2250 | - uint32_t const secs = time(nullptr); |
2251 | - while (!InternetGaming::ref().ips().first.is_valid()) { |
2252 | - InternetGaming::ref().handle_metaserver_communication(); |
2253 | - // give some time for the answer + for a relogin, if a problem occurs. |
2254 | - if ((INTERNET_GAMING_TIMEOUT * 5 / 3) < time(nullptr) - secs) { |
2255 | - // Show a popup warning message |
2256 | - const std::string warning( |
2257 | - _("Widelands was unable to get the IP address of the server in time.\n" |
2258 | - "There seems to be a network problem, either on your side or on the side\n" |
2259 | - "of the server.\n")); |
2260 | - UI::WLMessageBox mmb(this, _("Connection timed out"), warning, |
2261 | - UI::WLMessageBox::MBoxType::kOk, UI::Align::kLeft); |
2262 | - mmb.run<UI::Panel::Returncodes>(); |
2263 | - return InternetGaming::ref().set_error(); |
2264 | - } |
2265 | + if (!wait_for_ip()) { |
2266 | + return; |
2267 | } |
2268 | const std::pair<NetAddress, NetAddress>& ips = InternetGaming::ref().ips(); |
2269 | |
2270 | - GameClient netgame(ips, InternetGaming::ref().get_local_clientname(), true); |
2271 | + GameClient netgame(ips, InternetGaming::ref().get_local_clientname(), |
2272 | + true, opengames_list_.get_selected().name); |
2273 | netgame.run(); |
2274 | } else |
2275 | throw wexception("No server selected! That should not happen!"); |
2276 | @@ -399,6 +409,16 @@ |
2277 | |
2278 | // Start the game |
2279 | try { |
2280 | + |
2281 | + // Tell the metaserver about it |
2282 | + InternetGaming::ref().open_game(); |
2283 | + |
2284 | + // Wait for his response with the IPs of the relay server |
2285 | + if (!wait_for_ip()) { |
2286 | + return; |
2287 | + } |
2288 | + |
2289 | + // Start our relay host |
2290 | GameHost netgame(InternetGaming::ref().get_local_clientname(), true); |
2291 | netgame.run(); |
2292 | } catch (...) { |
2293 | |
2294 | === modified file 'src/ui_fsmenu/internet_lobby.h' |
2295 | --- src/ui_fsmenu/internet_lobby.h 2017-01-25 18:55:59 +0000 |
2296 | +++ src/ui_fsmenu/internet_lobby.h 2017-12-05 20:38:52 +0000 |
2297 | @@ -75,6 +75,7 @@ |
2298 | void server_doubleclicked(); |
2299 | |
2300 | void change_servername(); |
2301 | + bool wait_for_ip(); |
2302 | void clicked_joingame(); |
2303 | void clicked_hostgame(); |
2304 | |
2305 | |
2306 | === modified file 'src/ui_fsmenu/multiplayer.cc' |
2307 | --- src/ui_fsmenu/multiplayer.cc 2017-11-25 20:29:26 +0000 |
2308 | +++ src/ui_fsmenu/multiplayer.cc 2017-12-05 20:38:52 +0000 |
2309 | @@ -123,7 +123,7 @@ |
2310 | |
2311 | // Try to connect to the metaserver |
2312 | const std::string& meta = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str()); |
2313 | - uint32_t port = s.get_natural("metaserverport", INTERNET_GAMING_PORT); |
2314 | + uint32_t port = s.get_natural("metaserverport", kInternetGamingPort); |
2315 | std::string auth = register_ ? password_ : s.get_string("uuid"); |
2316 | assert(!auth.empty()); |
2317 | InternetGaming::ref().login(nickname_, auth, register_, meta, port); |
Continuous integration builds have changed state:
Travis build 2443. State: failed. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 254133988. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ net_relay- 2269.
Appveyor build 2269. State: success. Details: https:/