2026年9月16日 星期三

Python UDP 分組網路實作:1 位組長收集 5 位組員的 IP、MAC、學號與姓名

大一網際網路應用|Python UDP 分組網路實作:1 位組長收集 5 位組員的 IP、MAC、學號與姓名

在「網際網路應用」課程中,我們會學到 IP Address、MAC Address、Port、Socket、TCP、UDP、Client 與 Server 等重要觀念。 如果只記住名詞定義,很難真正理解電腦之間到底如何交換資料。

因此這次我們直接進行一個學生分組網路實驗。 每組由一位學生擔任組長(Group Leader), 組長的電腦同時擔任 UDP Server; 另外 5 位學生擔任組員(Node)

每位組員執行 Python 程式後,只要輸入自己的學號與姓名, 程式會取得該電腦的 IP 與 MAC Address, 再透過 UDP 傳送到組長電腦。

本次實驗的核心問題:
如何讓一組學生自己建立一個小型區域網路, 由「組長 Server」收集 5 位「組員 Node」的網路與身分資料?

一、學習目標

  • 理解 Client / Server 網路架構。
  • 了解 IP Address、MAC Address 與 Port 的基本角色。
  • 了解 TCP 與 UDP 的基本差異。
  • 使用 Python Socket 建立 UDP 通訊。
  • 使用 JSON 傳送結構化資料。
  • 觀察 UDP 封包的實際來源 IP。
  • 讓學生自己完成一個小型區域網路。
  • 建立「組長 Server + 5 個組員 Node」的分組合作模式。

二、分組方式

每組共有 6 位學生

角色 人數 程式 主要任務
組長 1 udp_leader.py UDP Server
組員 5 udp_node.py UDP Node

三、網路架構

組員1 Node ─┐
組員2 Node ─┤
組員3 Node ─┤
組員4 Node ─┤── UDP ──→ 組長 Server
組員5 Node ─┘             Port 5005
                              │
                              ↓
                     建立本組節點資料表

例如組長電腦的 IP 是:

192.168.1.100

其他 5 位組員可能是:

組員1:192.168.1.101
組員2:192.168.1.102
組員3:192.168.1.103
組員4:192.168.1.104
組員5:192.168.1.105

實際 IP 由教室的 DHCP 或網路設定決定, 不一定與範例相同。


四、誰是 Server?誰是 Client?

這個實驗中,老師不再擔任 Server。

組長:擔任 UDP Server,等待 5 位組員傳送資料。

組員:擔任 UDP Client / Node,將自己的資料傳送給組長。

因此一個小組本身就是一個簡單的 Client / Server 網路:

       組員 Node
           │
       組員 Node
           │
       組員 Node
           │
       組員 Node
           │
       組員 Node
           │
           ↓
       組長 Server

這也說明一個重要觀念: Server 不一定是一台大型主機。 只要一支程式正在等待並提供某種網路服務, 一般筆記型電腦也可以暫時扮演 Server。


五、TCP 與 UDP

TCP 與 UDP 都是常見的傳輸層協定。 本次實驗選擇 UDP。

比較 TCP UDP
建立連線 需要 不需要
可靠傳輸機制 本身不保證
順序控制 本身不保證
協定負擔 較多 較少

可以先把 TCP 想成「打電話」, UDP 則像「直接寄出一張明信片」。

但我們可以在應用程式中自行加入確認機制。 所以本實驗設計成:

組員
  │
  │ UDP:我的資料
  ↓
組長
  │
  │ UDP:已收到
  ↓
組員

六、每位組員要傳什麼?

每位組員傳送四項資料:

  • 學號 student_id
  • 姓名 name
  • IP Address
  • MAC Address

例如:

{
    "student_id": "411001001",
    "name": "王小明",
    "ip": "192.168.1.101",
    "mac": "34:12:98:AA:01:10"
}

這就是 JSON 格式。

整個資料處理流程為:

Python Dictionary
        ↓
       JSON
        ↓
      UTF-8
        ↓
       Bytes
        ↓
       UDP

七、組長端完整程式

組長將程式儲存為:

udp_leader.py
import socket
import json

SERVER_IP = "0.0.0.0"
SERVER_PORT = 5005

sock = socket.socket(
    socket.AF_INET,
    socket.SOCK_DGRAM
)

sock.bind(
    (SERVER_IP, SERVER_PORT)
)

students = {}

print("=" * 60)
print("UDP 小組節點資料收集系統")
print("本電腦角色:組長 Server")
print(f"等待 UDP Port {SERVER_PORT}...")
print("=" * 60)


while len(students) < 5:

    data, address = sock.recvfrom(4096)

    try:

        student = json.loads(
            data.decode("utf-8")
        )

        student_id = student["student_id"]
        name = student["name"]
        ip = student["ip"]
        mac = student["mac"]

        students[student_id] = {
            "name": name,
            "ip": ip,
            "mac": mac,
            "source_ip": address[0]
        }

        print()
        print(f"目前收到 {len(students)} / 5 位組員")
        print("-" * 50)

        print(f"學號:{student_id}")
        print(f"姓名:{name}")
        print(f"組員回報 IP:{ip}")
        print(f"實際來源 IP:{address[0]}")
        print(f"MAC:{mac}")
        print(f"來源 Port:{address[1]}")

        print("-" * 50)

        message = (
            f"組長已收到 "
            f"{student_id} {name} 的資料"
        )

        sock.sendto(
            message.encode("utf-8"),
            address
        )

    except Exception as e:

        print("資料格式錯誤:", e)


