Skip to content

Commit 9ff45e2

Browse files
committed
fix: ntp
1 parent 2f9bf33 commit 9ff45e2

3 files changed

Lines changed: 188 additions & 10 deletions

File tree

blog/sharing/2025-12-16.mdx

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: "ExpTech NTP Server"
3-
description: "ExpTech NTP Server 已經正式上線,提供 SNTP、HTTP 兩種時間同步方式"
3+
description: "越來越多 ExpTech 相關產品及服務需要時間同步服務,在 ttyAMA0 的幫助下,架設了 ExpTech NTP Server,同時整理了舊的時間同步機制"
44
date: 2025-12-16
55
slug: sharing-2025-12-16
66
image: /img/ExpTech_Studio_white.png
@@ -10,24 +10,29 @@ keywords: [開發分享]
1010
---
1111

1212
import ImageLightbox from "@site/src/components/ImageLightbox";
13+
import GithubUserCard from "@site/src/components/GithubUserCard";
1314

1415
越來越多 ExpTech 相關產品及服務需要時間同步服務,
15-
因此決定架設一個 NTP Server,提供時間同步服務。
16+
在 <GithubUserCard username="bclswl0827" displayName="ttyAMA0" /> 的幫助下,
17+
剛好有這個機會,加上我自己小小加料一下,
18+
就有了這個 ExpTech NTP Server,此外也順便一起把舊的時間同步機制整理一下。
1619

1720
{/* truncate */}
1821

1922
## 舊架構
2023

21-
採用 HTTP 在 Body 提供 Unix Timestamp (text/plain) 的時間同步服務,
22-
很陽春的設計,因為當時對時間精度沒有特別要求,所以很長一段時間都是維持這個狀態
24+
早期是用一個很簡單的 HTTP 服務,在 Body 回傳 Unix Timestamp`text/plain`)。
25+
因為當時對時間精度沒有特別要求,所以這個「陽春版」其實撐了滿長一段時間
2326

2427
## 新架構
2528

26-
time.exptech.com.tw (Port 123) 提供 SNTP (UDP) 時間同步服務,
27-
同時也提供 HTTP 的時間同步服務
29+
現在改成在 `time.exptech.com.tw:123` 提供 **SNTPUDP** 時間同步服務,
30+
同時保留 **HTTP** 介面,方便在無法用 UDP 的環境(例如 Web)也能對時
2831

2932
### SNTP
3033

34+
先拿幾個常見的公共 NTP Server 來對照一下:
35+
3136
#### 各家 NTP Server 對照
3237

3338
<div
@@ -77,20 +82,30 @@ import ImageLightbox from "@site/src/components/ImageLightbox";
7782

7883
#### ExpTech NTP Server
7984

85+
最後是主角 ExpTech 自家的 NTP Server:
86+
8087
<ImageLightbox
8188
src="/img/sharing/2025-12-16/exptech.png"
8289
alt="ExpTech NTP Server"
8390
/>
8491

85-
### HTTP GET (text/plain)
92+
### HTTP GET(text/plain)
93+
94+
除了 SNTP 以外,也可以用 HTTP 的方式取得時間:
8695

87-
提供 X-NTP-T2 及 X-NTP-T3 兩個 Header,也可直接利用 Body (向後相容舊架構) 取得 Unix Timestamp。
96+
- 會在 Header 回傳 `X-NTP-T2``X-NTP-T3`
97+
- 同時也會在 Body 回傳 Unix Timestamp(向下相容舊架構)
8898

8999
<ImageLightbox
90100
src="/img/sharing/2025-12-16/http.png"
91-
alt="ExpTech NTP(HTTP) Server"
101+
alt="ExpTech NTP (HTTP) Server"
92102
/>
93103

94104
## 總結
95105

