2026年9月21日 星期一

綠照趣旅行-菓葉智慧走讀學習教具

綠照趣旅行

菓葉智慧走讀學習教具

小Q機器人 × 菓葉故事 × 程式闖關

如何讓長者在熟悉的家鄉地景中活動身體、分享記憶,也讓孩子透過程式操作認識社區?「綠照趣旅行」以菓葉村為學習場域,把實體拼接地圖、社區圖卡、程式教育機器人、口述故事與 Google 地圖結合,設計出一套適合成果展、社區課程及青銀共學使用的智慧走讀教具。

「綠照趣旅行」是整套系列教具的名稱,「菓葉智慧走讀」則是菓葉社區版本。未來也能依相同架構延伸至重光、山水及湖東等社區,呈現各地不同的生活、生產、生態與文化特色。

重要說明:本地圖沒有 OID 碼。學習活動以方向指令、程式積木、圖卡任務及故事錄音進行,不使用 OID 點讀功能。

一、設計理念

菓葉的觀日樓、保安林、菜宅、灰窯、關懷據點與漁港,承載著長者的生活經驗,也呈現澎湖聚落與自然環境互相依存的樣貌。本教具把這些真實地景轉化為可移動、可排列、可說故事的學習素材。

參與者先從圖卡認識地方,再對照菓葉村 Google 地圖確認景點位置,接著規劃機器人的行走路線。機器人到達指定地點後,可以播放事先錄製的景點故事、台語導覽或長者口述記憶。走讀不只發生在紙上,也與真實的社區空間連結。

二、教具包含哪些內容?

四張A4拼接地圖

以四張A4特殊紙印製並拼接,形成機器人可以移動的菓葉走讀地圖。

菓葉主題圖卡

包含觀日樓、菜宅、灰窯、黃槿花、石滬捕魚、夜釣小管、健康園區與玻璃龜DIY等地方素材。

程式教育機器人

可搭配 Tale-Bot Pro 或 Matatalab Coding Set,依現場設備與參與者能力選擇操作方式。

故事語音

利用 Tale-Bot Pro 的故事鍵錄製景點介紹、台語故事或長者的家鄉記憶。

菓葉村 Google 地圖

查找真實景點、道路與方位,讓實體教材連結到社區的實際空間。

三、兩套教具如何搭配?

教具主要操作方式適合的學習活動
Tale-Bot Pro
小Q機器人
直接按下前進、後退、左轉及右轉等方向鍵,輸入行走路線;使用故事鍵錄製與播放語音。長者操作、親子共學、台語導覽、社區故事分享及簡易路徑挑戰。
Matatalab Coding Set利用程式積木、控制板及指令塔排列程序,再讓機器人依指令行走。順序概念、程式邏輯、路徑規劃、分組討論與任務解題。

四、教學目標

  • 認識家鄉:從圖卡與地圖認識菓葉的重要景點、產業、飲食與文化記憶。
  • 促進健康:透過取卡、排卡、操作機器人與路線討論,增加手眼協調、空間判斷及參與動機。
  • 保存記憶:邀請長者以台語錄下地方故事,讓生活經驗成為可分享的教材。
  • 培養邏輯:把目的地轉換成一連串方向指令,練習順序、方位、推理與修正。
  • 青銀共學:由長者提供故事,青年協助錄音與數位地圖,孩子負責操作或闖關,各世代共同完成走讀任務。

五、建議操作方式

  1. 拼接地圖:將四張A4特殊紙依正確位置拼成完整地圖,確認紙張平整,讓機器人能順利移動。
  2. 認識圖卡:請參與者挑選一張菓葉主題圖卡,說說是否去過這個地方,或分享與它有關的記憶。
  3. 查看真實位置:打開菓葉村 Google 地圖,找出景點所在位置、周邊道路及彼此的方向關係。
  4. 安排任務路線:把圖卡放入指定格位,選擇起點與終點,討論機器人需要前進幾格、在哪裡轉彎。
  5. 輸入程式:使用 Tale-Bot Pro 的機身按鍵,或利用 Matatalab Coding Set 的程式積木排列指令。
  6. 執行與修正:觀察機器人是否抵達目標。若路線錯誤,找出問題並重新調整指令。
  7. 播放家鄉故事:機器人抵達圖卡後,播放事先以 Tale-Bot Pro 故事鍵錄製的景點介紹或台語故事。
  8. 完成分享:參與者說出今天走過的路線、最喜歡的景點,以及新認識的一段菓葉故事。

六、故事鍵語音怎麼運用?

帶領者可先邀請長者選擇一張熟悉的圖卡,用自然的台語說出約20至30秒的回憶,再利用 Tale-Bot Pro 的故事鍵完成錄音。例如:

「這是菓葉的菜宅。以前海風很大,大家用石牆保護作物。菜宅裡種的蔬菜和地瓜,是許多家庭熟悉的生活記憶。」

機器人抵達菜宅圖卡後,由參與者按下故事鍵播放錄音。若活動時間較長,也可以讓不同長者分別錄製觀日樓、灰窯、漁港或黃槿花的故事,逐步建立菓葉社區的聲音資料庫。

七、三種任務範例

任務一:迎日健康行

從關懷據點出發前往觀日樓。抵達後播放迎日故事,並由帶領者帶大家完成簡單伸展活動。

任務二:菜宅到健康餐桌

依序走訪菜宅與漁港,選出在地蔬菜及漁產圖卡,討論如何搭配成較均衡的健康餐點。

任務三:文化記憶巡禮

規劃灰窯、黃槿花與玻璃龜DIY的走讀路線。每到一站便播放一段故事,完成後請參與者分享最想保存的地方記憶。

八、對應綠色照顧四面向

綠場域綠飲食綠療育綠陪伴

面向在教具中的實踐
綠場域透過實體地圖與 Google 地圖認識菓葉的自然環境、聚落及公共空間。
綠飲食從菜宅、漁港與地方料理圖卡認識在地食材、惜食與健康餐桌。
綠療育結合地圖操作、程式闖關、手部活動及家鄉故事,增加參與感與成就感。
綠陪伴透過長者、青年與孩子分工合作,創造對話與互相協助的機會。

九、從成果展示走向社區共學

「綠照趣旅行」保留了紙本教具容易觀看與操作的優點,也加入程式教育、故事錄音及數位地圖。長者可以成為說故事的人,孩子可以成為機器人的小小導航員,青年則能協助錄音、查找地圖與整理內容。

這套教具可用於成果展的互動體驗,也能延伸為社區走讀、樂齡課程、親子活動及青銀共學教材。每增加一張圖卡或一段長者錄音,菓葉的地方記憶就多了一種被保存與分享的方式。

菓葉小Q・三圈綠照智慧走讀

菓葉小Q・三圈綠照智慧走讀

結合Tale-Bot Pro、OID點讀、台語故事與地中海飲食的青銀童共學教材

菓葉小Q・三圈綠照智慧走讀海報
菓葉小Q・三圈綠照智慧走讀|115年臺南澎湖農村綠色照顧四面向行動方案

當長者的家鄉記憶遇上可程式控制的小Q機器人,一張地圖就能同時成為文化走讀、健康飲食、台語傳承與運算思維的學習場域。「菓葉小Q・三圈綠照智慧走讀」以6×6 TaleMap為核心,讓參與者用按鍵規劃路徑;小Q抵達目的地、讀取OID碼後,便播放對應的台語故事、地方介紹或褒歌。

計畫名稱:115年臺南澎湖農村綠色照顧四面向行動方案
教材名稱:菓葉小Q・三圈綠照智慧走讀
互動載具:Tale-Bot Pro(簡稱「小Q機器人」)+6×6 TaleMap+OID語音圖卡

一、設計理念

這套教材不是把科技單向帶進社區,而是讓科技成為保存地方記憶、促進世代交流的媒介。菓葉村的日出、觀日樓、玄武岩石牆、菜宅、保安林、石滬、漁港、灰窯工藝與在地料理,都轉化成可探索的圖卡;長者熟悉的台語故事與褒歌,則成為小Q抵達圖卡後播放的聲音內容。

參與者必須先觀察地圖、決定目的地、思考方向,再輸入移動指令。這個過程把抽象的程式順序概念,轉化為看得見、摸得到、聽得懂的操作活動。長者能分享生活經驗,兒童能協助按鍵及路徑規劃,青年則擔任陪伴者與數位記錄者,形成「長者說故事、兒童玩科技、青年做陪伴」的青銀童共學模式。

三圈式學習架構

圈層圖卡位置學習內容互動聲音
核心圈15、16、21、22綠色場域、綠色飲食、綠色療育、綠色陪伴及體驗活動規劃計畫介紹、活動任務、青銀共學故事
健康飲食圈8、9、10、11、14、17、20、23、26、27、28、29我的餐盤、地中海飲食、低鈉、肌少症、IDDSI及在地健康料理飲食說明、料理故事、營養闖關提示
社區特色圈最外圍20格澎湖農村再生、菓葉地景、農漁文化、工藝與社區創生台語導覽、耆老記憶、台語褒歌

二、教學目標

