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