分散システム時刻統一プログラム


1. 背景

使用シナリオは大規模な分散システム下であり、時間に関して高度な要件があります。サービスを実行している各ホストの時刻を修正する必要があるためです。

2. クリスティアンアルゴリズムアルゴリズム(クリスチャンアルゴリズム)

Cristian's algorithmアルゴリズムは、クライアント プロセスを通じてタイム サーバーと時間を同期するために使用されるクロック同期アルゴリズムです。このアルゴリズムは、精度に比べてラウンドトリップ時間が短く、冗長性が生じやすい分散システム/アプリケーションはこのアルゴリズムと共存できない低遅延ネットワークに適しています。ここでのラウンドトリップ時間とは、リクエストの開始から対応する応答の終了までの時間を指します。
ここに画像の説明を挿入

3. 実装アイデア

T_0 をクライアントの時刻取得要求の現地時刻とします。
T_1 を、クライアントがサーバーの確認パケットを受信した現地時刻とします。

3.1. 手順:

1. まず、クライアントが時刻要求パケットを送信します。
2. サーバーは、クライアントにサーバーの現地時間を即座に通知するパケットを受信します。
3. クライアントはサーバーから受信パケットを受信し、誤差計算を実行します。 。

3.2、公式

公式:
ここに画像の説明を挿入

その内:
T_0 は、クロック時間を取得するためにクライアントによって要求されたローカル時間です。
T_1 は、クライアントがサーバーから受信パケットを受信したときの現地時間です。
T_server は、サーバーによって返されたクロック時間です。
T_client 同期クロック時間
T_1 ~ T_0 は、ネットワークとサーバーがサーバーにリクエストを送信し、リクエストを処理し、クライアント プロセスに応答を返すまでにかかる合計時間を指します。ここでは、ネットワーク遅延 T_0 と T_1 はほぼ等しいと想定されます。

クライアントの時刻とサーバーの時刻の差は最大でも (T_1 - T_0)/2 です。

したがって、t を最大許容誤差とみなします。

4. 特定のコード

4.1. ビルドのタイムスタンプ

送信されるパッケージには現地時間が必要なので。したがって、適切なタイムスタンプを構築する必要があります。もちろん、C++ 標準ライブラリに付属しているタイムスタンプを使用することもできます。

struct DateTime
{
    
    
	int year;
	int month;
	int day;
	int hour;
	int min;
	int sec;
	int milliSec;
};

その後の処理と時間の計算のために、いくつかの便利なメソッドがここで提供されます。

たとえば、カスタム タイムスタンプを QDatetime に、カスタム タイムスタンプを SYSTEMTIME に、などです。

	QDateTime DateTime::ToQDateTime() const
	{
    
    
		QString str = QString("%1-%2-%3 %4:%5:%6 %7")
			.arg(year).arg(month).arg(day).arg(hour).arg(min).arg(sec).arg(milliSec);
		QDateTime time = QDateTime::fromString(str, "yyyy-M-d h:m:s z");
	
		return time;
	}
	SYSTEMTIME DateTime::ToSystmTime() const
	{
    
    
		SYSTEMTIME time;
		time.wYear = year;
		time.wMonth = month;
		time.wDay = day;
		time.wHour = hour;
		time.wMinute = min;
		time.wSecond = sec;
		time.wMilliseconds = milliSec;

		return time;
	}

	void DateTimeFrom::QDateTime(const QDateTime& time)
	{
    
    
		year = time.date().year();
		month = time.date().month();
		day = time.date().day();
		hour = time.time().hour();
		min = time.time().minute();
		sec = time.time().second();
		milliSec = time.time().msec();
	}

	DateTime DateTime::operator -(const DateTime& other)
	{
    
    
		QDateTime t1 = this->ToQDateTime();
		QDateTime t2 = other.ToQDateTime();
		qint64 d1 = t1.toMSecsSinceEpoch();
		qint64 d2 = t2.toMSecsSinceEpoch();
		QDateTime interval = QDateTime::fromMSecsSinceEpoch(abs(t1.toMSecsSinceEpoch() - t2.toMSecsSinceEpoch()));
		DateTime time;


		time.FromQDateTime(interval);
		time.hour -= 8;
		return time;
	}

	DateTime DateTime::operator +(const DateTime& other)
	{
    
    
		QDateTime t1 = this->ToQDateTime();
		QDateTime t2 = other.ToQDateTime();
		QDateTime interval = QDateTime::fromMSecsSinceEpoch(t1.toMSecsSinceEpoch() + t2.toMSecsSinceEpoch());
		DateTime time;
		time.FromQDateTime(interval);
		return time;
	}

	DateTime& DateTime::operator +=(const DateTime& other)
	{
    
    
		QDateTime t1 = this->ToQDateTime();
		QDateTime t2 = other.ToQDateTime();

		QDateTime interval = QDateTime::fromMSecsSinceEpoch(t1.toMSecsSinceEpoch() + t2.toMSecsSinceEpoch());
		FromQDateTime(interval);

		return *this;
	}
};

4.2. データパッケージの定義