目標面向具體學習表現
地方文化認識認識菓葉村的自然地景、農漁生活、傳統工藝及農村再生成果。
台語與記憶傳承透過聆聽、跟讀、吟唱與分享,保存長者的台語語彙、故事及褒歌。
健康飲食素養辨識蔬菜海藻、全穀雜糧、海鮮蛋白、堅果好油,理解低鈉、肌少症防護及質地調整。
運算思維學習方向、順序、分解任務、預測結果與錯誤修正等基礎程式概念。
認知與動作促進藉由觀察、數格、按鍵、追視與口語回應,促進手眼協調、空間辨識及認知刺激。
世代共學培養輪流、合作、傾聽與表達,讓長者、兒童及青年共同完成走讀任務。

三、操作方式

  1. 選擇任務。參與者抽取任務卡,或從三圈地圖中選擇想探索的景點、料理、健康知識或文化工藝。
  2. 尋找目的地。在6×6 TaleMap上確認目標圖卡的位置,觀察小Q目前所在的格子與面向。
  3. 規劃路徑。先用手指在地圖上模擬移動,計算需要前進、後退、左轉或右轉的次數。
  4. 輸入指令。依照規劃順序,操作小Q機器人上方的9個按鈕輸入行動指令。
  5. 按下Play。小Q開始依照指令在TaleMap上移動;參與者一起觀察是否朝正確方向前進。
  6. 讀取OID。小Q抵達目的地後,由底部感測器讀取圖卡上的OID碼,自動播放對應的台語故事、導覽或褒歌。
  7. 回應與分享。參與者回答問題、跟讀台語、吟唱褒歌,或請長者補充自己的生活記憶。
  8. 修正再挑戰。若沒有抵達目標,大家共同找出路徑或方向錯誤,重新輸入指令再試一次。

四、建議活動模式

1. 菓葉社區走讀

小Q依序走訪菓葉日出、觀日樓、菜宅、保安林、石滬、漁港、灰窯與言午時光。每到一站便播放台語導覽,引導長者補充故事、兒童提出問題。

2. 地中海健康餐盤闖關

參與者為小Q規劃路線,蒐集「二分之一蔬菜海藻、四分之一全穀雜糧、四分之一海鮮蛋白、適量堅果好油」等圖卡,最後完成一份適合長者的澎湖健康餐盤。

3. 台語故事與褒歌尋聲

小Q抵達指定圖卡後播放長者錄製的台語故事或褒歌,大家先聆聽,再跟著朗讀或吟唱。也可邀請長者重新說一次,讓青年以文字、錄音或影像保存。

五、帶領與評量建議

帶領者不必急著替參與者修正答案,可以用「小Q現在面向佗一爿?」「閣愛行幾格?」「這張圖予你想著啥物?」等簡短問題,引導大家共同思考。對長者可減少路徑長度並放大方向提示;對兒童則可增加指定必經點、限制指令數或分組競賽。

活動評量可觀察五項表現:能否辨識目的地、能否說出方向、能否依序輸入指令、能否理解語音內容,以及能否與同伴分享地方記憶。評量重點不是速度,而是參與、溝通、記憶喚起與合作歷程。

按鍵規劃路徑,小Q走讀菓葉;聽見台語故事,找回家鄉記憶。

成果展應用:現場可由一位工作人員負責起點設定與安全提示,讓長者、兒童及青年三人一組,分別擔任「故事家、導航員、操作員」,完成任務後再交換角色。

Python容器實作:我的出國自由行規劃

Python容器實作:我的出國自由行規劃

Python初學list, tuple, dict, set

課程挑戰:如果你準備和同學出國自由行,如何使用Python整理景點、日期、航班、住宿、預算及大家共同想去的地方?本篇文章暫時不使用 ifforwhile,專心練習容器的建立、讀取、修改與選擇。

一、為什麼要學容器?

程式不只處理一個數字或一句文字,經常需要同時管理很多資料。例如,旅遊規劃中可能有五個景點、多項費用、一筆航班資料,以及不同組員的願望清單。Python提供不同容器,協助我們依照資料特性進行整理。

旅遊資料需求適合容器選擇原因
有先後順序的景點list 串列可以新增、刪除、修改及調整順序
出發日期、座標tuple 元組用來表達一組不希望任意更動的資料
航班、飯店、景點資料dict 字典可以利用欄位名稱取得對應內容
組員共同想去的景點set 集合排除重複,並可進行交集、聯集及差集

二、實作1:為旅程取一個名稱

字串 str 是由文字依序組成的容器。先輸入想去的國家、城市及旅遊天數,再組合成行程名稱。

country = input("請輸入旅遊國家:")
city = input("請輸入旅遊城市:")
days = input("請輸入旅遊天數:")

tour_name = f"{country}{city}{days}日自由行"

print(f"我的旅遊計畫:{tour_name}")
print(f"城市名稱共有 {len(city)} 個字")
動手做:輸入自己真正想去的國家與城市,不必和老師的範例相同。

三、實作2:建立想去的景點清單

串列適合保存「有順序,而且可能調整」的資料。以下用東京景點示範。

places = ["淺草寺", "東京晴空塔", "上野公園", "澀谷", "東京鐵塔"]

print(f"完整景點:{places}")
print(f"第一個景點:{places[0]}")
print(f"最後一個景點:{places[-1]}")
print(f"前三個景點:{places[:3]}")
print(f"景點數量:{len(places)}")
執行前先預測:
  1. places[1] 的結果是什麼?
  2. places[-2] 的結果是什麼?
  3. places[1:4] 會包含哪些景點?

四、實作3:調整旅遊景點

自由行最常遇到行程異動。利用串列方法練習新增、插入、修改及刪除景點。

places = ["淺草寺", "東京晴空塔", "上野公園", "澀谷"]

# 新增在最後面
places.append("東京鐵塔")
print(f"新增後:{places}")

# 插入索引1的位置
places.insert(1, "秋葉原")
print(f"插入後:{places}")

# 修改原本位於索引3的景點
places[3] = "上野動物園"
print(f"修改後:{places}")

# 刪除指定景點
places.remove("東京鐵塔")
print(f"刪除後:{places}")
學生任務:建立自己的五個景點,完成新增、插入、修改及刪除各一次,最後顯示第一站、最後一站及景點總數。

五、實作4:安排每天的行程

尚未學習迴圈時,可先用不同變數保存每天的行程。

day1 = ["成田機場", "飯店", "淺草寺"]
day2 = ["上野公園", "秋葉原", "東京車站"]
day3 = ["明治神宮", "原宿", "澀谷"]

print(f"第一天:{day1}")
print(f"第二天:{day2}")
print(f"第三天:{day3}")

# 調整第二天行程
day2.append("東京晴空塔")
day2.remove("秋葉原")
print(f"調整後的第二天:{day2}")

# 交換第一天的第二站與第三站
day1[1], day1[2] = day1[2], day1[1]
print(f"交換後的第一天:{day1}")

六、實作5:用元組保存固定資料

元組適合表達一組相對固定、不希望在程式中被任意改動的資料,例如日期或座標。

1. 出發日期

departure_date = (2027, 1, 20)
year, month, day = departure_date

print(f"出發年份:{year}")
print(f"出發月份:{month}")
print(f"出發日期:{day}")
print(f"完整日期:{year}/{month}/{day}")

2. 景點座標

sensoji_location = (35.7148, 139.7967)
latitude, longitude = sensoji_location

print(f"淺草寺緯度:{latitude}")
print(f"淺草寺經度:{longitude}")
錯誤也是教材:試著執行 sensoji_location[0] = 35.7000,觀察錯誤訊息。元組建立後不能直接修改;如果資料需要頻繁調整,串列通常更合適。

七、實作6:建立景點電子資料卡

字典以「鍵與值」保存資料,適合描述一個景點的多項屬性。

place = {
    "名稱": "淺草寺",
    "城市": "東京",
    "類型": "文化景點",
    "門票": 0,
    "停留時間": "2小時"
}

print(f"景點名稱:{place['名稱']}")
print(f"所在城市:{place['城市']}")
print(f"景點類型:{place['類型']}")
print(f"門票:{place['門票']} 元")

# 修改既有資料
place["停留時間"] = "3小時"

# 增加新資料
place["交通方式"] = "搭乘地鐵"
place["推薦美食"] = "人形燒"

print(f"建議停留:{place['停留時間']}")
print(f"交通方式:{place['交通方式']}")
print(f"推薦美食:{place['推薦美食']}")
學生任務:選擇一個想去的景點,建立包含「名稱、國家、城市、類型、門票、停留時間、交通方式」的字典。

八、實作7:整理航班與住宿資料

flight = {
    "航空公司": "範例航空",
    "航班編號": "AB123",
    "出發機場": "桃園國際機場",
    "抵達機場": "成田國際機場",
    "出發時間": "08:30",
    "抵達時間": "12:30"
}

hotel = {
    "名稱": "東京城市飯店",
    "住宿天數": 4,
    "每晚費用": 1800,
    "鄰近車站": "上野站"
}

print(f"航班:{flight['航班編號']}")
print(f"飛行路線:{flight['出發機場']} → {flight['抵達機場']}")
print(f"住宿飯店:{hotel['名稱']}")
print(f"住宿費用:{hotel['住宿天數'] * hotel['每晚費用']} 元")
以上航班、時間及價格均為程式練習用的假設資料,不是訂票資訊。實際旅行前應查閱航空公司、旅館及官方旅遊資訊。

九、實作8:比較組員想去的景點

集合沒有固定順序,但可以排除重複項目,並快速比較兩人的旅遊偏好。

student_a = {"淺草寺", "東京晴空塔", "秋葉原", "澀谷"}
student_b = {"淺草寺", "東京鐵塔", "澀谷", "迪士尼樂園"}