96-
SNTP 協議下實測表現優秀,HTTP 協議下也足夠使用,HTTP 提供給無法建立 UDP 的情境如 Web,可依需求選擇使用。
106+
實測結果:
107+
108+
- **SNTP 協議**:誤差約 **±5ms**,精度表現很好,實測結果相當穩定
109+
- **HTTP 協議**:誤差約 **±50ms**(主要是 TCP 開銷),雖然精度沒有 SNTP 那麼理想,但在 Web 等無法使用 UDP 的情境下,非常實用
110+
111+
依照實際使用環境選擇 SNTP 或 HTTP,就能在不同平台上都維持不錯的時間同步效果。
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { JSX } from "react";
2+
import styles from "./styles.module.css";
3+
4+
type GithubUserCardProps = {
5+
username: string;
6+
displayName?: string;
7+
description?: string;
8+
};
9+
10+
export default function GithubUserCard({
11+
username,
12+
displayName,
13+
description,
14+
}: GithubUserCardProps): JSX.Element {
15+
const avatarUrlSmall = `https://github.com/${username}.png?size=32`;
16+
const avatarUrlLarge = `https://github.com/${username}.png?size=128`;
17+
const profileUrl = `https://github.com/${username}`;
18+
19+
return (
20+
<span className={styles.wrapper}>
21+
<a
22+
href={profileUrl}
23+
target="_blank"
24+
rel="noopener noreferrer"
25+
className={styles.badge}
26+
>
27+
<img
28+
src={avatarUrlSmall}
29+
alt={`${username} avatar`}
30+
className={styles.avatarSmall}
31+
/>
32+
<span className={styles.name}>{displayName || username}</span>
33+
</a>
34+
35+
<a
36+
href={profileUrl}
37+
target="_blank"
38+
rel="noopener noreferrer"
39+
className={styles.popup}
40+
>
41+
<div className={styles.card}>
42+
<img
43+
src={avatarUrlLarge}
44+
alt={`${username} avatar`}
45+
className={styles.avatarLarge}
46+
/>
47+
<div className={styles.cardContent}>
48+
<div className={styles.cardTitle}>{displayName || username}</div>
49+
<div className={styles.cardUsername}>@{username}</div>
50+
{description && (
51+
<div className={styles.cardDescription}>{description}</div>
52+
)}
53+
</div>
54+
</div>
55+
</a>
56+
</span>
57+
);
58+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
.wrapper {
2+
display: inline-block;
3+
position: relative;
4+
}
5+
6+
.badge {
7+
display: inline-flex;
8+
align-items: center;
9+
gap: 6px;
10+
padding: 4px 10px;
11+
background: var(--ifm-color-emphasis-100);
12+
border: 1px solid var(--ifm-color-emphasis-200);
13+
border-radius: 16px;
14+
text-decoration: none;
15+
color: inherit;
16+
transition: all 0.2s ease;
17+
vertical-align: middle;
18+
font-size: 14px;
19+
line-height: 1;
20+
}
21+
22+
.badge:hover {
23+
background: var(--ifm-color-emphasis-200);
24+
border-color: var(--ifm-color-primary);
25+
color: inherit;
26+
text-decoration: none;
27+
transform: translateY(-1px);
28+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
29+
}
30+
31+
.avatarSmall {
32+
width: 20px;
33+
height: 20px;
34+
border-radius: 50%;
35+
flex-shrink: 0;
36+
}
37+
38+
.name {
39+
font-weight: 500;
40+
color: var(--ifm-color-content);
41+
}
42+
43+
.popup {
44+
position: absolute;
45+
top: 125%;
46+
left: 0;
47+
z-index: 20;
48+
text-decoration: none;
49+
color: inherit;
50+
opacity: 0;
51+
pointer-events: none;
52+
transform: translateY(4px) scale(0.97);
53+
transition: opacity 0.15s ease, transform 0.15s ease;
54+
}
55+
56+
.wrapper:hover .popup {
57+
opacity: 1;
58+
pointer-events: auto;
59+
transform: translateY(0) scale(1);
60+
}
61+
62+
.card {
63+
display: flex;
64+
align-items: center;
65+
gap: 12px;
66+
padding: 12px;
67+
min-width: 220px;
68+
max-width: 280px;
69+
background: var(--ifm-card-background-color);
70+
border-radius: 12px;
71+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
72+
border: 1px solid var(--ifm-color-emphasis-200);
73+
}
74+
75+
.avatarLarge {
76+
width: 56px;
77+
height: 56px;
78+
border-radius: 50%;
79+
flex-shrink: 0;
80+
border: 2px solid var(--ifm-color-emphasis-200);
81+
}
82+
83+
.cardContent {
84+
min-width: 0;
85+
}
86+
87+
.cardTitle {
88+
font-weight: 600;
89+
font-size: 16px;
90+
color: var(--ifm-heading-color);
91+
margin-bottom: 2px;
92+
}
93+
94+
.cardUsername {
95+
font-size: 13px;
96+
color: var(--ifm-color-emphasis-600);
97+
margin-bottom: 4px;
98+
font-family: var(--ifm-font-family-monospace);
99+
}
100+
101+
.cardDescription {
102+
font-size: 13px;
103+
color: var(--ifm-color-emphasis-700);
104+
line-height: 1.3;
105+
}

0 commit comments

Comments
 (0)