1 /*
2 * Copyright (C) 2011-2016 OpenDungeons Team
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "ODSocketClient.h"
19 #include "network/ODPacket.h"
20 #include "network/ServerNotification.h"
21
22 #include "utils/Helper.h"
23 #include "utils/LogManager.h"
24
25 #include <boost/date_time/posix_time/posix_time.hpp>
26 #include <boost/filesystem.hpp>
27
connect(const std::string & host,const int port,uint32_t timeout,const std::string & outputReplayFilename)28 bool ODSocketClient::connect(const std::string& host, const int port, uint32_t timeout, const std::string& outputReplayFilename)
29 {
30 mSource = ODSource::none;
31
32 // As we use selector, there is no need to set the socket as not-blocking
33 sf::Socket::Status status = mSockClient.connect(host, port, sf::milliseconds(timeout));
34 if (status != sf::Socket::Done)
35 {
36 OD_LOG_ERR("Could not connect to distant server status="
37 + Helper::toString(status));
38 mSockClient.disconnect();
39 return false;
40 }
41 mSockSelector.add(mSockClient);
42 OD_LOG_INF("Connected to server successfully");
43
44 mOutputReplayFilename = outputReplayFilename;
45
46 mReplayOutputStream.open(mOutputReplayFilename, std::ios::out | std::ios::binary);
47 mGameClock.restart();
48 mSource = ODSource::network;
49 return true;
50 }
51
replay(const std::string & filename)52 bool ODSocketClient::replay(const std::string& filename)
53 {
54 OD_LOG_INF("Reading replay from file " + filename);
55 mReplayInputStream.open(filename, std::ios::in | std::ios::binary);
56 mGameClock.restart();
57 mSource = ODSource::file;
58 return true;
59 }
60
disconnect(bool keepReplay)61 void ODSocketClient::disconnect(bool keepReplay)
62 {
63 mPendingTimestamp = -1;
64 ODSource src = mSource;
65 mSource = ODSource::none;
66 switch(src)
67 {
68 case ODSource::none:
69 {
70 // Nothing to do
71 return;
72 }
73 case ODSource::network:
74 {
75 // Remove any remaining client sockets from the socket selector,
76 // if there is any left.
77 mSockSelector.clear();
78 mSockClient.disconnect();
79 break;
80 }
81 case ODSource::file:
82 {
83 mReplayInputStream.close();
84 return;
85 }
86 default:
87 assert(false);
88 break;
89 }
90
91 mReplayOutputStream.close();
92 // Delete the replay newly created if asked to.
93 if (!keepReplay)
94 boost::filesystem::remove(mOutputReplayFilename);
95 mOutputReplayFilename.clear();
96 }
97
isDataAvailable()98 bool ODSocketClient::isDataAvailable()
99 {
100 switch(mSource)
101 {
102 case ODSource::none:
103 {
104 return false;
105 }
106 case ODSource::network:
107 {
108 // There is only 1 socket in the selector so it should be ready if
109 // wait returns true but it doesn't hurt to return isReady...
110 if(!mSockSelector.wait(sf::milliseconds(5)))
111 return false;
112 return mSockSelector.isReady(mSockClient);
113 }
114 case ODSource::file:
115 {
116 if(mReplayInputStream.eof())
117 return false;
118
119 if(mPendingTimestamp == -1)
120 mPendingTimestamp = mPendingPacket.readPacket(mReplayInputStream);
121
122 if(mPendingTimestamp < 0)
123 return false;
124
125 if(mPendingTimestamp < mGameClock.getElapsedTime().asMilliseconds())
126 return true;
127
128 return false;
129 }
130 default:
131 assert(false);
132 break;
133 }
134
135 return false;
136 }
137
send(ODPacket & s)138 ODSocketClient::ODComStatus ODSocketClient::send(ODPacket& s)
139 {
140 if(mSource != ODSource::network)
141 return ODComStatus::OK;
142
143 sf::Socket::Status status = mSockClient.send(s.mPacket);
144 if (status == sf::Socket::Done)
145 return ODComStatus::OK;
146
147 OD_LOG_ERR("Could not send data from client status="
148 + Helper::toString(status));
149 return ODComStatus::Error;
150 }
151
recv(ODPacket & s)152 ODSocketClient::ODComStatus ODSocketClient::recv(ODPacket& s)
153 {
154 switch(mSource)
155 {
156 case ODSource::none:
157 {
158 // We should not try to send anything until connected
159 OD_LOG_ERR("Unexpected null server mode");
160 return ODComStatus::Error;
161 }
162 case ODSource::network:
163 {
164 sf::Socket::Status status = mSockClient.receive(s.mPacket);
165 if (status == sf::Socket::Done)
166 {
167 s.writePacket(mGameClock.getElapsedTime().asMilliseconds(),
168 mReplayOutputStream);
169 return ODComStatus::OK;
170 }
171
172 if((!mSockClient.isBlocking()) &&
173 (status == sf::Socket::NotReady))
174 {
175 return ODComStatus::NotReady;
176 }
177
178 if(status == sf::Socket::Disconnected)
179 {
180 OD_LOG_WRN("Socket disconnected");
181 return ODComStatus::Error;
182 }
183 OD_LOG_ERR("Could not receive data from client status=" + Helper::toString(status));
184 return ODComStatus::Error;
185 }
186 case ODSource::file:
187 {
188 OD_ASSERT_TRUE(mPendingPacket != 0);
189 s = mPendingPacket;
190 mPendingTimestamp = -1;
191 return ODComStatus::OK;
192 }
193 default:
194 break;
195 }
196 return ODComStatus::Error;
197 }
198
isConnected()199 bool ODSocketClient::isConnected()
200 {
201 return mSource != ODSource::none;
202 }
203
processClientSocketMessages()204 void ODSocketClient::processClientSocketMessages()
205 {
206 // If we receive message for a new turn, after processing every message,
207 // we will refresh what is needed
208 // We loop until no more data is available
209 while(isConnected() && processOneClientSocketMessage());
210 }
211
processOneClientSocketMessage()212 bool ODSocketClient::processOneClientSocketMessage()
213 {
214 if(!isDataAvailable())
215 return false;
216
217 ODPacket packetReceived;
218
219 // Check if data available
220 ODComStatus comStatus = recv(packetReceived);
221 if(comStatus != ODComStatus::OK)
222 {
223 playerDisconnected();
224 return false;
225 }
226
227 ServerNotificationType serverCommand;
228 OD_ASSERT_TRUE(packetReceived >> serverCommand);
229
230 return processMessage(serverCommand, packetReceived);
231 }
232