print(f"共同想去:{student_a & student_b}")
print(f"全部推薦:{student_a | student_b}")
print(f"A同學特別想去:{student_a - student_b}")
print(f"B同學特別想去:{student_b - student_a}")
運算寫法旅遊規劃意義
交集A & B兩人共同想去的景點
聯集A | B兩人提出的全部景點
差集A - BA提出但B沒有提出的景點

十、實作9:估算旅遊預算

只有金額時可以使用串列;需要知道每筆金額的意義時,字典更加清楚。

budget = {
    "機票": 12000,
    "住宿": 7200,
    "餐費": 5000,
    "交通": 3000,
    "門票": 3500
}

total = sum(budget.values())

print(f"機票:{budget['機票']} 元")
print(f"住宿:{budget['住宿']} 元")
print(f"餐費:{budget['餐費']} 元")
print(f"交通:{budget['交通']} 元")
print(f"門票:{budget['門票']} 元")
print(f"預估總旅費:{total} 元")
print(f"最高單項費用:{max(budget.values())} 元")
print(f"最低單項費用:{min(budget.values())} 元")

十一、綜合實作:我的出國自由行企畫

分組挑戰:3至4人一組,選擇一個想去的國家與城市,完成可執行的Python旅遊企畫。
容器任務要求
str輸入國家、城市、天數,產生行程名稱
list建立至少五個有順序的景點,並完成新增、插入、修改及刪除
tuple建立出發日期及一個景點的經緯度
dict建立景點資料卡、航班或住宿資料,以及旅費預算
set比較兩位組員的景點清單,找出交集、聯集及差集

綜合程式範例

country = "日本"
city = "大阪"
days = 5
tour_name = f"{country}{city}{days}日自由行"

places = ["大阪城", "道頓堀", "心齋橋", "通天閣", "環球影城"]
departure_date = (2027, 1, 20)

hotel = {
    "名稱": "大阪城市飯店",
    "住宿天數": 4,
    "每晚費用": 1800,
    "鄰近車站": "難波站"
}

budget = {
    "機票": 12000,
    "住宿": 7200,
    "餐費": 5000,
    "交通": 3000,
    "門票": 3500
}

my_choices = {"大阪城", "道頓堀", "環球影城"}
partner_choices = {"大阪城", "海遊館", "環球影城"}

print(f"行程名稱:{tour_name}")
print(f"旅遊景點:{places}")
print(f"出發日期:{departure_date}")
print(f"住宿資料:{hotel}")
print(f"預估總旅費:{sum(budget.values())} 元")
print(f"共同想去:{my_choices & partner_choices}")
print(f"全部推薦:{my_choices | partner_choices}")

十二、三小時課程安排

時間課程活動進行方式
0~20分鐘旅遊資料分類與容器選擇全班討論
20~55分鐘景點串列、索引、切片與修改個人實作
55~75分鐘日期、座標與元組錯誤觀察兩人合作
75~85分鐘休息
85~120分鐘景點、航班、住宿與預算字典個人實作
120~140分鐘比較組員的景點集合兩人合作
140~170分鐘完成出國自由行企畫分組實作
170~180分鐘成果展示與概念檢核口頭發表

十三、個人作業與分組作業

作業類型內容繳交成果
個人建立五個景點,練習索引、切片及串列修改程式與執行畫面
個人建立一張景點字典資料卡程式與輸出結果
兩人比較兩人的景點集合交集、聯集與差集結果
分組整合景點、日期、住宿、預算及共同偏好自由行企畫程式與3分鐘報告

十四、評量方式

評量項目比例
正確建立並操作不同容器30%
能依資料需求選擇適合容器25%
程式可以執行且輸出清楚20%
能說明每種容器的選擇理由15%
分組合作與成果發表10%

十五、課後檢核

  1. 需要保存有順序且可以修改的景點,應使用哪種容器?
  2. 需要保存不希望任意更動的日期或座標,應使用哪種容器?
  3. 需要用「航空公司」「航班編號」等欄位查找資料,應使用哪種容器?
  4. 需要找出兩位同學共同想去的景點,應使用哪種容器與運算?
  5. 為什麼集合不適合直接保存旅遊參觀順序?
本課重點:
有順序、可修改的資料使用 list;一組不希望任意改動的資料使用 tuple;有欄位名稱的資料使用 dict;排除重複及比較共同項目使用 set。真正重要的不是背下語法,而是看到資料需求時,能選擇適合的容器。

看見網路的旅行:ping、tracert、ipconfig與nslookup實作

CH03 網際網路原理|電腦教室實作

看見網路的旅行:ping、tracert、ipconfig與nslookup實作

從自己的電腦出發,找出網路身分、測試連線、追蹤封包路徑,並觀察網域名稱如何轉換成IP位址。

我們每天打開瀏覽器、輸入網址,就能看到遠端網站,但網頁並不是直接「出現在」電腦裡。資料必須先取得網路設定、找到出口、經過多部路由器,最後才抵達網站伺服器。本篇將使用Windows內建的四項工具,帶領大一與專一學生實際觀察這個過程。

本篇學習目標
  1. 使用 ipconfig 查詢本機IP、子網路遮罩、閘道器與DNS。
  2. 使用 ping 測試目的主機是否可達及觀察延遲。
  3. 使用 tracert 追蹤封包經過的路由節點。
  4. 使用 nslookup 查詢網域名稱、IP與MX郵件記錄。
  5. 從本機IP與對外IP的差異判斷是否可能經過NAT。

一、開始之前:開啟命令提示字元

  1. 按下鍵盤上的 Windows鍵
  2. 輸入「命令提示字元」或 cmd
  3. 開啟命令提示字元,不需要使用系統管理員身分。

畫面會出現類似下列提示符號:

C:\Users\student>

二、ipconfig:找出電腦的網路身分

ipconfig 是Windows的網路設定查詢工具。它能顯示電腦目前取得的IPv4位址、子網路遮罩與預設閘道器。

ipconfig

若要查看更完整的資訊,可以輸入:

ipconfig /all
欄位作用生活化比喻
IPv4位址辨識網路中的電腦門牌號碼
子網路遮罩判斷哪些裝置位於同一網段行政區範圍
預設閘道前往其他網路的出口社區大門
DNS伺服器將網域名稱查成IP位址網際網路電話簿
實體位址網路介面的MAC位址網路卡識別碼
DHCP自動提供IP等網路設定自動發門牌

認識私有IPv4位址

若本機IPv4位址位於下列範圍,表示它是供內部網路使用的私有IP:

  • 10.0.0.0~10.255.255.255
  • 172.16.0.0~172.31.255.255
  • 192.168.0.0~192.168.255.255

注意:192.192.x.x不是私有IP;不能只看到「192」開頭就判定為私有位址。

三、ping:測試是否能與目的主機通訊

ping會向目的主機送出測試封包,並觀察是否收到回應。它常用來檢查基本連線與往返延遲。

ping www.google.com

執行結果通常包含:

  • 位元組:測試封包的資料量。
  • 時間:封包送出並收到回應所需的往返時間,單位是毫秒(ms)。
  • TTL:封包還可經過多少個路由節點,用來避免封包無限循環。
  • 已傳送、已收到、已遺失:用來計算封包遺失率。

也可以先測試預設閘道,再測試外部網站:

ping 你的預設閘道IP
ping www.nfu.edu.tw
ping www.google.com
收到「要求等候逾時」不一定代表網站故障。
部分伺服器或防火牆會阻擋ping使用的ICMP封包,但網站的HTTP或HTTPS服務仍可能正常開啟。

四、tracert:追蹤資料封包的旅行路線

tracert會顯示封包到達目的主機前經過的路由節點。在Windows使用 tracert;Linux與macOS通常使用 traceroute

tracert www.google.com

可能看到如下結果:

1     7 ms     7 ms     6 ms    10.200.200.200
2    16 ms    16 ms    15 ms    router.example.edu.tw [120.xxx.xxx.xxx]
3    22 ms    24 ms    25 ms    140.xxx.xxx.xxx
...
10   27 ms    27 ms    27 ms    142.xxx.xxx.xxx
畫面內容意義
最左邊的數字第幾個躍點(Hop)
三個ms數值三次測試的往返時間
主機名稱路由節點的反向DNS名稱;不一定每站都有
IP位址該路由節點回應時所使用的位址
*該次測試在期限內未收到回應

各站時間不一定一路增加。網路負載、回程路線與路由器回應測試封包的優先順序都會造成差異,因此不能把每一站的毫秒數相加。

人在家中,為什麼路徑可能經過學校?

如果電腦正在使用學校VPN,網路封包可能先建立加密通道回到學校,再由校園出口前往Google。另一種情況是:使用者正在遠端控制學校電腦,實際執行指令的是校內電腦。因此,「人所在的位置」和「網路封包的出口」不一定相同。

五、nslookup:查詢DNS網域名稱

人比較容易記住 www.google.com,電腦傳輸時則需要IP位址。DNS負責將網域名稱轉換成IP位址,而 nslookup可以觀察這個查詢結果。

nslookup www.google.com

查詢結果通常會包含:

  • 目前使用的DNS伺服器。
  • DNS伺服器的IP位址。
  • 查詢網域所對應的一個或多個IPv4/IPv6位址。

查詢MX郵件伺服器

MX(Mail Exchanger)記錄指出某個網域應該由哪一部郵件伺服器接收電子郵件。

