分布式各系统时间统一程序

news2024/11/26 20:39:57

目录

  • 1、背景
  • 2、Cristian's algorithm 算法(克里斯蒂安算法)
  • 3、实现思路
    • 3.1、步骤:
    • 3.2、公式
  • 4、具体代码
    • 4.1、构建时间戳
    • 4.2、定义数据包
    • 4.3、客户端实现
    • 4.3、服务端实现
  • 说明


1、背景

使用场景是在一个大型分布式系统下,对时间有一个较高的水平要求。因为需要矫正每台运行服务的主机时间。

2、Cristian’s algorithm 算法(克里斯蒂安算法)

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信息。所以这个包将很简单。state的范围: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();
}

说明

这里我删减了一些不重要代码,原本都是纯后台的。所以我懒得再改代码截效果了。请读者自便。有技术问题请联系我。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/662518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第一天,掌握PyTorch的张量创建

文章目录 一&#xff0c;张量二&#xff0c;创建张量1. 直接从数据来创建张量Tensor函数TODO &#xff1a;从数据直接创建张量 2. 从numpy数据创建张量from_numpy函数从numpy数据创建张量 3. 从另一个张量来进行创建张量4. 使用随机值或者常数值来创建张量5. 张量的属性&#x…

【C】C语言数据类型、常量变量的介绍

C语言基础 数据类型常量和变量变量全局变量和局部变量变量的作用域和生命周期作用域生命周期 常量 数据类型 下图为C语言常见的数据类型&#xff1a; &#xff08;浮点数就是我们常见的小数&#xff0c;字符类型要用‘’引起来&#xff0c;在C语言中字符串类型要用字符数组来…

如何知识变现?介绍几个变现途径

哈喽&#xff0c;大家好&#xff0c;我是海哥&#xff0c;知识付费变现创业教练&#xff0c;教育公司培训总监&#xff0c;从事知识付费变现咨询10年&#xff0c;已助力3000人实现知识付费变现。 在互联网时代&#xff0c;所有线下的产业都可以在线上再做一遍&#xff0c;知识产…

简易版python爬虫--通过关键字爬取网页

背景&#xff1a; 帮同学写了个爬虫程序&#xff0c;特此记录&#xff0c;怕以后忘了 这里是爬取百度https://www.baidu.com 不为什么&#xff0c;主要就是百度老实&#xff0c;能爬&#xff0c;爬着简单&#xff0c;爬着不犯法。。。 关键字爬取基本模板&#xff1a; import…

Git第一章、Git的原理与使用

背景知识&#xff1a; 我们在编写各种文档时&#xff0c;为了防止文档丢失&#xff0c;更改失误&#xff0c;失误后能恢复到原来的版本&#xff0c;不得不复制出一个副本。每个版本有各自的内容&#xff0c;但最终会只有一份报告需要被我们使用 。但在此之前的工作都需要这些不…

[Flutter]理解Widget-Key的作用

这里主要是理解在Widget中key的作用/用途。 import dart:math;import package:flutter/material.dart;/// 这里主要是理解在Widget中key的作用/用途。 void main() {runApp(const MyApp()); }class MyApp extends StatelessWidget {const MyApp({super.key});overrideWidget b…

Vault从入门到精通系列之二:启动Vault服务器

Vault从入门到精通系列之二&#xff1a;启动Vault服务器 一、启动开发服务器二、设置环境变量三、验证服务器正在运行四、vault命令汇总 Vault 作为客户端-服务器应用程序运行。Vault 服务器是唯一与数据存储和后端交互的 Vault 架构。通过 Vault CLI 完成的所有操作都通过 TLS…

【Leetcode60天带刷】day09字符串—— 459.重复的子字符串,28. 实现 strStr(),剑指Offer 05.替换空格

题目&#xff1a; 459. 重复的子字符串 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s "abab" 输出: true 解释: 可由子串 "ab" 重复两次构成。示例 2: 输入: s "aba" 输出: false示例…

Grammarly:AI语法检测写作助手工具

【产品介绍】 Grammarly是于2009年发布&#xff0c;当前使用最普遍和准确的英语语法检查&#xff08;grammar checker&#xff09;、拼写、校对&#xff08;proofreading&#xff09;检查和抄袭&#xff08;plagiarism&#xff09;检测软件工具&#xff0c;其中Grammarly校对检…

pytorch笔记:transformer 和 vision transformer