必要なのはタイムスタンプのみであり、パケットが要求パケットであるか受信パケットであるか、およびパケット自体の IP 情報を識別する必要があるためです。そのため、このパッケージはシンプルになります。状態の範囲: 1-要求 2-受信

struct ClockPag
{
    
    
	char client[40]{
    
     0 };
	int state = 0; 
	DateTime time;
};

4.3. クライアントの実装

ヘッダー ファイル ClockClient.h

#pragma once

#include "DateTime.h"

#include <QUdpsocket>
#include <QNetworkDatagram>
#include <QTimer>
extern int g_step;
constexpr int gc_port = 10023;
constexpr int gc_recvPort = 10024;

class ClockClient : public QObject
{
    
    
	Q_OBJECT
public:
	ClockClient();

	void Start(int step = g_step * 1000);
	void Stop();
private:
	bool CorrectionTime();
	void SendLocaltime();
	QString GetLocalIp();
	bool InitNetWork();
public:
	DWORD AppOfSelfCheck(LPCTSTR name);
	HANDLE GetProcessHandleByID(DWORD nID);
	std::vector<DWORD> GetProcessIDByname(const char * name);
private slots:
	void onTimerServer();

private:
	QString m_localIp;
	qint32 m_localPort;

	ClockPag m_clockPag;

	QUdpSocket m_send;
	QUdpSocket m_rece;

	DateTime m_oldTime;
	DateTime m_currTime;
	DateTime m_serviceTime;
	QTimer m_timer;
	QList<QHostAddress> ipAddressesList;
};

ソースファイル ClockClient.cpp

#include "ClockClient.h"

#include <iostream>
#include <minwindef.h>
#include <QList>
#include <QNetworkInterface>
extern int g_step;
ClockClient::ClockClient()
	: m_localIp{
    
     GetLocalIp() }
	, m_localPort{
    
     gc_port }
{
    
    
	bool ret = connect(&m_timer, &QTimer::timeout, this, &ClockClient::SendLocaltime);
	if (false == ret)
	{
    
    
		std::cout << "绑定失败!" << std::endl;
		exit(EXIT_FAILURE);
	}
	if (false == InitNetWork())
	{
    
    
		std::cout << "网络初始化失败!" << std::endl;
		exit(EXIT_FAILURE);
	}
}


bool ClockClient::InitNetWork()
{
    
    
	bool ret = m_rece.bind(gc_recvPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
	if (false == ret)
	{
    
    
		return false;
	}
	ret = connect(&m_rece, &QUdpSocket::readyRead, this, &ClockClient::onTimerServer);

	return ret;
}


void ClockClient::Start(int step)
{
    
    
	m_timer.start(step); // step:10s
}

void ClockClient::Stop()
{
    
    
	m_timer.stop();
}

void ClockClient::onTimerServer()
{
    
    
	while (true == m_rece.hasPendingDatagrams())
	{
    
    
		QByteArray ba = m_rece.receiveDatagram().data();

		char* buffer = new char[ba.size()];
		memcpy(buffer, ba.data(), ba.size());
		memset(m_clockPag.client, 0, sizeof(m_clockPag.client));
		m_clockPag = *reinterpret_cast<ClockPag*>(buffer);

		QString ip = m_rece.receiveDatagram().senderAddress().toString();
		//ip = ip.mid(7);
		if (m_localIp != QString(m_clockPag.client)
			|| ip == m_localIp || m_clockPag.state != 2)
		{
    
    
			delete[] buffer;
			buffer = nullptr;
			continue;
		}
		m_clockPag.state = 3;
		m_serviceTime = m_clockPag.time;//服务器时间
		m_currTime.FromQDateTime(QDateTime::currentDateTime()); // 本地时间
		delete[] buffer;
		buffer = nullptr;
		std::cout << "设置状态:" << CorrectionTime() << std::endl;
	}
}

bool ClockClient::CorrectionTime()
{
    
    
	DateTime rtt = m_currTime - m_oldTime;
	rtt.hour += 8;
	qint64 eee = rtt.ToQDateTime().toMSecsSinceEpoch();
	qint64 rtt2 = rtt.ToQDateTime().toMSecsSinceEpoch() / 2; // 平均延时
	std::cout << "校验周期(毫秒)" << rtt2 << std::endl;
	DateTime midTime;
	midTime.FromQDateTime(QDateTime::fromMSecsSinceEpoch(rtt2));

	DateTime localTime = m_serviceTime + midTime;

	//if(m_serviceTime.ToQDateTime().toMSecsSinceEpoch() >)

	if (localTime.ToQDateTime().toMSecsSinceEpoch() > m_currTime.ToQDateTime().toMSecsSinceEpoch())
	{
    
    
		DateTime tmp = localTime - m_currTime;
		tmp.hour += 8;
		std::cout << "本地误差(毫秒)" << tmp.ToQDateTime().toMSecsSinceEpoch() << std::endl;
	}
	else
	{
    
    
		DateTime tmp = m_currTime - localTime;
		tmp.hour += 8;
		std::cout << "本地误差(毫秒)" << tmp.ToQDateTime().toMSecsSinceEpoch() << std::endl;
	}
	SYSTEMTIME myTime = localTime.ToSystmTime();

	return SetLocalTime(&myTime);
}

void ClockClient::SendLocaltime()
{
    
    
	DateTime currTime{
    
    };
	currTime.FromQDateTime(QDateTime::currentDateTime());

	m_clockPag.time = currTime;
	memset(m_clockPag.client, 0, sizeof(m_clockPag.client));
	char* ptr = const_cast<char*>(m_localIp.toStdString().c_str());
	std::string str = m_localIp.toLocal8Bit().toStdString();
	m_clockPag.state = 1;
	for (int i = 0; i < m_localIp.size(); ++i)
	{
    
    
		m_clockPag.client[i] = m_localIp[i].toLatin1();
	}
	int len = m_send.writeDatagram(reinterpret_cast<char*>(&m_clockPag), sizeof(m_clockPag),
		QHostAddress::Broadcast, m_localPort);

	if (len != sizeof(m_clockPag))
	{
    
    
		return ;
	}
	m_oldTime = currTime;

	return ;
}

QString ClockClient::GetLocalIp()
{
    
    
	QString ipAddress;
	foreach(QNetworkInterface netInterface, QNetworkInterface::allInterfaces())
	{
    
    
		QList<QNetworkAddressEntry>  entryList = netInterface.addressEntries();

		foreach(QNetworkAddressEntry entry, entryList)
		{
    
    
			if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && entry.ip() != QHostAddress::LocalHost)
			{
    
    
				QString LocalIP = entry.ip().toString();
				
				if (LocalIP.startsWith("192.", Qt::CaseSensitive))
				{
    
    
					ipAddress = entry.ip().toString();
				}
			}
		}
	}
	return ipAddress;
}