nslookup
> set type=MX
> nfu.edu.tw

也可以直接使用單行指令:

nslookup -type=MX nfu.edu.tw

若一個網域有多筆MX記錄,通常優先值越小,使用順序越前面;主要伺服器無法服務時,寄件端才會嘗試其他郵件伺服器。

六、電腦教室會有NAT嗎?

多數電腦教室可能使用NAT,但NAT設備通常位於校園核心路由器或防火牆,而不是放在每一間教室。NAT可以讓多部使用私有IP的電腦共用一個或少量公有IPv4位址連上網際網路。

教室電腦(私有IP) → 教室交換器 → 校園核心網路 → 防火牆/NAT → 公有IP → 網際網路

判斷方式:

  1. 使用 ipconfig 查詢本機IPv4位址。
  2. 用瀏覽器搜尋「我的IP」,查詢網站看到的對外IP。
  3. 如果本機是私有IP,而網站看到不同的公有IP,表示中間很可能經過NAT。
NAT不等於防火牆:NAT負責轉換IP位址;防火牆負責允許或阻擋網路流量。實際網路設備經常同時具備兩種功能。

七、四項工具的使用順序

步驟工具要回答的問題
1ipconfig我的電腦取得哪些網路設定?
2ping我能否與目的主機進行基本通訊?
3tracert封包經過哪些路由節點?
4nslookup網域名稱解析成哪些IP或郵件伺服器?

八、個人實作任務

請每位學生選擇一個合法且公開的網站,完成下列操作:

ipconfig /all
ping 網域名稱
tracert 網域名稱
nslookup 網域名稱
nslookup -type=MX 網域名稱

將結果整理成下表:

觀察項目紀錄結果
本機IPv4位址
預設閘道
DNS伺服器
目的網站與IP
ping平均延遲與遺失率
tracert躍點數
MX郵件伺服器
是否可能使用NAT

九、思考與討論

  1. 網域名稱與IP位址有什麼不同?為什麼需要DNS?
  2. ping失敗時,為什麼網站仍可能正常開啟?
  3. tracert中的三個時間為什麼不完全相同?
  4. 為什麼後面的躍點有時比前面的躍點回應更快?
  5. 同一間電腦教室的多部電腦,對外IP為什麼可能相同?
  6. 人在家中上網時,什麼情況會讓封包先經過學校網路?
  7. MX記錄和一般網站的IP查詢有什麼不同?

十、資安與隱私提醒

  • 只測試教師指定、本人管理或公開合法的網站。
  • 不要掃描整個校園網段,也不要嘗試未授權的連接。
  • 繳交報告時,MAC位址可遮蔽後三組,例如 A4-B1-C2-XX-XX-XX
  • IP、主機名稱與路由可能隨時間改變,報告應註明測試日期與網路環境。
小結

ipconfig讓我們知道「我是誰、出口在哪裡」;ping回答「能不能到達」;tracert呈現「經過哪些路」;nslookup則說明「網域名稱如何找到伺服器」。把四項工具串在一起,就能從看似抽象的網際網路原理,建立一套可以實際操作與驗證的網路診斷流程。

2026年9月19日 星期六

從菓葉菜宅到健康餐桌:用 iVoice 與 FFmpeg 製作台語故事音檔

從菓葉菜宅到健康餐桌:用 iVoice 與 FFmpeg 製作台語故事音檔

讓菓葉人的菜宅、漁獲與惜物精神,透過台語聲音走進地中海健康餐盤



菓葉迎日・海味地中海餐盤:一半蔬菜海藻、四分之一全穀雜糧、四分之一海鮮蛋白。

這次的實作,是為「115年臺南澎湖農村綠色照顧四面向行動方案」製作一段具有地方感的台語故事音檔。主題從澎湖菓葉社區的菜宅出發,經過漁港與惜食記憶,最後連結到適合長輩的地中海健康餐盤。

語音合成採用意傳科技 iVoice 台語語音合成 API,沒有使用 Runway。原因很單純:這次需要的是能處理臺灣台語與台羅拼音的專用語音引擎,而不是一般影音生成服務。實作結果也再次說明,地方故事的自然度不只取決於聲音模型,更取決於文稿中的用詞、語尾、停頓與生活細節。

完成規格
  • 語音引擎:iVoice 台語語音合成 API
  • 聲音:男聲
  • 腔口:強勢腔(偏高雄腔)
  • 內容:菓葉菜宅、漁獲、惜物精神與地中海餐盤
  • 後製:去除頭尾靜音、調整音量與語速
  • 長度:約 4 分 46 秒
  • 格式:MP3、44.1 kHz、單聲道、192 kbps

一、先寫出「菓葉人的話」,再交給語音引擎

若只把一般中文文章直接送進語音合成,聲音即使發音正確,也容易像在朗讀新聞,缺少社區長輩的生活感。因此,第一步不是調整模型參數,而是先把文稿改成適合「說」的台語故事。

文稿設計掌握三個原則:

  1. 用地方生活做主角:以玄武岩石牆、菜宅、菓葉漁港、魚、小管及拇指管形成故事場景。
  2. 用惜物精神串起情節:一瓢水要省著用、菜葉有蟲孔仍可食、魚肉做菜、魚骨熬湯,呈現菓葉人的辛勤與節省。
  3. 把健康知識轉成生活語言:不直接堆疊營養名詞,而是從「以前菜宅有什麼就煮什麼,漁船捕到什麼就吃什麼」帶出地中海飲食。
土地生出來的物件,攏是辛苦換來的,毋通浪費。

若希望更接近菓葉阿婆的海口語氣,可以適度加入「阮、海跤、袂使糟蹋、毋通討債」等詞彙,並把長句拆短。這種調整通常比只在提示詞寫上「澎湖口音」更有效。

二、iVoice 台語語音合成流程

本次採用兩階段處理。先將台文轉為帶數字聲調的台羅拼音,再將台羅拼音交給台語語音合成端點輸出 WAV。

台文轉台羅/html_taigi_tw_py?text0=台文內容
台語語音合成/synthesize_TLPA?text1=台羅拼音&gender=男聲&accent=強勢腔(高雄腔)
服務端點tts001.ivoice.tw:8804
原始輸出WAV、22.05 kHz、16-bit、單聲道
使用提醒:API 參數、可用腔口與服務政策可能調整,正式應用前應再次確認服務介面與授權規範。包含中文、括號與台羅拼音的查詢參數,必須進行 URL 編碼。

為什麼要分段合成?

四至五分鐘的故事若一次送入,可能遇到逾時、截斷或重做成本過高的問題。因此,我將故事依情節切成九段,逐段轉換台羅、逐段合成 WAV,確認每段有效後再串接。這樣即使其中一段失敗,也只需重新處理該段。

台語故事稿
  ↓ 依情節切成短段落
台文轉台羅拼音
  ↓
iVoice 逐段合成 WAV
  ↓
去除每段頭尾靜音
  ↓
串接、音量正規化、語速調整
  ↓
輸出 MP3

三、用 FFmpeg 整理停頓、音量與語速

語音引擎輸出的 WAV 可以直接播放,但若要用在成果展、點讀設備或故事機,仍需整理頭尾空白、段落音量與播放速度。

1. 只移除頭尾靜音,不破壞句中停頓

第一次測試時,我把靜音移除條件設得過度積極,結果句子中原本自然的停頓也被判定為切點,將接近五分鐘的音檔錯誤縮短成約十一秒。修正方式是:先修剪開頭,再將音訊反轉、修剪反轉後的開頭,最後再反轉回來。如此便能只處理原始音訊的頭尾。

ffmpeg -i input.wav \
  -af "silenceremove=start_periods=1:start_duration=0.08:start_threshold=-45dB,\
areverse,\
silenceremove=start_periods=1:start_duration=0.08:start_threshold=-45dB,\
areverse,apad=pad_dur=0.32" \
  output_clean.wav

apad=pad_dur=0.32 會在段落尾端保留約 0.32 秒空白,使九段串接後仍有呼吸感,不會像連珠炮。

2. 串接、音量正規化與 MP3 輸出

ffmpeg -f concat -safe 0 -i concat.txt \
  -af "loudnorm=I=-18:LRA=7:TP=-1.5,atempo=1.04" \
  -ar 44100 -ac 1 -b:a 192k \
  guoye_taigi_story.mp3
  • loudnorm:統一各段的響度,避免忽大忽小。
  • atempo=1.04:在不明顯改變聲音質感的前提下,略微調整速度。
  • -ar 44100:輸出取樣率設定為 44.1 kHz。
  • -ac 1:旁白採單聲道,適合展示與點讀應用。
  • -b:a 192k:MP3 位元率設定為 192 kbps。

四、一只餐盤,裝著菓葉人的日子

從菓葉菜宅、漁港到地中海健康餐桌

我是在菓葉長大的阿婆。

細漢的時陣,天才微微光,厝裡的大人就已經起床。阿母帶我走進玄武岩石牆圍起來的菜宅,看看番薯、高麗菜、絲瓜和番茄。菓葉的海風強,土地又乾,種菜並不簡單。石牆替菜苗擋風,每一瓢水都要用得有打算;菜葉若有蟲孔,只要還能食,就不會隨便丟掉。

大人常講:「土地生出來的物件,攏是辛苦換來的,毋通浪費。」