print()
print("=" * 90)
print("本組 5 位組員資料收集完成")
print("=" * 90)

print(
    f"{'學號':<15}"
    f"{'姓名':<12}"
    f"{'回報IP':<18}"
    f"{'來源IP':<18}"
    f"{'MAC'}"
)

print("-" * 90)

for student_id, info in students.items():

    print(
        f"{student_id:<15}"
        f"{info['name']:<12}"
        f"{info['ip']:<18}"
        f"{info['source_ip']:<18}"
        f"{info['mac']}"
    )

sock.close()

八、5 位組員使用的完整程式

5 位組員全部使用同一支程式:

udp_node.py
import socket
import uuid
import json

# =========================================
# 修改成「組長電腦」的 IP
# =========================================

LEADER_IP = "192.168.1.100"
SERVER_PORT = 5005


# =========================================
# 取得本機 IP
# =========================================

def get_ip():

    s = socket.socket(
        socket.AF_INET,
        socket.SOCK_DGRAM
    )

    try:

        s.connect(("8.8.8.8", 80))
        ip = s.getsockname()[0]

    except:

        ip = "127.0.0.1"

    finally:

        s.close()

    return ip


# =========================================
# 取得本機 MAC Address
# =========================================

def get_mac():

    mac = uuid.getnode()

    return ":".join(
        f"{(mac >> i) & 0xff:02X}"
        for i in range(40, -1, -8)
    )


# =========================================
# 主程式
# =========================================

print("=" * 50)
print("UDP 小組節點資料傳送程式")
print("本電腦角色:組員 Node")
print("=" * 50)

student_id = input("請輸入學號:")
name = input("請輸入姓名:")

ip = get_ip()
mac = get_mac()

student = {
    "student_id": student_id,
    "name": name,
    "ip": ip,
    "mac": mac
}

print()
print("準備傳送資料")
print("-" * 40)

print("學號:", student_id)
print("姓名:", name)
print("IP:", ip)
print("MAC:", mac)

print("-" * 40)
print("組長 IP:", LEADER_IP)


# 建立 UDP Socket

sock = socket.socket(
    socket.AF_INET,
    socket.SOCK_DGRAM
)


# Dictionary → JSON → Bytes

data = json.dumps(
    student,
    ensure_ascii=False
).encode("utf-8")


# 傳送給組長

sock.sendto(
    data,
    (LEADER_IP, SERVER_PORT)
)

print()
print("資料已透過 UDP 傳送給組長!")


# 等待組長回覆

sock.settimeout(5)

try:

    reply, address = sock.recvfrom(4096)

    print(
        "組長回覆:",
        reply.decode("utf-8")
    )

except socket.timeout:

    print("5 秒內沒有收到組長回覆")

sock.close()

九、實驗操作流程

本實驗最重要的是執行順序

① 六位學生完成分組

② 選出一位組長

③ 組長查詢自己的 IP

④ 組長將 IP 告訴 5 位組員

⑤ 組長先執行 udp_leader.py

⑥ 5 位組員設定 LEADER_IP

⑦ 組員執行 udp_node.py

⑧ 輸入學號與姓名

⑨ UDP 傳送資料

⑩ 組長完成本組節點資料表

十、組長如何查自己的 IP?

Windows 可以開啟命令提示字元:

ipconfig

找到目前使用中的網路介面,例如:

IPv4 Address . . . . . . . . . :
192.168.1.100

組長就告訴其他 5 位組員:

「我們這組的 Server IP 是 192.168.1.100。」

5 位組員修改:

LEADER_IP = "192.168.1.100"

十一、組長先啟動 Server

組長執行:

python udp_leader.py

畫面:

============================================================
UDP 小組節點資料收集系統
本電腦角色:組長 Server
等待 UDP Port 5005...
============================================================

這代表組長電腦正在等待其他組員送資料到 UDP Port 5005


十二、組員開始傳送

組員執行:

python udp_node.py

例如:

請輸入學號:411001001
請輸入姓名:王小明

準備傳送資料
----------------------------------------
學號:411001001
姓名:王小明
IP:192.168.1.101
MAC:34:12:98:AA:01:10
----------------------------------------
組長 IP:192.168.1.100

資料已透過 UDP 傳送給組長!

組長回覆:
組長已收到 411001001 王小明 的資料

十三、組長會看到什麼?

第一位組員傳送後:

目前收到 1 / 5 位組員
--------------------------------------------------

學號:411001001
姓名:王小明

組員回報 IP:
192.168.1.101

實際來源 IP:
192.168.1.101

MAC:
34:12:98:AA:01:10

來源 Port:
52341

這裡要特別觀察:

組員自己回報的 IP

組長 Server 從 UDP 封包看到的來源 IP

兩者是不是相同?

十四、recvfrom() 告訴組長什麼?

組長程式中最重要的一行之一:

data, address = sock.recvfrom(4096)

它會得到:

data
│
└── 組員傳送的 JSON 資料

address
│
├── 來源 IP
└── 來源 Port

例如:

('192.168.1.101', 52341)

因此:

address[0]

就是組長實際看到的來源 IP。


十五、MAC 為什麼不是組長直接查到的?

本實驗中的 MAC 是:

組員電腦
   ↓
uuid.getnode()
   ↓
取得本機 MAC
   ↓
放入 Python Dictionary
   ↓
轉成 JSON
   ↓
UDP
   ↓
組長 Server

因此:

MAC 是組員主動回報的應用層資料。
它不是組長使用 recvfrom() 直接從遠端 UDP 封包取得的 MAC Address。

十六、5 位組員完成後

當組長收到 5 個不同學號後:

==========================================================================================
本組 5 位組員資料收集完成
==========================================================================================

學號           姓名        回報IP             來源IP             MAC
------------------------------------------------------------------------------------------
411001001      王小明      192.168.1.101      192.168.1.101      34:12:98:AA:01:10
411001002      李小華      192.168.1.102      192.168.1.102      50:EB:F6:22:18:21
411001003      陳大文      192.168.1.103      192.168.1.103      10:62:E5:31:21:33
411001004      林小美      192.168.1.104      192.168.1.104      A4:BB:6D:11:23:44
411001005      張志明      192.168.1.105      192.168.1.105      D8:43:AE:12:35:55

到這裡,一個由學生自行建立的 「1 Server + 5 Nodes」小型網路 就完成了。


十七、從輸入姓名到組長收到資料

把整個網路資料流串起來:

組員輸入學號、姓名

Python 取得 IP、MAC

建立 Dictionary

轉成 JSON

UTF-8 編碼

Socket

UDP

IP Network

組長 IP

UDP Port 5005

recvfrom()

JSON Decode

加入本組節點資料表

組長回覆「已收到」

十八、實驗失敗時如何檢查?

  1. 組長與組員是否位於可以互通的網路環境。
  2. 組長 IP 是否查詢正確。
  3. 5 位組員的 LEADER_IP 是否設定正確。
  4. 組長是否先啟動 udp_leader.py
  5. UDP Port 5005 是否受到防火牆限制。
  6. Python 是否取得 Windows 防火牆的網路存取權限。
  7. 校園 Wi-Fi 是否啟用 Client Isolation / AP Isolation。

如果校園 Wi-Fi 基於資訊安全限制學生裝置互相連線, 可依學校網路政策使用教學實驗用 AP 建立獨立區域網路。


十九、課堂思考題

  1. 為什麼組長要先啟動程式?
  2. 組長在這個實驗中扮演 Server 還是 Client?
  3. 5 位組員為什麼都必須知道組長 IP?
  4. Port 5005 的作用是什麼?
  5. AF_INET 表示什麼?
  6. SOCK_DGRAM 與 UDP 有什麼關係?
  7. IP Address 和 MAC Address 的角色有什麼不同?
  8. 為什麼組員要把資料轉成 JSON?
  9. recvfrom() 可以取得哪些資訊?
  10. 為什麼組長可以知道封包的來源 IP,卻不能用同樣方式取得跨網段的遠端 MAC?
  11. UDP 封包如果遺失,這支程式會自動重傳嗎?
  12. 如果組長換人,程式的網路架構需要修改哪些地方?

二十、進階實驗:組長也加入節點

目前架構是:

1 位組長 Server
+
5 位組員 Node
=
6 位學生

下一階段可以進一步讓組長不只是 Server, 同時也是一個 Node:

              組長
        Server + Node
             ↑ ↓
      ┌──────┼──────┐
      ↓      ↓      ↓
   組員1   組員2   組員3
      ↑      ↑      ↑
      └── 組員4、組員5 ──┘

再進一步加入:

  • UDP Broadcast:自動尋找組長。
  • UDP Unicast:指定某位組員傳送訊息。
  • 節點上線通知。
  • 節點離線通知。
  • 簡易群組聊天室。

二十一、結語:每一組就是一個小型網路實驗室

這次實驗最大的改變,是老師不再擔任 Server

每組由學生自己選出一位組長, 組長的筆記型電腦就是 Server; 其他同學的電腦就是 Node。

6 位學生

選出 1 位組長

組長建立 UDP Server

5 位組員建立 UDP Node

Socket + IP + Port

JSON + UDP

完成本組節點資料表

透過這個實驗,同學不只是背誦 IP、MAC、Port、Socket、UDP、Client 與 Server, 而是親手讓這些技術真正運作一次。

更重要的是,每一組學生都自己建立了一個 「1 Server + 5 Nodes」的小型區域網路。 這可以作為後續學習 UDP Broadcast、Peer-to-Peer、IoT、即時通訊與網路服務 的重要基礎。

沒有留言:

張貼留言