メイン関数 main.cpp

#include "ClockClient.h"
#include <iostream>
#include <string>
#include <string.h>
#include "Windows.h"
#include <tchar.h>
#include <comdef.h>
#include "tlhelp32.h"
int g_step = 20;

int main(int argc, char* argv[])
{
    
    
	QCoreApplication a(argc, argv);
	ClockClient client;
	client.Start();

	return a.exec();
}

4.3. サーバーの実装

ヘッダー ファイル ClockServer.h

#pragma once

#include "DateTime.h"

#include <QUdpsocket>
#include <QNetworkDatagram>

constexpr int gc_port = 10023;
constexpr int gc_recvPort = 10024;
class ClockServer : public QObject
{
    
    
	Q_OBJECT
public:
	ClockServer();
	HANDLE GetProcessHandleByID(DWORD nID);
	std::vector<DWORD> GetProcessIDByname(const char * name);
	void AppOfSelfCheck(const char * name);
private:
	bool InitNetWork();
	void onCheckTime();
private:
	qint32 m_localPort;

	QUdpSocket m_send;
	QUdpSocket m_rece;
};

ソースファイル ClockServer.cpp

#include "ClockServer.h"

#include <iostream>

ClockServer::ClockServer()
	:m_localPort{
    
    gc_port}
{
    
    
	bool ret = InitNetWork();
	if (false == ret)
	{
    
    
		std::cout << "网络初始化失败" << std::endl;
		exit(EXIT_FAILURE);
	}
}

bool ClockServer::InitNetWork()
{
    
    
	bool ret = m_rece.bind(/*QHostAddress(m_localIp),*/ m_localPort);
	if (false == ret)return ret;
	ret = connect(&m_rece, &QUdpSocket::readyRead, this, &ClockServer::onCheckTime);
	m_rece.setSocketOption(QAbstractSocket::MulticastLoopbackOption, 0);
	return ret;
}

void ClockServer::onCheckTime()
{
    
    
	while (true == m_rece.hasPendingDatagrams())
	{
    
    
		QByteArray ba = m_rece.receiveDatagram().data();

		char* buffer = new char[ba.size()];
		if(nullptr == buffer)continue;
		memcpy(buffer, ba.data(), ba.size());

		ClockPag data;
		data = *reinterpret_cast<ClockPag*>(buffer);
		delete[] buffer;
		buffer = nullptr;
		if (data.state != 1)
		{
    
    
			continue;
		}
		int port = gc_port;
		DateTime currTime{
    
    };
		currTime.FromQDateTime(QDateTime::currentDateTime());
		data.state = 2;
		data.time = currTime;
		int len = m_send.writeDatagram(reinterpret_cast<char*>(&data),
			sizeof(data),
			QHostAddress::Broadcast, gc_recvPort);

		qDebug() << u8"收到 ip:" << QString(data.client) << u8"的时钟校验包";
	}
}

メインプログラム mian.cpp

#include <QtCore/QCoreApplication>

#include "ClockServer.h"

int main(int argc, char* argv[])
{
    
    
	QCoreApplication a(argc, argv);
	ClockServer server;
	return a.exec();
}

説明する

ここでは、元々は純粋な背景であったいくつかの重要でないコードを削除しました。したがって、効果をインターセプトするためにコードを変更するのが面倒です。読者はご自身の判断で。技術的なご質問がございましたらお問い合わせください。

おすすめ

転載: blog.csdn.net/qq_45254369/article/details/131288712