太陽升起後,漁船慢慢駛回菓葉漁港。魚、小管和拇指管一籃一籃搬上岸,漁網上還留著鹹鹹的海水味。賣相好的送去市場,較小尾或外形不完整的,就帶回家煮湯、汆燙、曝乾。魚肉做菜,魚骨熬湯,能利用的都留下來。那個年代大家生活不富裕,卻很懂得珍惜土地與海洋給我們的每一份食物。

以前我們不知道什麼叫「地中海飲食」,只知道菜宅有什麼就煮什麼,漁船捕到什麼就吃什麼;多吃菜、海菜、番薯和魚,少放一點鹽,也很少大魚大肉。

後來,社區的青年和老師回到菓葉,和長輩一起把老一輩的飲食智慧重新整理。他們將餐盤分成三個部分:一半放菜宅蔬菜和澎湖海菜;四分之一放番薯、玉米和糙米;另外四分之一放當季鮮魚、小管或拇指管,再加一點花生和苦茶油。

我們看了都笑著說:「這不就是菓葉人以前吃的飯菜嗎?」

不一樣的是,現在我們把傳統經驗變成清楚的健康餐盤。海菜先洗去鹽分,蔬菜煮得柔軟,小管切成長輩容易入口的小塊;利用檸檬、番茄和蒜頭增加香氣,減少鹽和加工醬料。老味道沒有消失,反而用新的方法變得更健康、更適合長輩。

這一盤「菓葉迎日・海味地中海餐盤」,裝的不只是蔬菜、番薯和漁獲。它裝著菓葉人迎著海風耕作的辛勤、珍惜每一份食物的節省,也裝著年輕人與長輩共同創新的心意。

從菜宅走到漁港,再從漁港走上健康餐桌,菓葉人把過去的生活智慧,變成照顧現在、也能傳給未來的一頓好飯。

五、這次實作帶來的三個體會

  1. 地方感先寫進文稿,不能只靠聲音模型。iVoice 的強勢腔偏高雄腔,並非真正的澎湖真人口音;菓葉的海口感主要來自生活詞彙、語尾與敘事節奏。
  2. 長文要分段,停頓要刻意保留。技術上的「去靜音」不等於把所有安靜處刪除。對長輩故事而言,呼吸與停頓也是內容的一部分。
  3. AI 的價值是協助地方知識轉譯。這份音檔不是要取代社區長輩,而是先建立可展示、可點讀、可再修改的素材,讓長輩、青年與教師能一起確認內容。

結語:讓科技替地方記憶多留一條路

從菓葉菜宅、漁獲到地中海餐盤,表面上是一段飲食故事,背後其實連結了地方農漁業、長輩記憶、健康促進、台語傳承與青銀共創。透過台語語音合成與簡單的音訊後製,我們可以把文字變成能在成果展播放、能放入故事機,也能搭配 Tale-Map 或 OID 點讀圖卡使用的聲音素材。

科技不一定要把地方變成另一個樣子。更有意義的做法,是讓科技聽懂地方、說出地方,也讓菓葉人的生活智慧被更多人聽見。

標籤:台語語音合成、iVoice、FFmpeg、澎湖菓葉、農村綠色照顧、地中海飲食、地方創生、AI教育、青銀共創

2026年9月16日 星期三

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

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

在「網際網路應用」課程中,我們會學到 IP Address、MAC Address、Port、Socket、TCP、Client、Server 等重要觀念。

如果只背誦這些名詞,很難真正理解一筆資料究竟如何從一台電腦傳送到另一台電腦。 因此這次我們直接使用 Python, 讓學生自己建立一個小型的 TCP Client / Server 網路

每組由一位學生擔任組長, 組長的電腦擔任 TCP Server; 另外 5 位學生擔任組員, 組員的電腦擔任 TCP Client

每位組員輸入自己的學號與姓名後, Python 自動取得本機的 IP 與 MAC Address, 將資料轉換成 JSON, 再透過 TCP 傳送到組長電腦。

本次實驗的核心問題:
如何讓 5 位組員先與組長建立 TCP 連線, 再將自己的學號、姓名、IP 與 MAC 傳送給組長?

一、學習目標

  • 理解 TCP Client / Server 架構。
  • 了解 IP Address、MAC Address 與 Port。
  • 理解 Socket 在網路程式中的角色。
  • 理解 TCP 是 Connection-oriented Protocol。
  • 了解 TCP Server 的 bind、listen、accept。
  • 了解 TCP Client 的 connect。
  • 使用 sendall() 與 recv() 傳送資料。
  • 使用 JSON 交換結構化資料。
  • 完成「1 位組長 + 5 位組員」TCP 網路實驗。

二、分組方式

每組共有 6 位學生

角色 人數 程式 網路角色
組長 1 tcp_leader.py TCP Server
組員 5 tcp_node.py TCP Client

三、網路架構

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

例如組長 IP:

192.168.1.100

組員可能分別為:

組員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 由教室的網路環境決定。


四、TCP 最大的特色:先建立連線

TCP 的全名是:

Transmission Control Protocol

TCP 是連線導向(Connection-oriented)的傳輸層協定。

因此組員不能直接把資料丟給組長,而是先:

找到組長 IP

找到 Port 5005

建立 TCP Connection

傳送 JSON 資料

組長接收

組長回覆

關閉 Connection

五、TCP 與 UDP 有什麼不同?

比較 TCP UDP
連線 需要建立連線 不需建立連線
可靠傳輸 提供可靠、有序的位元組流 本身不保證可靠送達
資料形式 Byte Stream Datagram
常見 Socket SOCK_STREAM SOCK_DGRAM

初學時可以先這樣理解:

TCP:比較像先打電話接通,再開始說話。

UDP:比較像直接寄出一張明信片。

六、TCP Socket

建立 TCP Socket:

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

其中:

  • AF_INET:使用 IPv4。
  • SOCK_STREAM:建立 TCP Socket。

這裡就是與上一個 UDP 實驗非常重要的差異:

UDP
socket.SOCK_DGRAM

TCP
socket.SOCK_STREAM

七、TCP Server 的四個重要步驟

組長端有四個非常重要的 Socket 操作:

bind()

listen()

accept()

recv()

1. bind()

server.bind(("0.0.0.0", 5005))

讓 Server 使用本機的 TCP Port 5005。

2. listen()

server.listen(5)

讓 Socket 進入監聽狀態,準備接受連線。 參數代表等待連線佇列的 backlog 設定,並不是「最多只能服務 5 位學生」。

3. accept()

client_socket, address = server.accept()

等待一位組員建立 TCP 連線。

4. recv()

data = client_socket.recv(4096)

接收該組員送來的資料。


八、組長端 TCP Server 完整程式

組長儲存為:

tcp_leader.py
import socket
import json

SERVER_IP = "0.0.0.0"
SERVER_PORT = 5005

# 建立 TCP Socket
server = socket.socket(
    socket.AF_INET,
    socket.SOCK_STREAM
)

# 允許程式重新啟動後較快重新使用 Port
server.setsockopt(
    socket.SOL_SOCKET,
    socket.SO_REUSEADDR,
    1
)

# 綁定 IP 與 Port
server.bind(
    (SERVER_IP, SERVER_PORT)
)

# 開始監聽
server.listen(5)

students = {}

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


while len(students) < 5:

    print("\n等待組員連線...")

    # 接受 TCP Connection
    client_socket, address = server.accept()

    print(
        f"收到 TCP 連線:"
        f"{address[0]}:{address[1]}"
    )

    try:

        # 接收資料
        data = client_socket.recv(4096)

        if not data:
            client_socket.close()
            continue

        # JSON → Python Dictionary
        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"目前收到 "
            f"{len(students)} / 5 位組員"
        )

        print("-" * 50)

        print(f"學號:{student_id}")
        print(f"姓名:{name}")

        print(
            f"組員回報 IP:{ip}"
        )

        print(
            f"TCP 實際來源 IP:"
            f"{address[0]}"
        )

        print(f"MAC:{mac}")

        print(
            f"來源 Port:"
            f"{address[1]}"
        )

        print("-" * 50)

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

        client_socket.sendall(
            message.encode("utf-8")
        )

    except Exception as e:

        print(
            "處理資料時發生錯誤:",
            e
        )

    finally:

        # 關閉這一位組員的 Connection
        client_socket.close()


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']}"
    )

server.close()

九、組員端 TCP Client 完整程式

5 位組員都使用相同程式:

tcp_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:

        # 此處不是真的傳資料給 8.8.8.8
        # 主要用來判斷系統會選用哪個本機 IP
        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("TCP 小組節點資料傳送程式")
print("本電腦角色:組員 TCP Client")
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(
    "組長 Server:",
    LEADER_IP
)


# =========================================
# 建立 TCP Socket
# =========================================

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


try:

    # =====================================
    # 建立 TCP Connection
    # =====================================

    print("\n正在連線到組長...")

    sock.connect(
        (LEADER_IP, SERVER_PORT)
    )

    print("TCP 連線成功!")


    # =====================================
    # Dictionary → JSON → Bytes
    # =====================================

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


    # =====================================
    # 傳送資料
    # =====================================

    sock.sendall(data)

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


    # =====================================
    # 等待組長回覆
    # =====================================

    reply = sock.recv(4096)

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


except ConnectionRefusedError:

    print(
        "\n連線失敗!"
        "請確認組長程式是否已啟動。"
    )


except Exception as e:

    print(
        "\n發生錯誤:",
        e
    )


finally:

    sock.close()

    print(
        "\nTCP Connection 已關閉"
    )

