Core/Movement: Add time synchronisation (#18189)

(cherry picked from commit 975f1e364a)
This commit is contained in:
Chaouki Dhib
2019-04-15 23:31:25 +02:00
committed by Shauren
parent 2f6f1c7089
commit c19a4db1c1
11 changed files with 177 additions and 95 deletions

View File

@@ -21,13 +21,20 @@
#include "Define.h"
#include <chrono>
inline uint32 getMSTime()
inline std::chrono::steady_clock::time_point GetApplicationStartTime()
{
using namespace std::chrono;
static const steady_clock::time_point ApplicationStartTime = steady_clock::now();
return uint32(duration_cast<milliseconds>(steady_clock::now() - ApplicationStartTime).count());
return ApplicationStartTime;
}
inline uint32 getMSTime()
{
using namespace std::chrono;
return uint32(duration_cast<milliseconds>(steady_clock::now() - GetApplicationStartTime()).count());
}
inline uint32 getMSTimeDiff(uint32 oldMSTime, uint32 newMSTime)
@@ -39,6 +46,14 @@ inline uint32 getMSTimeDiff(uint32 oldMSTime, uint32 newMSTime)
return newMSTime - oldMSTime;
}
inline uint32 getMSTimeDiff(uint32 oldMSTime, std::chrono::steady_clock::time_point newTime)
{
using namespace std::chrono;
uint32 newMSTime = uint32(duration_cast<milliseconds>(newTime - GetApplicationStartTime()).count());
return getMSTimeDiff(oldMSTime, newMSTime);
}
inline uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
{
return getMSTimeDiff(oldMSTime, getMSTime());

View File

@@ -322,10 +322,6 @@ Player::Player(WorldSession* session) : Unit(true), m_sceneMgr(this)
m_ChampioningFaction = 0;
m_timeSyncTimer = 0;
m_timeSyncClient = 0;
m_timeSyncServer = GameTime::GetGameTimeMS();
for (uint8 i = 0; i < MAX_POWERS_PER_CLASS; ++i)
m_powerFraction[i] = 0;
@@ -1132,14 +1128,6 @@ void Player::Update(uint32 p_time)
m_zoneUpdateTimer -= p_time;
}
if (m_timeSyncTimer > 0 && !IsBeingTeleportedFar())
{
if (p_time >= m_timeSyncTimer)
SendTimeSync();
else
m_timeSyncTimer -= p_time;
}
if (IsAlive())
{
m_regenTimer += p_time;
@@ -24295,10 +24283,10 @@ void Player::SendInitialPacketsBeforeAddToMap()
if (!(m_teleport_options & TELE_TO_SEAMLESS))
{
m_movementCounter = 0;
ResetTimeSync();
GetSession()->ResetTimeSync();
}
SendTimeSync();
GetSession()->SendTimeSync();
/// Pass 'this' as argument because we're not stored in ObjectAccessor yet
GetSocial()->SendSocialList(this, SOCIAL_FLAG_ALL);
@@ -27802,30 +27790,6 @@ void Player::LoadActions(PreparedQueryResult result)
SendActionButtons(1);
}
void Player::ResetTimeSync()
{
m_timeSyncTimer = 0;
m_timeSyncClient = 0;
m_timeSyncServer = GameTime::GetGameTimeMS();
}
void Player::SendTimeSync()
{
m_timeSyncQueue.push(m_movementCounter++);
WorldPackets::Misc::TimeSyncRequest packet;
packet.SequenceIndex = m_timeSyncQueue.back();
SendDirectMessage(packet.Write());
// Schedule next sync in 10 sec
m_timeSyncTimer = 10000;
m_timeSyncServer = GameTime::GetGameTimeMS();
if (m_timeSyncQueue.size() > 3)
TC_LOG_ERROR("network", "Player::SendTimeSync: Did not receive CMSG_TIME_SYNC_RESP for over 30 seconds from '%s' (%s), possible cheater",
GetName().c_str(), GetGUID().ToString().c_str());
}
void Player::SetReputation(uint32 factionentry, int32 value)
{
GetReputationMgr().SetReputation(sFactionStore.LookupEntry(factionentry), value);

View File

@@ -2933,9 +2933,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
ItemDurationList m_itemDuration;
GuidUnorderedSet m_itemSoulboundTradeable;
void ResetTimeSync();
void SendTimeSync();
std::unique_ptr<ResurrectionData> _resurrectionData;
WorldSession* m_session;
@@ -3063,11 +3060,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
uint32 m_ChampioningFaction;
std::queue<uint32> m_timeSyncQueue;
uint32 m_timeSyncTimer;
uint32 m_timeSyncClient;
uint32 m_timeSyncServer;
InstanceTimeMap _instanceResetTimes;
uint32 _pendingBindId;
uint32 _pendingBindTimer;

View File

@@ -857,29 +857,6 @@ void WorldSession::HandleSetTitleOpcode(WorldPackets::Character::SetTitle& packe
GetPlayer()->SetChosenTitle(packet.TitleID);
}
void WorldSession::HandleTimeSyncResponse(WorldPackets::Misc::TimeSyncResponse& packet)
{
// Prevent crashing server if queue is empty
if (_player->m_timeSyncQueue.empty())
{
TC_LOG_ERROR("network", "Received CMSG_TIME_SYNC_RESPONSE from player %s without requesting it (hacker?)", _player->GetName().c_str());
return;
}
if (packet.SequenceIndex != _player->m_timeSyncQueue.front())
TC_LOG_ERROR("network", "Wrong time sync counter from player %s (cheater?)", _player->GetName().c_str());
TC_LOG_DEBUG("network", "Time sync received: counter %u, client ticks %u, time since last sync %u", packet.SequenceIndex, packet.ClientTime, packet.ClientTime - _player->m_timeSyncClient);
uint32 ourTicks = packet.ClientTime + (GameTime::GetGameTimeMS() - _player->m_timeSyncServer);
// diff should be small
TC_LOG_DEBUG("network", "Our ticks: %u, diff %u, latency %u", ourTicks, ourTicks - packet.ClientTime, GetLatency());
_player->m_timeSyncClient = packet.ClientTime;
_player->m_timeSyncQueue.pop();
}
void WorldSession::HandleResetInstancesOpcode(WorldPackets::Instance::ResetInstances& /*packet*/)
{
if (Group* group = _player->GetGroup())

View File

@@ -27,6 +27,7 @@
#include "InstanceSaveMgr.h"
#include "Log.h"
#include "MapManager.h"
#include "MiscPackets.h"
#include "MovementPackets.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
@@ -37,8 +38,9 @@
#include "Transport.h"
#include "Vehicle.h"
#include "SpellMgr.h"
#define MOVEMENT_PACKET_TIME_DELAY 0
#include <boost/accumulators/statistics/variance.hpp>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
void WorldSession::HandleMoveWorldportAckOpcode(WorldPackets::Movement::WorldPortResponse& /*packet*/)
{
@@ -382,15 +384,9 @@ void WorldSession::HandleMovementOpcode(OpcodeClient opcode, MovementInfo& movem
if (opcode == CMSG_MOVE_FALL_LAND || opcode == CMSG_MOVE_START_SWIM || opcode == CMSG_MOVE_SET_FLY)
mover->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LandingOrFlight); // Parachutes
uint32 mstime = GameTime::GetGameTimeMS();
/*----------------------*/
if (m_clientTimeDelay == 0)
m_clientTimeDelay = mstime - movementInfo.time;
/* process position-change */
movementInfo.time = movementInfo.time + m_clientTimeDelay + MOVEMENT_PACKET_TIME_DELAY;
movementInfo.guid = mover->GetGUID();
movementInfo.time = AdjustClientMovementTime(movementInfo.time);
mover->m_movementInfo = movementInfo;
// Some vehicles allow the passenger to turn by himself
@@ -579,7 +575,7 @@ void WorldSession::HandleMoveApplyMovementForceAck(WorldPackets::Movement::MoveA
return;
}
moveApplyMovementForceAck.Ack.Status.time += m_clientTimeDelay + MOVEMENT_PACKET_TIME_DELAY;
moveApplyMovementForceAck.Ack.Status.time = AdjustClientMovementTime(moveApplyMovementForceAck.Ack.Status.time);
WorldPackets::Movement::MoveUpdateApplyMovementForce updateApplyMovementForce;
updateApplyMovementForce.Status = &moveApplyMovementForceAck.Ack.Status;
@@ -601,7 +597,7 @@ void WorldSession::HandleMoveRemoveMovementForceAck(WorldPackets::Movement::Move
return;
}
moveRemoveMovementForceAck.Ack.Status.time += m_clientTimeDelay + MOVEMENT_PACKET_TIME_DELAY;
moveRemoveMovementForceAck.Ack.Status.time = AdjustClientMovementTime(moveRemoveMovementForceAck.Ack.Status.time);
WorldPackets::Movement::MoveUpdateRemoveMovementForce updateRemoveMovementForce;
updateRemoveMovementForce.Status = &moveRemoveMovementForceAck.Ack.Status;
@@ -643,7 +639,7 @@ void WorldSession::HandleMoveSetModMovementForceMagnitudeAck(WorldPackets::Movem
}
}
setModMovementForceMagnitudeAck.Ack.Status.time += m_clientTimeDelay + MOVEMENT_PACKET_TIME_DELAY;
setModMovementForceMagnitudeAck.Ack.Status.time = AdjustClientMovementTime(setModMovementForceMagnitudeAck.Ack.Status.time);
WorldPackets::Movement::MoveUpdateSpeed updateModMovementForceMagnitude(SMSG_MOVE_UPDATE_MOD_MOVEMENT_FORCE_MAGNITUDE);
updateModMovementForceMagnitude.Status = &setModMovementForceMagnitudeAck.Ack.Status;
@@ -716,3 +712,71 @@ void WorldSession::HandleMoveTimeSkippedOpcode(WorldPackets::Movement::MoveTimeS
moveSkipTime.TimeSkipped = moveTimeSkipped.TimeSkipped;
mover->SendMessageToSet(moveSkipTime.Write(), _player);
}
void WorldSession::HandleTimeSyncResponse(WorldPackets::Misc::TimeSyncResponse& timeSyncResponse)
{
if (_pendingTimeSyncRequests.count(timeSyncResponse.SequenceIndex) == 0)
return;
uint32 serverTimeAtSent = _pendingTimeSyncRequests.at(timeSyncResponse.SequenceIndex);
_pendingTimeSyncRequests.erase(timeSyncResponse.SequenceIndex);
// time it took for the request to travel to the client, for the client to process it and reply and for response to travel back to the server.
// we are going to make 2 assumptions:
// 1) we assume that the request processing time equals 0.
// 2) we assume that the packet took as much time to travel from server to client than it took to travel from client to server.
uint32 roundTripDuration = getMSTimeDiff(serverTimeAtSent, timeSyncResponse.GetReceivedTime());
uint32 lagDelay = roundTripDuration / 2;
/*
clockDelta = serverTime - clientTime
where
serverTime: time that was displayed on the clock of the SERVER at the moment when the client processed the SMSG_TIME_SYNC_REQUEST packet.
clientTime: time that was displayed on the clock of the CLIENT at the moment when the client processed the SMSG_TIME_SYNC_REQUEST packet.
Once clockDelta has been computed, we can compute the time of an event on server clock when we know the time of that same event on the client clock,
using the following relation:
serverTime = clockDelta + clientTime
*/
int64 clockDelta = (int64)(serverTimeAtSent + lagDelay) - (int64)timeSyncResponse.ClientTime;
_timeSyncClockDeltaQueue.push_back(std::pair<int64, uint32>(clockDelta, roundTripDuration));
ComputeNewClockDelta();
}
void WorldSession::ComputeNewClockDelta()
{
// implementation of the technique described here: https://web.archive.org/web/20180430214420/http://www.mine-control.com/zack/timesync/timesync.html
// to reduce the skew induced by dropped TCP packets that get resent.
using namespace boost::accumulators;
accumulator_set<uint32, features<tag::mean, tag::median, tag::variance(lazy)> > latencyAccumulator;
for (auto pair : _timeSyncClockDeltaQueue)
latencyAccumulator(pair.second);
uint32 latencyMedian = static_cast<uint32>(std::round(median(latencyAccumulator)));
uint32 latencyStandardDeviation = static_cast<uint32>(std::round(sqrt(variance(latencyAccumulator))));
accumulator_set<int64, features<tag::mean> > clockDeltasAfterFiltering;
uint32 sampleSizeAfterFiltering = 0;
for (auto pair : _timeSyncClockDeltaQueue)
{
if (pair.second < latencyStandardDeviation + latencyMedian) {
clockDeltasAfterFiltering(pair.first);
sampleSizeAfterFiltering++;
}
}
if (sampleSizeAfterFiltering != 0)
{
int64 meanClockDelta = static_cast<int64>(std::round(mean(clockDeltasAfterFiltering)));
if (std::abs(meanClockDelta - _timeSyncClockDelta) > 25)
_timeSyncClockDelta = meanClockDelta;
}
else if (_timeSyncClockDelta == 0)
{
std::pair<int64, uint32> back = _timeSyncClockDeltaQueue.back();
_timeSyncClockDelta = back.first;
}
}

View File

@@ -183,6 +183,8 @@ namespace WorldPackets
void Read() override;
std::chrono::steady_clock::time_point GetReceivedTime() const { return _worldPacket.GetReceivedTime(); }
uint32 ClientTime = 0; // Client ticks in ms
uint32 SequenceIndex = 0; // Same index as in request
};

View File

@@ -863,7 +863,7 @@ void OpcodeTable::Initialize()
DEFINE_HANDLER(CMSG_TAXI_QUERY_AVAILABLE_NODES, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleTaxiQueryAvailableNodesOpcode);
DEFINE_HANDLER(CMSG_TAXI_REQUEST_EARLY_LANDING, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleTaxiRequestEarlyLanding);
DEFINE_HANDLER(CMSG_TIME_ADJUSTMENT_RESPONSE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_TIME_SYNC_RESPONSE, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleTimeSyncResponse);
DEFINE_HANDLER(CMSG_TIME_SYNC_RESPONSE, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleTimeSyncResponse);
DEFINE_HANDLER(CMSG_TIME_SYNC_RESPONSE_DROPPED, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_TIME_SYNC_RESPONSE_FAILED, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_TOGGLE_DIFFICULTY, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);

View File

@@ -20,6 +20,7 @@
#include "ByteBuffer.h"
#include "Opcodes.h"
#include <chrono>
class WorldPacket : public ByteBuffer
{
@@ -40,7 +41,7 @@ class WorldPacket : public ByteBuffer
WorldPacket(uint32 opcode, size_t res, ConnectionType connection = CONNECTION_TYPE_DEFAULT) : WorldPacket(opcode, res, Reserve{}, connection) { }
WorldPacket(WorldPacket&& packet) noexcept : ByteBuffer(std::move(packet)), m_opcode(packet.m_opcode), _connection(packet._connection)
WorldPacket(WorldPacket&& packet) noexcept : ByteBuffer(std::move(packet)), m_opcode(packet.m_opcode), _connection(packet._connection), m_receivedTime(packet.m_receivedTime)
{
}
@@ -85,9 +86,13 @@ class WorldPacket : public ByteBuffer
ConnectionType GetConnection() const { return _connection; }
std::chrono::steady_clock::time_point GetReceivedTime() const { return m_receivedTime; }
void SetReceiveTime(std::chrono::steady_clock::time_point receivedTime) { m_receivedTime = receivedTime; }
protected:
uint32 m_opcode;
ConnectionType _connection;
std::chrono::steady_clock::time_point m_receivedTime; // only set for a specific set of opcodes, for performance reasons.
};
#endif

View File

@@ -128,7 +128,6 @@ WorldSession::WorldSession(uint32 id, std::string&& name, uint32 battlenetAccoun
m_sessionDbcLocale(sWorld->GetAvailableDbcLocale(locale)),
m_sessionDbLocaleIndex(locale),
m_latency(0),
m_clientTimeDelay(0),
_tutorialsChanged(TUTORIALS_FLAG_NONE),
_filterAddonMessages(false),
recruiterId(recruiter),
@@ -137,11 +136,17 @@ WorldSession::WorldSession(uint32 id, std::string&& name, uint32 battlenetAccoun
expireTime(60000), // 1 min after socket loss, session is deleted
forceExit(false),
m_currentBankerGUID(),
_timeSyncClockDeltaQueue(6),
_timeSyncClockDelta(0),
_pendingTimeSyncRequests(),
_battlePetMgr(std::make_unique<BattlePetMgr>(this)),
_collectionMgr(std::make_unique<CollectionMgr>(this))
{
memset(_tutorials, 0, sizeof(_tutorials));
_timeSyncNextCounter = 0;
_timeSyncTimer = 0;
if (sock)
{
m_Address = sock->GetRemoteIpAddress().to_string();
@@ -453,6 +458,18 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
if (m_Socket[CONNECTION_TYPE_REALM] && m_Socket[CONNECTION_TYPE_REALM]->IsOpen() && _warden)
_warden->Update();
if (!updater.ProcessUnsafe()) // <=> updater is of type MapSessionFilter
{
// Send time sync packet every 10s.
if (_timeSyncTimer > 0)
{
if (diff >= _timeSyncTimer)
SendTimeSync();
else
_timeSyncTimer -= diff;
}
}
ProcessQueryCallbacks();
//check if we are safe to proceed with logout
@@ -1457,3 +1474,34 @@ uint32 WorldSession::DosProtection::GetMaxPacketCounterAllowed(uint16 opcode) co
WorldSession::DosProtection::DosProtection(WorldSession* s) : Session(s), _policy((Policy)sWorld->getIntConfig(CONFIG_PACKET_SPOOF_POLICY))
{
}
void WorldSession::ResetTimeSync()
{
_timeSyncNextCounter = 0;
_pendingTimeSyncRequests.clear();
}
void WorldSession::SendTimeSync()
{
WorldPackets::Misc::TimeSyncRequest timeSyncRequest;
timeSyncRequest.SequenceIndex = _timeSyncNextCounter;
SendPacket(timeSyncRequest.Write());
_pendingTimeSyncRequests[_timeSyncNextCounter] = getMSTime();
// Schedule next sync in 10 sec (except for the 2 first packets, which are spaced by only 5s)
_timeSyncTimer = _timeSyncNextCounter == 0 ? 5000 : 10000;
_timeSyncNextCounter++;
}
uint32 WorldSession::AdjustClientMovementTime(uint32 time) const
{
int64 movementTime = int64(time) + _timeSyncClockDelta;
if (_timeSyncClockDelta == 0 || movementTime < 0 || movementTime > 0xFFFFFFFF)
{
TC_LOG_WARN("misc", "The computed movement time using clockDelta is erronous. Using fallback instead");
return GameTime::GetGameTimeMS();
}
else
return uint32(movementTime);
}

View File

@@ -34,7 +34,9 @@
#include "Packet.h"
#include "RaceMask.h"
#include "SharedDefines.h"
#include <boost/circular_buffer.hpp>
#include <array>
#include <map>
#include <unordered_map>
#include <unordered_set>
@@ -894,8 +896,8 @@ public:
explicit PacketFilter(WorldSession* pSession) : m_pSession(pSession) { }
virtual ~PacketFilter() { }
virtual bool Process(WorldPacket* /*packet*/) = 0;
virtual bool ProcessUnsafe() const { return false; }
virtual bool Process(WorldPacket* /*packet*/) { return true; }
virtual bool ProcessUnsafe() const { return true; }
protected:
WorldSession* const m_pSession;
@@ -912,7 +914,9 @@ public:
explicit MapSessionFilter(WorldSession* pSession) : PacketFilter(pSession) { }
~MapSessionFilter() { }
bool Process(WorldPacket* packet) override;
virtual bool Process(WorldPacket* packet) override;
//in Map::Update() we do not process player logout!
virtual bool ProcessUnsafe() const override { return false; }
};
//class used to filer only thread-unsafe packets from queue
@@ -1122,7 +1126,6 @@ class TC_GAME_API WorldSession
uint32 GetLatency() const { return m_latency; }
void SetLatency(uint32 latency) { m_latency = latency; }
void ResetClientTimeDelay() { m_clientTimeDelay = 0; }
std::atomic<time_t> m_timeOutTime;
@@ -1134,6 +1137,11 @@ class TC_GAME_API WorldSession
uint32 GetRecruiterId() const { return recruiterId; }
bool IsARecruiter() const { return isRecruiter; }
// Time Synchronisation
void ResetTimeSync();
void SendTimeSync();
uint32 AdjustClientMovementTime(uint32 time) const;
// Battle Pets
BattlePetMgr* GetBattlePetMgr() const { return _battlePetMgr.get(); }
@@ -1587,7 +1595,7 @@ class TC_GAME_API WorldSession
void HandleSetDungeonDifficultyOpcode(WorldPackets::Misc::SetDungeonDifficulty& setDungeonDifficulty);
void HandleSetRaidDifficultyOpcode(WorldPackets::Misc::SetRaidDifficulty& setRaidDifficulty);
void HandleSetTitleOpcode(WorldPackets::Character::SetTitle& packet);
void HandleTimeSyncResponse(WorldPackets::Misc::TimeSyncResponse& packet);
void HandleTimeSyncResponse(WorldPackets::Misc::TimeSyncResponse& timeSyncResponse);
void HandleWhoIsOpcode(WorldPackets::Who::WhoIsRequest& packet);
void HandleResetInstancesOpcode(WorldPackets::Instance::ResetInstances& packet);
void HandleInstanceLockResponse(WorldPackets::Instance::InstanceLockResponse& packet);
@@ -1900,7 +1908,6 @@ class TC_GAME_API WorldSession
LocaleConstant m_sessionDbcLocale;
LocaleConstant m_sessionDbLocaleIndex;
std::atomic<uint32> m_latency;
std::atomic<uint32> m_clientTimeDelay;
AccountData _accountData[NUM_ACCOUNT_DATA_TYPES];
uint32 _tutorials[MAX_ACCOUNT_TUTORIAL_VALUES];
uint8 _tutorialsChanged;
@@ -1914,6 +1921,14 @@ class TC_GAME_API WorldSession
bool forceExit;
ObjectGuid m_currentBankerGUID;
boost::circular_buffer<std::pair<int64, uint32>> _timeSyncClockDeltaQueue; // first member: clockDelta. Second member: latency of the packet exchange that was used to compute that clockDelta.
int64 _timeSyncClockDelta;
void ComputeNewClockDelta();
std::map<uint32, uint32> _pendingTimeSyncRequests; // key: counter. value: server time when packet with that counter was sent.
uint32 _timeSyncNextCounter;
uint32 _timeSyncTimer;
std::unique_ptr<BattlePetMgr> _battlePetMgr;
std::unique_ptr<CollectionMgr> _collectionMgr;

View File

@@ -468,6 +468,9 @@ WorldSocket::ReadDataHandlerResult WorldSocket::ReadDataHandler()
/* fallthrough */
default:
{
if (opcode == CMSG_TIME_SYNC_RESPONSE)
packet.SetReceiveTime(std::chrono::steady_clock::now());
sessionGuard.lock();
LogOpcodeText(opcode, sessionGuard);
@@ -1035,10 +1038,7 @@ bool WorldSocket::HandlePing(WorldPackets::Auth::Ping& ping)
std::lock_guard<std::mutex> sessionGuard(_worldSessionLock);
if (_worldSession)
{
_worldSession->SetLatency(ping.Latency);
_worldSession->ResetClientTimeDelay();
}
else
{
TC_LOG_ERROR("network", "WorldSocket::HandlePing: peer sent CMSG_PING, but is not authenticated or got recently kicked, address = %s", GetRemoteIpAddress().to_string().c_str());