OSDN Git Service

ロビー機能を仮実装
[mmo/main.git] / server / Server.cpp
1 //\r
2 // Server.cpp\r
3 //\r
4 \r
5 #include "Server.hpp"\r
6 #include "version.hpp"\r
7 #include <algorithm>\r
8 #include <boost/make_shared.hpp>\r
9 #include <boost/foreach.hpp>\r
10 #include <boost/archive/text_oarchive.hpp>\r
11 #include <boost/property_tree/ptree_serialization.hpp>\r
12 #include "../common/Logger.hpp"\r
13 #include "../common/network/Command.hpp"\r
14 #include "../common/network/Utils.hpp"\r
15 \r
16 namespace network {\r
17 \r
18     Server::Server() :\r
19             endpoint_(tcp::v4(), config_.port()),\r
20             acceptor_(io_service_, endpoint_),\r
21             socket_udp_(io_service_, udp::endpoint(udp::v4(), config_.port())),\r
22             udp_packet_count_(0),\r
23                         recent_chat_log_(10)\r
24     {\r
25     }\r
26 \r
27     void Server::Start(CallbackFuncPtr callback)\r
28     {\r
29         callback_ = std::make_shared<CallbackFunc>(\r
30                 [&](network::Command c){\r
31 \r
32             // ログアウト\r
33             if (c.header() == network::header::FatalConnectionError) {\r
34                 if (callback) {\r
35                                         (*callback)(c);\r
36                                 }\r
37             } else if (auto session = c.session().lock()) {\r
38                                 auto read_average = session->GetReadByteAverage();\r
39                                 if (read_average > config_.receive_limit_2()) {\r
40                                         Logger::Info(_T("Banished a session: %d %dbyte/s"), session->id(), read_average);\r
41                                         session->Close();\r
42                                 } else if(read_average > config_.receive_limit_1()) {\r
43                                         Logger::Info(_T("Receive limit exceeded: %d: %d byte/s"), session->id(), read_average);\r
44                                 } else {\r
45                                         if (callback) {\r
46                                                 (*callback)(c);\r
47                                         }\r
48                 }\r
49             }\r
50 \r
51         });\r
52 \r
53                 BOOST_FOREACH(const auto& host, config().lobby_servers()) {\r
54                         udp::resolver resolver(io_service_);\r
55                         udp::resolver::query query(udp::v4(), host.c_str(), "39380");\r
56                         lobby_hosts_.push_back(resolver.resolve(query));\r
57                 }\r
58 \r
59         {\r
60         auto new_session = boost::make_shared<ServerSession>(io_service_);\r
61         acceptor_.async_accept(new_session->tcp_socket(),\r
62                               boost::bind(&Server::ReceiveSession, this, new_session, boost::asio::placeholders::error));\r
63         }\r
64 \r
65         {\r
66             socket_udp_.async_receive_from(\r
67                 boost::asio::buffer(receive_buf_udp_, UDP_MAX_RECEIVE_LENGTH), sender_endpoint_,\r
68                 boost::bind(&Server::ReceiveUDP, this,\r
69                   boost::asio::placeholders::error,\r
70                   boost::asio::placeholders::bytes_transferred));\r
71         }\r
72 \r
73         boost::asio::io_service::work work(io_service_);\r
74         io_service_.run();\r
75     }\r
76 \r
77     void Server::Stop()\r
78     {\r
79         io_service_.stop();\r
80                 Logger::Info("stop server");\r
81     }\r
82     void Server::Stop(int innterrupt_type)\r
83     {\r
84         io_service_.stop();\r
85         Logger::Info(_T("stop server innterrupt_type=%d"),innterrupt_type);\r
86     }\r
87 \r
88         int Server::GetUserCount() const\r
89         {\r
90                 auto count = std::count_if(sessions_.begin(), sessions_.end(),\r
91                         [](const SessionWeakPtr& s){ \r
92                                 return !s.expired() && s.lock()->online() && s.lock()->id() > 0; \r
93                         });\r
94 \r
95                 return count;\r
96         }\r
97 \r
98         std::string Server::GetStatusJSON() const\r
99         {\r
100                 auto msg = (\r
101                                         boost::format("{\"nam\":\"%s\",\"ver\":\"%d.%d.%d\",\"cnt\":%d,\"cap\":%d,\"stg\":\"%s\"}")\r
102                                                 % config_.server_name()\r
103                                                 % MMO_VERSION_MAJOR % MMO_VERSION_MINOR % MMO_VERSION_REVISION\r
104                                                 % GetUserCount()\r
105                                                 % config_.capacity()\r
106                                                 % config_.stage()\r
107                                         ).str();\r
108 \r
109                 return msg;\r
110         }\r
111 \r
112         std::string Server::GetFullStatus() const\r
113         {\r
114                 using namespace boost::property_tree;\r
115                 ptree xml_ptree;\r
116 \r
117                 xml_ptree.put_child("config", config_.pt());\r
118                 xml_ptree.put("version", (boost::format("%d.%d.%d") \r
119                         % MMO_VERSION_MAJOR % MMO_VERSION_MINOR % MMO_VERSION_REVISION).str());\r
120                 xml_ptree.put("protocol_version", MMO_PROTOCOL_VERSION);\r
121 \r
122                 {\r
123                         ptree player_array;\r
124                         auto id_list = account_.GetIDList();\r
125                         BOOST_FOREACH(const auto& s, sessions_) {\r
126                                 if (!s.expired() && s.lock()->online() && s.lock()->id() > 0) {\r
127                                         auto id = s.lock()->id();\r
128                                         ptree player;\r
129                                         player.put("name", account_.GetUserName(id));\r
130                                         player.put("model_name", account_.GetUserModelName(id));\r
131                                         player_array.push_back(std::make_pair("", player));\r
132                                 }\r
133                         }\r
134                         xml_ptree.put_child("players", player_array);\r
135                 }\r
136 \r
137                 //{\r
138                 //      ptree log_array;\r
139                 //      BOOST_FOREACH(const std::string& msg, recent_chat_log_) {\r
140                 //              log_array.push_back(std::make_pair("", msg));\r
141                 //      }\r
142                 //      xml_ptree.put_child("recent_chat_log", log_array);\r
143                 //}\r
144 \r
145                 xml_ptree.put_child("channels", channel_.pt());\r
146                 std::stringstream stream;\r
147                 boost::archive::text_oarchive oa(stream);\r
148                 oa << xml_ptree;\r
149 \r
150                 return stream.str();\r
151         }\r
152 \r
153         const Config& Server::config() const\r
154         {\r
155                 return config_;\r
156         }\r
157 \r
158         Account& Server::account()\r
159         {\r
160                 return account_;\r
161         }\r
162         \r
163         void Server::AddChatLog(const std::string& msg)\r
164         {\r
165                 recent_chat_log_.push_back(msg);\r
166         }\r
167 \r
168     bool Server::Empty() const\r
169     {\r
170         return GetUserCount() == 0;\r
171     }\r
172 \r
173         bool Server::IsBlockedAddress(const boost::asio::ip::address& address)\r
174         {\r
175                 BOOST_FOREACH(const auto& pattern, config_.blocking_address_patterns()) {\r
176                         if (network::Utils::MatchWithWildcard(pattern, address.to_string())) {\r
177                                 return true;\r
178                         }\r
179                 }\r
180                 return false;\r
181         }\r
182 \r
183     void Server::ReceiveSession(const SessionPtr& session, const boost::system::error_code& error)\r
184     {\r
185                 \r
186                 config_.Reload();\r
187 \r
188                 const auto address = session->tcp_socket().remote_endpoint().address();\r
189 \r
190                 // 拒否IPでないか判定\r
191                 if(IsBlockedAddress(address)) {\r
192                         Logger::Info("Blocked IP Address: %s", address);\r
193             session->Close();\r
194 \r
195                 } else {\r
196             session->set_on_receive(callback_);\r
197             session->Start();\r
198             sessions_.push_back(SessionWeakPtr(session));\r
199 \r
200             // クライアント情報を要求\r
201             session->Send(ClientRequestedClientInfo());\r
202         }\r
203 \r
204         auto new_session = boost::make_shared<ServerSession>(io_service_);\r
205          acceptor_.async_accept(new_session->tcp_socket(),\r
206                  boost::bind(&Server::ReceiveSession, this, new_session, boost::asio::placeholders::error));\r
207 \r
208                 RefreshSession();\r
209     }\r
210 \r
211         void Server::RefreshSession()\r
212         {\r
213                 // 使用済のセッションのポインタを破棄\r
214         auto it = std::remove_if(sessions_.begin(), sessions_.end(),\r
215                 [](const SessionWeakPtr& ptr){\r
216             return ptr.expired();\r
217         });\r
218         sessions_.erase(it, sessions_.end());\r
219                 Logger::Info("Active connection: %d", GetUserCount());\r
220         }\r
221 \r
222     void Server::SendAll(const Command& command, int channel, bool limited)\r
223     {\r
224         BOOST_FOREACH(SessionWeakPtr& ptr, sessions_) {\r
225             if (auto session = ptr.lock()) {\r
226                                 if (channel < 0 || (channel >= 0 && session->channel() == channel)) {\r
227                                         if (!limited || session->write_average_limit() > session->GetWriteByteAverage()) {\r
228                                                 if (session->id() > 0) {\r
229                                                         session->Send(command);\r
230                                                 }\r
231                                         }\r
232                                 }\r
233             }\r
234         }\r
235     }\r
236 \r
237     void Server::SendOthers(const Command& command, uint32_t self_id, int channel, bool limited)\r
238     {\r
239         BOOST_FOREACH(SessionWeakPtr& ptr, sessions_) {\r
240             if (auto session = ptr.lock()) {\r
241                                 if (channel < 0 || (channel >= 0 && session->channel() == channel)) {\r
242                                         if (!limited || session->write_average_limit() > session->GetWriteByteAverage()) {\r
243                                                 if (session->id() > 0 && session->id() != self_id) {\r
244                                                         session->Send(command);\r
245                                                 }\r
246                                         }\r
247                                 }\r
248             }\r
249         }\r
250     }\r
251         \r
252     void Server::SendTo(const Command& command, uint32_t user_id)\r
253         {\r
254                 auto it = std::find_if(sessions_.begin(), sessions_.end(),\r
255                         [user_id](SessionWeakPtr& ptr){\r
256                                 return ptr.lock()->id() == user_id;\r
257                         });\r
258                 \r
259                 if (it != sessions_.end()) {\r
260                         it->lock()->Send(command);\r
261                 }\r
262         }\r
263 \r
264     void Server::SendUDPTestPacket(const std::string& ip_address, uint16_t port)\r
265     {\r
266         using boost::asio::ip::udp;\r
267 \r
268         std::stringstream port_str;\r
269         port_str << (int)port;\r
270 \r
271         udp::resolver resolver(io_service_);\r
272         udp::resolver::query query(udp::v4(), ip_address.c_str(), port_str.str().c_str());\r
273         udp::resolver::iterator iterator = resolver.resolve(query);\r
274 \r
275         static char request[] = "MMO UDP Test Packet";\r
276         for (int i = 0; i < UDP_TEST_PACKET_TIME; i++) {\r
277 \r
278             io_service_.post(boost::bind(&Server::DoWriteUDP, this, request, *iterator));\r
279         }\r
280     }\r
281 \r
282         void Server::SendPublicPing()\r
283         {\r
284                 static char request[] = "P";\r
285                 BOOST_FOREACH(const auto& iterator, lobby_hosts_) {\r
286                         io_service_.post(boost::bind(&Server::DoWriteUDP, this, request, *iterator));\r
287                 }\r
288         }\r
289 \r
290     void Server::SendUDP(const std::string& message, const boost::asio::ip::udp::endpoint endpoint)\r
291     {\r
292                 io_service_.post(boost::bind(&Server::DoWriteUDP, this, message, endpoint));\r
293     }\r
294 \r
295     void Server::ReceiveUDP(const boost::system::error_code& error, size_t bytes_recvd)\r
296     {\r
297         if (bytes_recvd > 0) {\r
298             std::string buffer(receive_buf_udp_, bytes_recvd);\r
299             FetchUDP(buffer, sender_endpoint_);\r
300         }\r
301         if (!error) {\r
302           socket_udp_.async_receive_from(\r
303               boost::asio::buffer(receive_buf_udp_, UDP_MAX_RECEIVE_LENGTH), sender_endpoint_,\r
304               boost::bind(&Server::ReceiveUDP, this,\r
305                 boost::asio::placeholders::error,\r
306                 boost::asio::placeholders::bytes_transferred));\r
307         } else {\r
308             Logger::Error("%s", error.message());\r
309         }\r
310     }\r
311 \r
312     void Server::DoWriteUDP(const std::string& msg, const udp::endpoint& endpoint)\r
313     {\r
314         boost::shared_ptr<std::string> s = \r
315               boost::make_shared<std::string>(msg.data(), msg.size());\r
316 \r
317         socket_udp_.async_send_to(\r
318             boost::asio::buffer(s->data(), s->size()), endpoint,\r
319             boost::bind(&Server::WriteUDP, this,\r
320               boost::asio::placeholders::error, s));\r
321     }\r
322 \r
323     void Server::WriteUDP(const boost::system::error_code& error, boost::shared_ptr<std::string> holder)\r
324     {\r
325 //        if (!error) {\r
326 //            if (!send_queue_.empty()) {\r
327 //                  send_queue_.pop();\r
328 //                  if (!send_queue_.empty())\r
329 //                  {\r
330 //                    boost::asio::async_write(socket_tcp_,\r
331 //                        boost::asio::buffer(send_queue_.front().data(),\r
332 //                          send_queue_.front().size()),\r
333 //                        boost::bind(&Session::WriteTCP, this,\r
334 //                          boost::asio::placeholders::error));\r
335 //                  }\r
336 //            }\r
337 //        } else {\r
338 //            FatalError();\r
339 //        }\r
340     }\r
341 \r
342     void Server::FetchUDP(const std::string& buffer, const boost::asio::ip::udp::endpoint endpoint)\r
343     {\r
344         uint8_t header;\r
345         std::string body;\r
346         SessionWeakPtr session;\r
347 \r
348                 // IPアドレスとポートからセッションを特定\r
349                 auto it = std::find_if(sessions_.begin(), sessions_.end(),\r
350                         [&endpoint](const SessionWeakPtr& session) -> bool {\r
351                                 if (auto session_ptr = session.lock()) {\r
352 \r
353                                         const auto session_endpoint = session_ptr->tcp_socket().remote_endpoint();\r
354                                         const auto session_port = session_ptr->udp_port();\r
355 \r
356                                         return (session_endpoint.address() == endpoint.address() &&\r
357                                                 session_port == endpoint.port());\r
358 \r
359                                 } else {\r
360                                         return false;\r
361                                 }\r
362                         });\r
363 \r
364                 if (it != sessions_.end()) {\r
365                         session = *it;\r
366                         Logger::Debug("Receive UDP Command: %d", session.lock()->id());\r
367                 } else {\r
368                         Logger::Debug("Receive anonymous UDP Command");\r
369                 }\r
370 \r
371         if (buffer.size() > network::Utils::Deserialize(buffer, &header)) {\r
372                         body = buffer.substr(sizeof(header));\r
373                 }\r
374 \r
375         // 復号\r
376         if (session.lock() && header == header::ENCRYPT_HEADER) {\r
377             body.erase(0, sizeof(header));\r
378                         body = session.lock()->encrypter().Decrypt(body);\r
379             Utils::Deserialize(body, &header);\r
380                         body = buffer.substr(sizeof(header));\r
381         }\r
382 \r
383                 if (header == network::header::ServerRequstedStatus) {\r
384                         SendUDP(GetStatusJSON(), endpoint);\r
385                 } else {\r
386                         if (callback_) {\r
387                                 (*callback_)(Command(static_cast<network::header::CommandHeader>(header), body, session));\r
388                         }\r
389                 }\r
390 \r
391     }\r
392 \r
393     void Server::ServerSession::Start()\r
394     {\r
395         online_ = true;\r
396 \r
397         // Nagleアルゴリズムを無効化\r
398         socket_tcp_.set_option(boost::asio::ip::tcp::no_delay(true));\r
399 \r
400                 // バッファサイズを変更 1MiB\r
401                 boost::asio::socket_base::receive_buffer_size option(1048576);\r
402                 socket_tcp_.set_option(option);\r
403 \r
404         // IPアドレスを取得\r
405         global_ip_ = socket_tcp_.remote_endpoint().address().to_string();\r
406 \r
407         boost::asio::async_read_until(socket_tcp_,\r
408             receive_buf_, NETWORK_UTILS_DELIMITOR,\r
409             boost::bind(\r
410               &ServerSession::ReceiveTCP, shared_from_this(),\r
411               boost::asio::placeholders::error));\r
412     }\r
413 }\r