十、注意:get_ip() 裡為什麼還看到 SOCK_DGRAM?

有同學可能會發現: 明明這一課改成 TCP,為什麼 get_ip() 裡面還有:

socket.SOCK_DGRAM

這裡的 UDP Socket 不是本次學生資料傳輸使用的通訊協定。 它只是利用作業系統的路由選擇,協助程式判斷目前會使用哪個本機 IP。

真正將學生資料傳給組長的 Socket 是:

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

所以本實驗的主要資料傳輸仍然是 TCP


十一、組員最重要的一行:connect()

UDP 版本沒有真正的 TCP Connection, 但 TCP Client 必須先:

sock.connect(
    (LEADER_IP, SERVER_PORT)
)

可以把它理解為:

組員知道:

組長 IP
192.168.1.100

+

TCP Port
5005

        ↓

192.168.1.100:5005

        ↓

建立 TCP Connection

十二、TCP 三向交握

在組員的 connect() 與組長的 accept() 背後,TCP 會進行建立連線的程序。 最經典的就是 Three-Way Handshake(三向交握)

組員 Client                         組長 Server
     │                                  │
     │ ----------- SYN --------------→ │
     │                                  │
     │ ←-------- SYN + ACK ------------ │
     │                                  │
     │ ----------- ACK --------------→ │
     │                                  │
     │      TCP Connection 建立         │
     │                                  │

完成連線後,應用程式才開始交換學生資料。

注意: Python 程式不需要自己寫 SYN、ACK。 這些工作由作業系統中的 TCP/IP 協定堆疊處理。 我們使用 Socket API 操作即可。

十三、sendall() 做什麼?

組員使用:

sock.sendall(data)

把 JSON 編碼後的 Bytes 寫入 TCP 連線。

組長則使用:

data = client_socket.recv(4096)

從 TCP 位元組流讀取資料。

本實驗的 JSON 很短,因此用單次 recv(4096) 即可作為入門示範。 但要特別知道: TCP 是 Byte Stream,不保留應用程式訊息邊界。 正式系統若要傳送較長或連續多筆資料,通常要自行設計長度欄位、分隔符號或其他 framing 機制。


十四、完整操作流程

① 6 位學生分組

② 選出 1 位組長

③ 組長查詢自己的 IP

④ 告訴其他 5 位組員

⑤ 組長啟動 tcp_leader.py

⑥ Server bind()

⑦ Server listen()

⑧ 組員執行 tcp_node.py

⑨ Client connect()

⑩ Server accept()

⑪ 建立 TCP Connection

⑫ 組員 sendall()

⑬ 組長 recv()

⑭ 組長回覆

⑮ 關閉該次 Connection

十五、實際執行

Step 1:組長先啟動

python tcp_leader.py

畫面:

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

等待組員連線...

Step 2:組員設定組長 IP

LEADER_IP = "192.168.1.100"

Step 3:組員執行

python tcp_node.py

例如:

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

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

正在連線到組長...
TCP 連線成功!

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

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

TCP Connection 已關閉

十六、組長看到的資料

收到 TCP 連線:
192.168.1.101:52341

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

學號:411001001
姓名:王小明

組員回報 IP:
192.168.1.101

TCP 實際來源 IP:
192.168.1.101

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

來源 Port:
52341

這裡同樣可以比較:

JSON 中組員自己回報的 IP

TCP Server 從 accept() 得到的實際來源 IP

十七、MAC 為什麼仍然需要組員自己回報?

雖然我們從 UDP 改成 TCP, 但這個觀念沒有改變。

組員
 │
 ├─ 學號
 ├─ 姓名
 ├─ IP
 └─ MAC
      │
      ↓
   Dictionary
      ↓
     JSON
      ↓
     TCP
      ↓
   組長 Server

Server 從 TCP 連線可以知道對端 IP 與來源 Port, 但 JSON 中的 MAC 是由組員程式自行取得後主動回報的應用資料。


十八、五位組員完成後

==========================================================================================
本組 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

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

輸入學號、姓名

取得 IP、MAC

Python Dictionary

JSON

UTF-8 Bytes

TCP Socket

connect()

TCP Connection

sendall()

IP Network

組長 Port 5005

accept()

recv()

JSON Decode

建立節點資料表

組長回覆

close()

二十、如果出現 Connection refused

例如:

ConnectionRefusedError

通常代表 Client 已經找到目標主機, 但指定的 TCP Port 沒有接受連線,或連線受到系統/網路政策阻擋。

首先檢查:

  1. 組長是否先執行 tcp_leader.py
  2. 組員設定的 LEADER_IP 是否正確。
  3. Server 與 Client 是否都使用 Port 5005。
  4. Windows 防火牆是否允許 Python 網路通訊。
  5. 目前網路是否允許組員電腦與組長電腦互相連線。

二十一、UDP 與 TCP 程式的關鍵差異

學過上一個 UDP 實驗後,可以直接比較:

功能 UDP TCP
Socket SOCK_DGRAM SOCK_STREAM
Server 等待 recvfrom() listen() + accept()
Client 連線 不需 TCP 連線 connect()
傳送 sendto() sendall()
接收 recvfrom() recv()

二十二、課堂思考題

  1. 為什麼 TCP Server 要先執行?
  2. SOCK_STREAM 代表什麼?
  3. bind() 的作用是什麼?
  4. listen() 的作用是什麼?
  5. accept() 在等待什麼?
  6. connect() 做了什麼?
  7. TCP 三向交握中的 SYN、SYN-ACK、ACK 是什麼?
  8. sendall() 和 UDP 的 sendto() 有什麼不同?
  9. 為什麼 TCP Server 可以知道 Client 的來源 IP?
  10. 為什麼 MAC 還是由組員自己放進 JSON?
  11. 為什麼 TCP 不能假設一次 recv() 永遠等於一次 sendall()
  12. 組長收到 5 位組員後,為什麼程式就結束?如果要一直提供服務,應該修改哪個部分?

二十三、從 TCP 實驗看懂網際網路應用

這次看起來只是把:

「學號 + 姓名 + IP + MAC」

從 5 台組員電腦傳到組長電腦, 背後卻已經串起一整套網路概念:

Client / Server

IP Address + Port

Socket

TCP

connect()

Three-Way Handshake

accept()

JSON

sendall() / recv()

組長建立節點資料表

透過這個實驗,同學不只是知道「TCP 是可靠的、連線導向的傳輸層協定」, 而是真正讓兩台電腦建立 TCP Connection, 再把自己建立的資料送過網路。

因此,學習網際網路最重要的不只是記住協定名稱, 而是理解: 應用程式的資料,如何透過 Socket、TCP、IP 與實際網路, 從一台電腦到達另一台電腦。

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、即時通訊與網路服務 的重要基礎。

Python UDP 五節點互傳實作:讓 5 台電腦自動交換 IP、MAC、學號與姓名

Python UDP 五節點互傳實作:讓 5 台電腦自動交換 IP、MAC、學號與姓名

在學習「網際網路應用」時,我們常會接觸到 IP、MAC Address、Port、TCP 與 UDP, 但如果只是背誦名詞,很難真正理解這些技術如何運作。

這次我們直接使用 Python 建立一個 「UDP 五節點學生資料互傳系統」。 5 位同學各自使用一台電腦,每台電腦都是一個 Node(節點), 不需要另外架設中央 Server。

每位同學啟動程式後輸入自己的學號與姓名, Python 自動取得本機的 IP Address 與 MAC Address, 再利用 UDP Broadcast 將自己的資料傳送給其他同學。


一、這個實驗要做什麼?

假設教室中有 5 位同學:

節點 學號 姓名 IP
Node 1 411001001 王小明 192.168.1.101
Node 2 411001002 李小華 192.168.1.102
Node 3 411001003 陳大文 192.168.1.103
Node 4 411001004 林小美 192.168.1.104
Node 5 411001005 張志明 192.168.1.105

每一台電腦都同時具有兩個角色:

  • UDP Sender:傳送自己的資料。
  • UDP Receiver:接收其他同學的資料。
                 UDP Broadcast
                       ↕
        ┌──────────────┼──────────────┐
        │              │              │
     Node 1 ←────→ Node 2 ←────→ Node 3
        ↕              ↕              ↕
     Node 4 ←──────────────────────→ Node 5

每台電腦:
UDP Sender + UDP Receiver
    

二、為什麼選 UDP?

UDP 是 User Datagram Protocol(使用者資料包協定)。 和 TCP 不同,UDP 在傳送資料之前不需要先建立連線。

可以先用一個簡單概念理解:

TCP:比較像打電話,先建立連線,再開始交談。
UDP:比較像直接寄出一張明信片,不先確認對方是否準備好。

UDP 具有簡單、快速、負擔較低的特色, 常見於即時通訊、影音串流、DNS、IoT 裝置通訊及區域網路設備探索等應用。


三、什麼是 UDP Broadcast?

如果我們已經知道某位同學的 IP,可以直接將 UDP 封包傳給他的 IP, 這種方式稱為 Unicast(單播)

但這次實驗的目的,是希望 5 台電腦可以互相發現彼此, 所以使用 Broadcast(廣播)

255.255.255.255
  

程式將資料送往廣播位址後,在網路環境允許廣播的情況下, 同一區域網路中監聽指定 UDP Port 的其他節點就有機會收到這份資料。


四、我們要交換哪些資料?