来自B站视频&#xff0c;API查阅&#xff0c;TORCH.NN seq2seq 可以是 CNN&#xff0c;RNN&#xff0c;transformer nn.Transformer 关键源码&#xff1a; encoder_layer TransformerEncoderLayer(d_model, nhead, dim_feedforward, dropout,activation, layer_norm_eps, ba…

杂谈 | 人类微生物组研究:解析挑战与前景

谷禾健康 人类微生物组研究正在从描述关联发展到了解整个微生态对人类的影响。虽然存在挑战&#xff0c;但在应用数据驱动的微生物组诊断和干预方面正在取得进展&#xff0c;这可能会在未来十年内带来精准医学的突破。 本文我们来探讨关于微生物组的研究进展及其对人类健康的影…

Windows10配置Kiosk(展台)模式

Windows10配置Kiosk&#xff08;展台&#xff09;模式 新建Kiosk专用用户 进入windows设置->账户->家庭与其他用户&#xff0c;点击’将其他人添加到这台电脑’ 在弹出的对话框里&#xff0c;点击我没有这个人的登录信息&#xff0c;然后添加一个没有Microsoft账户的用…

什么是眼图

眼图是指示波器用余辉方式将捕获的波形按每3bit的形式累积叠加显示采集到的串行信号的比特位的结果&#xff0c;叠加后的图形形状看起来和眼睛很像 眼图有很多参数&#xff0c;可以用来量化信号的质量&#xff0c;例如&#xff1a; 眼高&#xff1a;指眼图中最大和最小电压之差…

WOT全球技术创新大会2023在京召开:创新不止,实战为王

AIGC、大模型、大算力异常火爆的背后&#xff0c;其应用场景是什么、商业实践有何规律可循&#xff1f;多云实践、业务架构演进、研发效能等看似“老生常谈”的技术热点&#xff0c;在崇尚业务创新的当前时代有何新的发展趋势&#xff1f; 6月16-17日&#xff0c;51CTO集结50来…

android存储2--初始化.存储service的启动

android版本&#xff1a;android-11.0.0_r21 http://aospxref.com/android-11.0.0_r21/ android存储初始化分3个阶段&#xff1a; 1&#xff09;清理环境。因android支持多用户&#xff0c;解锁后登录的可能是另一个用户&#xff0c;需把之前用户执行的一些信息清理干净。《an…

Net6.0项目升级到Net7.0

NetCore3.1升级到Net6.0&#xff0c;可参考此文章&#xff1a;NetCore3.1项目升级到Net6.0_vs2022 没有startup_csdn_aspnet的博客-CSDN博客 其实与我之前发布的步骤基本一致&#xff0c;升级到net6.0之后&#xff0c;在升级net7.0基本没有可修改的代码&#xff0c;只是升级一…

NOTA双功能螯合剂:NOTA PEG11 MeTz,NOTA PEG11 Azide,两者试剂信息知识总结说明

NOTA及其衍生物是新型双功能整合剂之一。NOTA及其衍生物具有良好的配位和鳌合能力&#xff0c;可作为过渡金属离子的配体。 本文主要就NOTA PEG11 MeTz&#xff0c;NOTA PEG11 Azide两者进行说明&#xff0c;以下内容希望可以帮助到大家。 一、MeTz-PEG11-NOTA 理论分析&#…

【spring源码系列-06】refresh中obtainFreshBeanFactory方法的执行流程

Spring源码系列整体栏目 内容链接地址【一】spring源码整体概述https://blog.csdn.net/zhenghuishengq/article/details/130940885【二】通过refresh方法剖析IOC的整体流程https://blog.csdn.net/zhenghuishengq/article/details/131003428【三】xml配置文件启动spring时refres…

汽车云端主题的实现与应用

主题指的是车载电子中&#xff0c;如仪表&#xff0c;IVI等跟视觉相关的显示设备、包括车身&#xff0c;在不同的自定义模式下的不同显示表现。我们也可以把它理解成为皮肤。 传统的车身皮肤&#xff08;别克ELECTRA E5&#xff09; 传统主题的特点 固定&#xff0c;单一&…

使用Flow发送企业应用的通知到微信里

今天我们以kintone作为企业应用管理平台来解释&#xff0c;怎么使用flow发送企业的通知到微信上。 完成后的样子 &#xff11;&#xff0e;Microsoft Flow响应kintone添加记录的Webhook。 &#xff12;&#xff0e;Microsoft Flow向微信发送消息。 微信是什么&#xff1f; …