# Lab10: 網路管理、Socket
## 1. 計算機網路發展史
- 1960s - 美國國防部ARPANET專案問世,奠定了網路的基礎。
- 1980s - 國際標準化組織(ISO)釋出[OSI模型](https://zh.wikipedia.org/zh-tw/OSI%E6%A8%A1%E5%9E%8B),奠定了網路技術標準化的基礎。

- 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)發明了圖形化的瀏覽器,瀏覽器的簡單易用性使得計算機網路迅速被普及。
在沒有瀏覽器的年代,上網是這樣的。

有了瀏覽器以後,上網是這樣的。

## 2.TCP/IP 通訊協定

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

- 伺服器端
```=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
目前的伺服器與某一客戶端通訊時,其他客戶端只能排隊等待處理。這樣的設計顯然無法滿足我們的需求。那怎樣才能設計一個能夠同時處理多個用戶請求的伺服器呢。接下來,我們將設計一個採用多執行緒技術的伺服器,該伺服器將能夠同時接收多個用戶的連線請求並向客戶端傳送圖片。
## 同步與異步
- 同步:

- 異步

## 多執行緒Multithreading

<br><br><br><br>

### 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==