每位同學要交換四項基本資料:

  1. 學號 student_id
  2. 姓名 name
  3. IP Address
  4. MAC Address

例如王小明的資料可以表示成:

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

這種資料格式就是 JSON。 JSON 非常適合用來交換結構化資料, 在 Web API、IoT、雲端服務與 App 開發中都很常見。


五、Python 完整程式

5 位同學都使用完全相同的程式, 不需要為 Node 1、Node 2 分別修改程式。

import socket
import threading
import json
import uuid
import time

PORT = 5005

# 儲存目前收到的節點資料
nodes = {}


# ==========================================
# 取得本機 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)
    )


# ==========================================
# 顯示目前節點
# ==========================================
def show_nodes():

    print("\n" + "=" * 70)
    print("目前找到的 UDP 節點")
    print("=" * 70)

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

    print("-" * 70)

    for student_id, info in nodes.items():

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

    print("-" * 70)
    print(f"目前共有 {len(nodes)} 個節點")


# ==========================================
# UDP Receiver
# ==========================================
def receiver():

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

    sock.setsockopt(
        socket.SOL_SOCKET,
        socket.SO_REUSEADDR,
        1
    )

    sock.bind(("", PORT))

    print(f"\nUDP Receiver 已啟動,Port = {PORT}")

    while True:

        data, address = sock.recvfrom(4096)

        try:

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

            student_id = student["student_id"]

            nodes[student_id] = student

            print(
                f"\n收到 {student['name']} "
                f"({student_id}) 的資料"
            )

            print(
                f"UDP 封包來源 IP:{address[0]}"
            )

            show_nodes()

        except Exception as e:

            print("接收資料錯誤:", e)


# ==========================================
# UDP Broadcast
# ==========================================
def broadcast(student):

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

    sock.setsockopt(
        socket.SOL_SOCKET,
        socket.SO_BROADCAST,
        1
    )

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

    sock.sendto(
        data,
        ("255.255.255.255", PORT)
    )

    sock.close()


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

print("=" * 60)
print("      UDP 五節點學生資料互傳系統")
print("=" * 60)

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

ip = get_ip()
mac = get_mac()

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

# 將自己的資料放入節點表
nodes[student_id] = my_data

print("\n我的節點資料")
print("-" * 40)
print("學號:", student_id)
print("姓名:", name)
print("IP:", ip)
print("MAC:", mac)


# ==========================================
# 啟動 Receiver Thread
# ==========================================

thread = threading.Thread(
    target=receiver,
    daemon=True
)

thread.start()

time.sleep(1)


# ==========================================
# 操作選單
# ==========================================

while True:

    print("\n")
    print("========= UDP 操作選單 =========")
    print("1. 廣播我的資料")
    print("2. 查看目前5個節點")
    print("3. 離開")
    print("===============================")

    choice = input("請選擇:")

    if choice == "1":

        broadcast(my_data)

        print("\n已將我的資料廣播給其他同學!")

    elif choice == "2":

        show_nodes()

    elif choice == "3":

        print("程式結束")
        break

    else:

        print("請輸入 1、2 或 3")

六、實驗操作步驟

步驟 1:確認 5 台電腦連接同一個區域網路

建議課堂實驗時,5 台電腦連接同一台 Wi-Fi AP 或網路分享器。

步驟 2:5 台電腦都執行程式

python udp_5nodes.py
  

步驟 3:輸入自己的學號與姓名

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

我的節點資料
----------------------------------------
學號:411001001
姓名:王小明
IP:192.168.1.101
MAC:34:12:98:AA:01:10
  

步驟 4:選擇「1. 廣播我的資料」

========= UDP 操作選單 =========
1. 廣播我的資料
2. 查看目前5個節點
3. 離開
===============================

請選擇:1

已將我的資料廣播給其他同學!
  

五位同學依序廣播自己的資料後, 每一台電腦就可以逐漸建立自己的節點資料表。


七、觀察實驗結果

當 5 個節點都成功交換資料後,可以看到:

======================================================================
目前找到的 UDP 節點
======================================================================

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

目前共有 5 個節點
  

此時並不是只有某一台 Server 擁有資料, 而是每一個 Node 都可以接收其他節點廣播的資訊。


八、程式中的五個重要觀念

1. IP Address

IP Address 用來識別 IP 網路中的通訊端點。 在本實驗中,每一台學生電腦都有自己的區域網路 IP。

2. MAC Address

MAC Address 是網路介面的鏈路層識別資訊。 本程式由各節點自行取得 MAC,再把它當作 JSON 資料的一部分透過 UDP 傳送。

因此要特別注意: MAC 並不是 UDP 自動幫我們從遠端電腦取得的。

3. Port

這次所有節點共同使用:

UDP Port 5005
  

IP 可以理解為找到哪一台主機,而 Port 則協助作業系統將資料交給對應的網路應用程式。

4. JSON

Python Dictionary:

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

透過:

json.dumps()
  

將資料序列化,再編碼成位元組後透過 UDP 傳送。

5. Thread

每一台電腦既要等待接收資料,又要讓使用者操作選單。 如果只有單一執行流程,程式可能卡在:

sock.recvfrom(4096)
  

因此使用另一個 Thread 負責 UDP Receiver:

thread = threading.Thread(
    target=receiver,
    daemon=True
)

thread.start()
  

這樣主程式可以處理操作選單, Receiver Thread 則持續等待其他同學送來 UDP 封包。


九、封包到底怎麼走?

例如 Node 1 的王小明按下「廣播我的資料」, Python 先建立資料:

王小明
   ↓
Python Dictionary
   ↓
JSON
   ↓
UTF-8 Bytes
   ↓
UDP
   ↓
IP Network
   ↓
Broadcast
   ↓
Node 2 / Node 3 / Node 4 / Node 5
   ↓
JSON Decode
   ↓
加入 nodes 資料表
  

這一連串流程,就是應用程式資料真正進入網路並在不同主機間交換的一個簡化實例。


十、為什麼有時候 5 台電腦收不到彼此?

如果程式沒有錯,但同學彼此收不到資料,可以優先檢查:

  1. 5 台電腦是否位於可互通的同一區域網路。
  2. Windows 防火牆是否允許 Python 的網路通訊。
  3. UDP Port 5005 是否被防火牆或其他安全機制阻擋。
  4. Wi-Fi AP 是否開啟 Client Isolation / AP Isolation。
  5. 校園無線網路是否禁止裝置之間直接互相通訊。
實驗提醒:
校園 Wi-Fi 為了資訊安全,有時會限制用戶端彼此通訊或廣播。 因此課堂進行這類區域網路實驗時,可以準備獨立的教學用 Wi-Fi AP/分享器, 並依學校網路政策操作。

十一、思考題

  1. UDP 與 TCP 最大的差異是什麼?
  2. 為什麼這個實驗適合使用 Broadcast?
  3. IP Address 和 MAC Address 的角色有什麼不同?
  4. 為什麼所有同學都使用 Port 5005?
  5. 為什麼程式需要使用 Thread?
  6. 如果同一位同學廣播 10 次,為什麼 nodes 不一定會增加成 10 筆?
  7. UDP 如果發生封包遺失,這支程式會知道嗎?
  8. 如果要指定只傳給 Node 3,應該繼續使用 Broadcast,還是改成 Unicast?

十二、進階挑戰:從 Broadcast 走向 Unicast

第一階段,我們利用 Broadcast 完成:

自動交換身分資料 → 找到其他節點 → 建立 5 人節點表

下一階段就可以利用已經取得的 IP Address, 改成指定對象傳送訊息。

Node 1 王小明
      │
      │ UDP Unicast
      ↓
Node 3 陳大文

訊息:
Hello Node 3!
  

如此一來,就能形成更完整的學習路徑:

5 個 Node

UDP Broadcast

自動發現其他節點

建立 IP / MAC / 學號 / 姓名資料表

UDP Unicast

指定同學互傳訊息

十三、結語

這個實驗看似只是讓 5 位同學互傳「學號、姓名、IP、MAC」, 實際上已經把許多網際網路應用的重要概念串在一起:

IP → MAC → Port → Socket → UDP → Broadcast → JSON → Thread → Peer Node

學習網路程式設計最好的方式之一,就是讓不同的實體電腦真正交換資料。 當 Node 1 按下傳送,而 Node 2、Node 3、Node 4、Node 5 的螢幕真的出現 Node 1 的資料時, 原本抽象的 UDP、IP、Port 與封包概念,就會變成可以觀察、測試與修改的程式實驗。

下一個實驗,我們可以在這個五節點架構上加入 「選擇同學 → UDP Unicast → 一對一傳送文字訊息」, 進一步製作一個簡易的區域網路聊天室。

從家鄉食物記憶到地中海健康料理

智慧生活科技專業社群|AI社區共創教材

從家鄉食物記憶到地中海健康料理

用台語說記憶,與AI共同設計保留家鄉味的健康新料理

一道料理不只是食材與步驟,也包含家人、節慶、土地與生活記憶。當我們帶著AI走入社區,不應一開始就要求長輩計算幾公克、幾毫升,而是先聽他們說出最懷念的味道,再運用AI協助查找資料、換算份量,最後共同轉化成符合地中海飲食精神的新料理。

教學核心:
先保存「原味記憶」,再進行「健康轉化」。不是否定長輩原來的料理,而是讓地方飲食智慧與現代健康觀念一起被看見。

一、為什麼從食物記憶開始?

