1 /* Copyright (C) 2021 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. 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 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. 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.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "NetSession.h"
22 #include "NetClient.h"
23 #include "NetMessage.h"
24 #include "NetServer.h"
26 #include "ps/CLogger.h"
27 #include "ps/Profile.h"
29 constexpr int NETCLIENT_POLL_TIMEOUT
= 50;
31 constexpr int CHANNEL_COUNT
= 1;
33 CNetClientSession::CNetClientSession(CNetClient
& client
) :
34 m_Client(client
), m_FileTransferer(this), m_Host(nullptr), m_Server(nullptr),
35 m_Stats(nullptr), m_IncomingMessages(16), m_OutgoingMessages(16),
36 m_LoopRunning(false), m_ShouldShutdown(false), m_MeanRTT(0), m_LastReceivedTime(0)
40 CNetClientSession::~CNetClientSession()
42 ENSURE(!m_LoopRunning
);
46 if (m_Host
&& m_Server
)
48 // Disconnect immediately (we can't wait for acks)
49 enet_peer_disconnect_now(m_Server
, NDR_SERVER_SHUTDOWN
);
50 enet_host_destroy(m_Host
);
57 bool CNetClientSession::Connect(const CStr
& server
, const u16 port
, ENetHost
* enetClient
)
59 ENSURE(!m_LoopRunning
);
65 if (enetClient
!= nullptr)
68 host
= enet_host_create(NULL
, 1, CHANNEL_COUNT
, 0, 0);
73 // Bind to specified host
76 if (enet_address_set_host(&addr
, server
.c_str()) < 0)
79 // Initiate connection to server
80 ENetPeer
* peer
= enet_host_connect(host
, &addr
, CHANNEL_COUNT
, 0);
87 m_Stats
= new CNetStatsTable(m_Server
);
88 if (CProfileViewer::IsInitialised())
89 g_ProfileViewer
.AddRootTable(m_Stats
);
94 void CNetClientSession::RunNetLoop(CNetClientSession
* session
)
96 ENSURE(!session
->m_LoopRunning
);
97 session
->m_LoopRunning
= true;
99 debug_SetThreadName("NetClientSession loop");
101 while (!session
->m_ShouldShutdown
)
103 ENSURE(session
->m_Host
&& session
->m_Server
);
105 session
->m_FileTransferer
.Poll();
109 session
->m_LastReceivedTime
= enet_time_get() - session
->m_Server
->lastReceiveTime
;
110 session
->m_MeanRTT
= session
->m_Server
->roundTripTime
;
113 session
->m_LoopRunning
= false;
115 // Deleting the session is handled in this thread as it might outlive the CNetClient.
116 SAFE_DELETE(session
);
119 void CNetClientSession::Shutdown()
121 m_ShouldShutdown
= true;
124 void CNetClientSession::Poll()
128 // Use the timeout to make the thread wait and save CPU time.
129 if (enet_host_service(m_Host
, &event
, NETCLIENT_POLL_TIMEOUT
) <= 0)
132 if (event
.type
== ENET_EVENT_TYPE_CONNECT
)
134 ENSURE(event
.peer
== m_Server
);
136 // Report the server address immediately.
137 char hostname
[256] = "(error)";
138 enet_address_get_host_ip(&event
.peer
->address
, hostname
, ARRAY_SIZE(hostname
));
139 LOGMESSAGE("Net client: Connected to %s:%u", hostname
, (unsigned int)event
.peer
->address
.port
);
142 m_IncomingMessages
.push(event
);
144 else if (event
.type
== ENET_EVENT_TYPE_DISCONNECT
)
146 ENSURE(event
.peer
== m_Server
);
148 // Report immediately.
149 LOGMESSAGE("Net client: Disconnected");
152 m_IncomingMessages
.push(event
);
154 else if (event
.type
== ENET_EVENT_TYPE_RECEIVE
)
155 m_IncomingMessages
.push(event
);
158 void CNetClientSession::Flush()
161 while (m_OutgoingMessages
.pop(packet
))
162 if (enet_peer_send(m_Server
, CNetHost::DEFAULT_CHANNEL
, packet
) < 0)
164 // Report the error, but do so silently if we know we are disconnected.
166 LOGERROR("NetClient: Failed to send packet to server");
168 LOGMESSAGE("NetClient: Failed to send packet to server");
171 enet_host_flush(m_Host
);
174 void CNetClientSession::ProcessPolledMessages()
177 while(m_IncomingMessages
.pop(event
))
179 if (event
.type
== ENET_EVENT_TYPE_CONNECT
)
180 m_Client
.HandleConnect();
181 else if (event
.type
== ENET_EVENT_TYPE_DISCONNECT
)
183 // This deletes the session, so we must break;
184 m_Client
.HandleDisconnect(event
.data
);
187 else if (event
.type
== ENET_EVENT_TYPE_RECEIVE
)
189 CNetMessage
* msg
= CNetMessageFactory::CreateMessage(event
.packet
->data
, event
.packet
->dataLength
, m_Client
.GetScriptInterface());
192 LOGMESSAGE("Net client: Received message %s of size %lu from server", msg
->ToString().c_str(), (unsigned long)msg
->GetSerializedLength());
194 m_Client
.HandleMessage(msg
);
197 enet_packet_destroy(event
.packet
);
202 bool CNetClientSession::SendMessage(const CNetMessage
* message
)
204 ENSURE(m_Host
&& m_Server
);
207 ENetPacket
* packet
= CNetHost::CreatePacket(message
);
211 if (!m_OutgoingMessages
.push(packet
))
213 LOGERROR("NetClient: Failed to push message on the outgoing queue.");
220 u32
CNetClientSession::GetLastReceivedTime() const
225 return m_LastReceivedTime
;
228 u32
CNetClientSession::GetMeanRTT() const
236 CNetServerSession::CNetServerSession(CNetServerWorker
& server
, ENetPeer
* peer
) :
237 m_Server(server
), m_FileTransferer(this), m_Peer(peer
), m_HostID(0), m_GUID(), m_UserName()
241 u32
CNetServerSession::GetIPAddress() const
243 return m_Peer
->address
.host
;
246 u32
CNetServerSession::GetLastReceivedTime() const
251 return enet_time_get() - m_Peer
->lastReceiveTime
;
254 u32
CNetServerSession::GetMeanRTT() const
259 return m_Peer
->roundTripTime
;
262 void CNetServerSession::Disconnect(NetDisconnectReason reason
)
264 if (reason
== NDR_UNKNOWN
)
265 LOGWARNING("Disconnecting client without communicating the disconnect reason!");
267 Update((uint
)NMT_CONNECTION_LOST
, NULL
);
269 enet_peer_disconnect(m_Peer
, static_cast<enet_uint32
>(reason
));
272 void CNetServerSession::DisconnectNow(NetDisconnectReason reason
)
274 if (reason
== NDR_UNKNOWN
)
275 LOGWARNING("Disconnecting client without communicating the disconnect reason!");
277 enet_peer_disconnect_now(m_Peer
, static_cast<enet_uint32
>(reason
));
280 bool CNetServerSession::SendMessage(const CNetMessage
* message
)
282 return m_Server
.SendMessage(m_Peer
, message
);