# Lab10: 網路管理、Socket ## 1. 計算機網路發展史 - 1960s - 美國國防部ARPANET專案問世,奠定了網路的基礎。 - 1980s - 國際標準化組織(ISO)釋出[OSI模型](https://zh.wikipedia.org/zh-tw/OSI%E6%A8%A1%E5%9E%8B),奠定了網路技術標準化的基礎。 ![osimodel](https://hackmd.io/_uploads/HJgWC1LWyl.png) - 1990s - 英國人[Timothy John Berners-Lee](https://zh.wikipedia.org/zh-tw/%E8%92%82%E5%A7%86%C2%B7%E4%BC%AF%E7%BA%B3%E6%96%AF-%E6%9D%8E)發明了圖形化的瀏覽器,瀏覽器的簡單易用性使得計算機網路迅速被普及。 在沒有瀏覽器的年代,上網是這樣的。 ![before-browser](https://hackmd.io/_uploads/ryqGR1I-yg.jpg) 有了瀏覽器以後,上網是這樣的。 ![after-browser](https://hackmd.io/_uploads/HkWXAJUWJx.png) ## 2.TCP/IP 通訊協定 ![image](https://hackmd.io/_uploads/B14h12uXye.png) 1. **應用層( Application Layer )** HTTP(HTTPS) / SMTP / FTP … 整合OSI的會議層(Session Layer)、展示層(Presentation Layer)以及應用層(Application Layer)。 2. **傳輸層( Transport Layer )** (如何傳送資料) TCP / UDP 對應於 OSI 的傳輸層(Transport Layer)。 3. **網路層( Internet Layer )** (加上收發地址) IP (Internet Protocol)。 4. **鏈結層( Link Layer )** (實際傳送資料) 路由器 / 海底電纜 整合OSI的資料連結層(Data Link Layer)、實體層(Physical Layer)。 ## TCP( Transmission Control Protocol ) TCP是傳輸層( Transport Layer )的一種協定,可靠的傳輸是它的特色。 而TCP是利用三次握手(Three-way handshake)來確保雙方的接收功能、發送功能是好的。 進而保證訊息能傳到對方手上。 <div style='text-align:center;'> <img src='https://miro.medium.com/v2/resize:fit:720/format:webp/0*nj7oJaWNUZzDSKD0.gif'></img> </div> ## UDP ( User Datagram Protocol ) TCP傳輸可靠性高,確保一定送的到 但每次傳輸都必須經過三次握手對於某些服務效率實在次太低了 所以誕生了UDP,UDP就是拿掉三次握手,直接發送訊息 但代價就是訊息不保證能送達 所以像是現場直播、視訊需要低延遲的應用就會使用UDP。 ## IP IP 旨在將網路封包傳送到預期的目標電腦。 Port則導向電腦上的某個服務。 常見的預設連接埠( port ): **80:** http **443:** https(安全版的http) **22:** ssh IP 位址有兩種形式:IPv4 和 IPv6。 IPv4 位址的格式為 X.X.X.X,其中每個 X 都是 0-255 範圍內的值。 由於擔心使用可用 IPv4 位址集區耗盡,因此建立了 IPv6 通訊協定。 ## 3.Socket ![圖片2](https://hackmd.io/_uploads/SJVuWh_7Jg.png) - 伺服器端 ```=python from socket import socket, SOCK_STREAM, AF_INET # 1. 創建Socket物件並指定使用哪種IP格式、傳輸協議 # family=AF_INET - IPv4位址 # family=AF_INET6 - IPv6位址 # type=SOCK_STREAM - TCP套接字 # type=SOCK_DGRAM - UDP套接字 server = socket(family=AF_INET, type=SOCK_STREAM) # 2. 綁定IP位址和port(port用於區分不同的服務) server.bind(('127.0.0.1', 5678)) # 3. 開啟監聽 - 監聽客戶端連接到伺服器 # 參數來限制最多能有幾個客戶端排隊連接伺服器 server.listen(5) print('伺服器啟動,開始監聽...') while True: # 4. 通過迴圈接收客戶端的連接並作出相應的處理(提供服務) # accept方法是一個阻塞方法,如果沒有客戶端連接到伺服器,程式碼不會繼續向下執行 # accept方法返回一個元組,其中的第一個元素是客戶端物件 # 第二個元素是連接到伺服器的客戶端地址(由IP和port兩部分構成) client, addr = server.accept() print(str(addr) + ' 連接到了伺服器。') # 5. 發送資料 msg = input('請輸入訊息:\n') client.send(msg.encode('utf-8')) # 6. 斷開連接 client.close() ``` - 客戶端 ```=python from socket import socket, SOCK_STREAM, AF_INET client = socket(family=AF_INET, type=SOCK_STREAM) # 2. 連接到伺服器(需要指定IP位址和port) client.connect(('127.0.0.1', 5678)) print('連線成功') # 3. 從伺服器接收資料 print(client.recv(1024).decode('utf-8')) client.close() ``` ## socket + thread 目前的伺服器與某一客戶端通訊時,其他客戶端只能排隊等待處理。這樣的設計顯然無法滿足我們的需求。那怎樣才能設計一個能夠同時處理多個用戶請求的伺服器呢。接下來,我們將設計一個採用多執行緒技術的伺服器,該伺服器將能夠同時接收多個用戶的連線請求並向客戶端傳送圖片。 ## 同步與異步 - 同步: ![圖片3](https://hackmd.io/_uploads/BkAlEhu7yg.png) - 異步 ![圖片4](https://hackmd.io/_uploads/Sy1G43uQJx.png) ## 多執行緒Multithreading ![圖片7](https://hackmd.io/_uploads/S1t3DLtmye.png) <br><br><br><br> ![圖片9](https://hackmd.io/_uploads/H1GMj8YQJg.png) ### python threading - 以下是模擬下載兩份pdf的情況 ```=python ffrom time import time, sleep def download_pdf(filename): print(f'開始下載"{filename}"...') t = 5 sleep(t) print(f'{filename}下載完成 耗費時間{t}秒') start = time() download_pdf('Python從入門到放棄.pdf') download_pdf('計算機概論.pdf') end = time() print(f'總共花費了{end - start}秒') ``` 輸出: ```=python 開始下載"Python從入門到入土.pdf"... Python從入門到入土.pdf下載完成 耗費時間5秒 開始下載"計算機概論.pdf"... 計算機概論.pdf下載完成 耗費時間5秒 總共花費了10.00235104560852秒 ``` 從上述例子可以看出,如果程式中的程式碼只能按順序逐步執行,那麼即使是執行兩個彼此無關的下載任務,也需要先等一個檔案下載完成後才能開始下一個下載任務。很顯然,這樣的方式既不合理也缺乏效率。 - 以下使用python threading來多執行緒執行下載任務 ```=python from threading import Thread from time import time, sleep def download(filename): print(f'開始下載"{filename}"...') t = 5 sleep(t) print(f'{filename}下載完成 耗費時間{t}秒') start = time() # target=目標韓式, args=(目標韓式的參數1, 目標韓式的參數2, ....) t1 = Thread(target=download, args=('Python從入門到放棄.pdf',)) # 啟動t1執行緒 t1.start() t2 = Thread(target=download, args=('計算機概論.pdf',)) # 啟動t2執行緒 t2.start() # 賭塞,等待t1執行緒執行完 t1.join() # 賭塞,等待t2執行緒執行完 t2.join() end = time() print(f'總共花費了{end - start}秒') ``` 輸出: ```=python 開始下載"Python從入門到入土.pdf"... 開始下載"計算機概論.pdf"... 計算機概論.pdf下載完成 耗費時間5秒 Python從入門到入土.pdf下載完成 耗費時間5秒 總共花費了5.001640558242798秒 ``` ### 多執行緒的socket伺服器 - socket + thread: 伺服器端 ```=python from socket import socket, SOCK_STREAM, AF_INET from threading import Thread def handle_client(client, addr): # 處理與客戶端的互動 print(f'{addr} 連接到了伺服器。') msg = input('請輸入訊息:\n') client.send(msg.encode('utf-8')) client.close() print(f'{addr} 的連接已關閉。') # 1. 創建Socket物件並指定使用哪種IP格式、傳輸協議 # family=AF_INET - IPv4位址 # family=AF_INET6 - IPv6位址 # type=SOCK_STREAM - TCP套接字 # type=SOCK_DGRAM - UDP套接字 server = socket(family=AF_INET, type=SOCK_STREAM) # 2. 綁定IP位址和port(port用於區分不同的服務) server.bind(('127.0.0.1', 5678)) # 3. 開啟監聽 - 監聽客戶端連接到伺服器 # 參數來限制最多能有幾個客戶端排隊連接伺服器 server.listen(5) print('伺服器啟動,開始監聽...') while True: # 4. 通過迴圈接收客戶端的連接並作出相應的處理(提供服務) # accept方法是一個阻塞方法,如果沒有客戶端連接到伺服器,程式碼不會繼續向下執行 # accept方法返回一個元組,其中的第一個元素是客戶端物件 # 第二個元素是連接到伺服器的客戶端地址(由IP和port兩部分構成) client, addr = server.accept() # 5. 為每個客戶端新建一個執行緒 client_thread = Thread(target=handle_client, args=(client, addr)) client_thread.start() ``` - socket + thread: 客戶端 ```=python from socket import socket, SOCK_STREAM, AF_INET client = socket(family=AF_INET, type=SOCK_STREAM) # 2. 連接到伺服器(需要指定IP位址和port) client.connect(('127.0.0.1', 5678)) print('連線成功') # 3. 從伺服器接收資料 print(client.recv(1024).decode('utf-8')) client.close() ``` # Lab題目作業 - 創建一個資料夾,名稱: ==學號_Lab10==,放入作業後,上傳至GitHub ## 作業1 : 簡易通訊軟體1 - 伺服器端要求: - 在樹梅派上執行 - 使用多執行緒來服務最少2個客戶端,並且能接收客戶端發送的訊息 - 檔案名稱: ==Lab10_server.py== - 客戶端要求: - 在筆電或桌電上執行 - 能夠對伺服器端發送訊息 - 檔案名稱: ==Lab10_client.py== - 擷取成功畫面,圖片檔案名稱: ==Lab10.png== ## 加分題 : 簡易通訊軟體2 - 伺服器端要求: - 在樹梅派上執行 - 使用多執行緒來服務最少2個客戶端,並且能接收客戶端發送的訊息 - 檔案名稱: ==Lab10_server_plus.py== - 客戶端要求: - 在筆電或桌電上執行 - 能夠對伺服器端發送訊息,並且一直保持連線 - 檔案名稱: ==Lab10_client_plus.py== - 擷取成功畫面,圖片檔案名稱: ==Lab10_plus.png==