如果直接問長輩「請設計一道地中海料理」,「地中海飲食」可能成為陌生的專業名詞;如果先問「你上懷念的是啥物料理?」長輩就能從熟悉的人、地方、節慶與味道開始說起。

因此,本活動將AI定位為整理與轉化的助手:長輩提供料理的靈魂,AI負責一般資料查找與份量換算,最後仍由長輩決定哪些特色必須保留、哪些健康調整可以接受。

二、教學流程:「4問+2次確認」

  1. 記憶第1問:最懷念哪一道料理?
  2. 記憶第2問:誰煮的?什麼時候、在哪裡吃?
  3. 記憶第3問:一定要有哪些食材?最懷念什麼味道或情景?
  4. 第一次確認:整理並確認「原味記憶食譜」。
  5. 健康第4問:從加菜、換好油、增加魚豆或使用全穀中,選擇適合的調整。
  6. 第二次確認:確認「地中海風味版家鄉料理」。
  7. 成果製作:生成一人份食譜與A4圖卡。

說出記憶 → 確認原味 → 選擇健康調整 → 確認新料理 → 生成食譜圖卡

三、把地中海飲食說得簡單一點

活動中不必向長輩講授太多營養名詞。可以把健康轉化濃縮成四個容易選擇的方向:

方向台語簡單說法料理調整
多蔬菜加一寡菜增加當季、在地蔬菜
好油脂換較好的油適量使用橄欖油或其他植物油
優質蛋白加魚、豆仔優先選擇魚類、豆類,適量禽肉
全穀雜糧換一寡全穀以全穀替代部分精製白米或白麵

重點不是每一道菜都加入橄欖油,而是掌握「多蔬果、全穀、豆類、堅果與魚類,使用適量好油,減少過多鹽、糖、飽和脂肪及高度加工食品」的精神,並優先採用社區原有的在地食材。

四、長輩、AI與教學人員如何分工?

角色主要任務
長輩提供料理名稱、人物、地方、時節、主要食材、特殊做法與記憶,選擇能接受的健康調整。
AI整理口述、查找相近食譜、換算一人份建議用量、提出2至3個合適方案及製作圖卡。
教學人員協助語音操作、控制訪談時間、說明健康概念,確保長輩擁有最後決定權。

五、第一階段提示詞:台語訪談與地中海健康轉化

第一階段只進行訪談、資料查找、健康轉化與文字確認,不要生成圖片。完成後應保留在同一個ChatGPT對話中,再上傳長輩照片並執行第二階段提示詞。

我是參加「115年臺南澎湖農村綠色照顧四面向行動方案」的社區長輩,想用台語說出記憶中的家鄉料理,再和AI一起把它轉化成一道保留家鄉味的地中海健康料理。 請擔任親切的「家鄉料理記憶訪問者」與「健康料理小助手」。全程使用簡單、自然、容易聽懂的台語,講慢一點,一次只問一個問題,等我回答後才繼續。 【第一階段:找回食物記憶】 最多詢問3題: 第1題:「你上懷念的是啥物料理?」 第2題:「這道料理是啥物人煮的?通常佇啥物所在、啥物時陣食?」 第3題:「這道料理一定愛有啥物食材?你上懷念的是啥物滋味抑是情景?」 不要逐項追問食材重量、調味料用量、鍋具及烹調時間。只有料理名稱、主要食材或特色做法完全聽不清楚時,才可簡短追問一次。 回答完成後,整理成:料理名稱、家鄉記憶、主要食材、傳統特色或特殊做法,以及最懷念的味道與情景。 請用簡單台語讀出結果,詢問:「這是你記持內底原來的料理,按呢整理敢有著?」等待確認後,才進入第二階段。 【第二階段:轉化為地中海健康料理】 先依照長輩確認的料理內容,查找可信且相近的公開食譜,換算合理的一人份建議用量。 分析這道料理可從哪些方向調整: 1. 增加當季、在地蔬菜。 2. 增加豆類、魚類或適量禽肉。 3. 將部分精製澱粉換成全穀雜糧。 4. 使用適量橄欖油或其他植物油。 5. 減少過多的鹽、糖、油及加工食品。 只能提出2至3個真正適合這道料理的方向,不要一次介紹太多健康知識,也不要為了符合地中海飲食而硬加不合適的食材。 接著只問一題:「若欲保留原來的家鄉味,閣予伊較健康,你想欲加一寡菜、換較好的油、增加魚豆仔,抑是用一寡全穀?」 如果我不清楚如何選擇,請用簡單台語介紹2個適合的方案讓我選,不要連續追問。 選擇完成後,整理成「地中海風味版家鄉料理」: 1. 新料理名稱。 2. 原料理的家鄉記憶。 3. 一人份食材與精確用量。 4. 4至5個簡單料理步驟。 5. 保留下來的傳統特色。 6. 健康調整的內容。 7. 一句簡單的健康說明。 不可刪除長輩認為重要的主要食材、人物、地點與料理記憶。不可把網路食譜說成長輩親口提供的資料。若網路資料與長輩說法不同,以長輩說法為優先。 最後用簡單台語讀出完整內容,只問一次:「這道料理有留你的家鄉味,嘛有加一寡健康的做法。按呢改,你敢合意?」 長輩回答「好」、「可以」、「正確」或相同意思後,停止提問,輸出最後確認版文字食譜。 重要:第一階段只完成訪談、健康轉化、食譜整理及長輩確認,不要生成圖片,也不要自行進入圖卡製作。請等待使用者另外上傳長輩照片並貼上第二階段提示詞。

六、第二階段提示詞:生成A4地中海食譜圖卡

確認文字食譜後,在同一個對話上傳一張已取得本人同意的長輩上半身照片,再複製以下提示詞。照片不必拿盤子,AI可生成長輩雙手捧著完成料理的自然姿勢。

請使用第一階段最後經過長輩確認的「地中海風味版家鄉料理」,直接生成一張A4直式繁體中文食譜圖卡。不要重新訪問,也不要再次改寫食材、份量、人物、地點、故事或料理方法。 【圖卡內容與版面】 1. 最上方清楚呈現「地中海風味版家鄉料理」的新料理名稱。 2. 畫面上方或適當位置設置「我的食物記憶」小區塊,放入原料理名稱、人物、地方、時節,以及一句經長輩確認的懷念話語。 3. 左側列出「1人份食材」,每項食材都要有清楚的小圖片及精確數量;只能使用第一階段最後確認的食材與份量。 4. 右側安排受訪長輩雙手捧著完成的地中海風味料理。 5. 下方呈現4至5個料理步驟,每一步都要有獨立操作圖片、步驟編號及簡短繁體中文說明。 6. 加入一個簡短的「地中海健康調整」區塊,只呈現第一階段經長輩同意的調整,例如增加在地蔬菜、魚豆類、全穀或適量好油。 7. 呈現臺南或澎湖社區的海島、漁村、農村及在地飲食特色;只使用訪談已確認的場域,不可自行增加其他地點。 8. 最底部完整加入:「115年臺南澎湖農村綠色照顧四面向行動方案」及「菊島海風地中海:澎湖四社區綠色照顧 × 食農教育實踐計畫」。 【人物照片規則】 1. 已提供並同意使用受訪長輩照片,請參考照片中的年齡感、臉部特徵、髮型、自然神情與服裝特色。 2. 原照片即使沒有拿盤子,也請自然延伸成半身或腰部構圖,生成長輩面帶親切微笑、雙手穩定捧著完成料理的姿勢。 3. 手掌、手指、餐盤與料理的位置必須自然合理。 4. 不可改變人物性別,不可過度年輕化、美化或卡通化,也不可增加其他人物。 5. 若本次沒有提供或未同意使用本人照片,不得推測受訪者外貌,改用不具特定身分的家鄉長輩示意人物。 【文字與視覺規則】 1. 所有文字必須使用清楚、正確的繁體中文,不得出現簡體字、英文、亂碼或無意義文字。 2. 字體要大、對比清楚,版面圖多字少,適合長輩閱讀。 3. 採用溫暖、自然、健康的食譜海報風格,料理必須是畫面焦點。 4. 不得增加第一階段未經長輩確認的主要食材、人物、地點、故事與料理方法。 5. 原味記憶與健康新版要分區呈現,不可把AI提出的新作法寫成長輩過去的生活記憶。 請直接生成圖卡,不要只提供文字說明、版面建議或生圖提示詞。

七、教學現場注意事項

  • 健康轉化要由長輩選擇,不能由AI直接替長輩決定。
  • 原味記憶與健康新版分開呈現,避免歷史記憶被改寫。
  • AI查找的份量須標示為建議值,不可假裝是長輩親口提供。
  • 每次只給2至3個合適方案,避免資訊太多造成選擇壓力。
  • 食材應優先考慮當地取得、季節適合及長輩咀嚼能力。
  • 若涉及糖尿病、腎臟病、吞嚥困難或特殊飲食限制,應由營養師或醫療專業人員個別評估。

八、結語

這套方法不是請AI取代長輩的料理智慧,而是讓AI成為記憶整理、營養轉譯與視覺創作的助手。長輩先說出自己的故事,再親自選擇健康調整,最後確認食譜與圖卡。如此完成的作品,既保留家鄉味,也能成為社區食農教育、綠色照顧與跨世代學習的教材。

長輩的地方飲食智慧 × 地中海健康觀念 × AI共同創作
115年臺南澎湖農村綠色照顧四面向行動方案