01. Bing 提供的範例

展示如何與位於 IP 地址 192.168.1.1 和端口 2000 的 PLC 進行連線,以及如何從 PLC 讀取資料和向 PLC 寫入資料。

檔案: socket_0307-1.py

import socket

# 設定伺服器的 IP 地址和端口號
server_ip = '192.168.1.1'
server_port = 2000

# 建立一個 socket 物件
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 連線到伺服器
client_socket.connect((server_ip, server_port))
# 向伺服器發送資料
client_socket.sendall("Hello, PLC!".encode())
# 從伺服器接收資料
data = client_socket.recv(1024)
# 打印接收到的資料
print('Received from the server:', data.decode())

# 關閉連線
client_socket.close()

02. 伺服器端

我們將在本機建置一個伺服器與客戶端,將對客戶端接收與傳送訊息。

(2-1) 載入套件

由於客戶數量可能會很大,因此我們需要使用 threading 多執行緒。

Python 網路程式設計_Socket內建方法

Python 提供了兩個級別訪問的網路服務:

  • 低級別的網路服務支援基本的Socket,它提供了標準的BSD Sockets API,可以訪問底層操作系統Socket介面的全部方法。
  • 高級別的網路服務模組 SocketServer, 它提供了伺服器中心類,可以簡化網路伺服器的開發。

什麼是Socket?

Socket又稱「套接字」,應用程式通常通過「套接字」向網路發出請求或者應答網路請求,使主機間或者一台電腦上的進程間可以通訊。


socket()函數

Python 中,我們用 socket() 函數來創建套接字,語法格式如下:

socket.socket([family[, type[, proto]]])
參數
  • family: 套接字家族可以使 AF_UNIX 或者 AF_INET。
  • type: 套接字類型可以根據是面向連接的還是非連接分為 SOCK_STREAM 或 SOCK_DGRAM。
  • protocol: 一般不填預設為 0。
簡單實例

(1) 服務端

我們使用 socket 模組的 socket 函數來建立一個 socket 物件。 socket 物件可以透過呼叫其他函數來設定一個 socket 服務。

現在我們可以透過呼叫 bind(hostname, port) 函數來指定服務的 port(連接埠)。

接著,我們呼叫 socket 物件的 accept 方法。 此方法等待客戶端的連接,並傳回 connection 對象,表示已連接到客戶端。

檔案: server.py

import socket
 
s = socket.socket()         # 創建 socket 對象
host = socket.gethostname()  # 獲取本地主機名
port = 12345
s.bind((host, port))        # 绑定端口

s.listen(5)                 # 等待客户端连接
while True:
    c,addr = s.accept()     # 建立客户端连接
    print(f"連接地址: {addr}")
    c.send("歡迎訪問菜鳥教程!")
    c.close()                # 關閉連接

(2) 客戶端

接下來我們寫一個簡單的客戶端實例連接到以上所建立的服務。 連接埠號碼為 12345。

socket.connect(hostname, port ) 方法開啟一個 TCP 連線到主機為 hostname 連接埠為 port 的服務商。 連線後我們就可以從服務端取得數據,記住,操作完成後需要關閉連線。

檔案: client.py

import socket
 
s = socket.socket()         # 創建 socket 對象
host = socket.gethostname() # 獲取本地主機名
port = 12345
 
s.connect((host, port))
print(s.recv(1024))
s.close()

(2-2) 檢查網路狀態

輸入 ipconfig 檢查目前的網路狀態。IPv4 是目前網路的位置。

(2-3) 獲取當前主機IP

使用 socket 模組來獲取當前主機的IP地址。gethostname() 函數返回當前執行程式的系統的主機名,而 gethostbyname() 函數將這個主機名轉換成IP地址。

檔案: server.py

import socket
import threading

# (2-3)獲取當前主機IP
PORT = 5050
# SERVER = "192.168.1.26"
SERVER = socket.gethostbyname(socket.gethostname())
print(SERVER)  #127.0.0.1

(2-4) 綁定socket位址
  • socket.AF_INET 是一個地址族(Address Family),指定了這個socket將使用IPv4網路協議。
  • socket.SOCK_STREAM 表示socket類型。SOCK_STREAM 代表這個socket將使用TCP協議,這是一種連接導向的、可靠的、基於字節流的傳輸方式。

檔案: server.py

import socket
import threading

# (2-3)獲取當前主機IP
PORT = 5050
# SERVER = "192.168.1.26"
SERVER = socket.gethostbyname(socket.gethostname())
# print(SERVER)  #127.0.0.1
# print(socket.gethostname())  #23-0535487-H1

# (2-4)綁定socket位址
ADDR = (SERVER, PORT)
# 創建一個新的socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(ADDR)

(2-5) server accept設置

conn 和 addr 是由 server.accept() 返回的兩個值:

  1. conn:這是一個新的socket物件,用於與客戶端進行通信。你可以使用它來傳送和接收數據。
  2. addr:這是一個包含客戶端地址的元組。它包含客戶端的IP地址和埠號。例如,如果客戶端從IP地址192.168.1.10的埠號12345連接,則addr的值將是("192.168.1.10", 12345)

因此,conn 是一個socket物件,addr 是一個元組,包含了客戶端的地址信息。你可以在 handle_client(conn, addr) 函式中使用這些值來處理客戶端的請求。

檔案: server.py

import socket
import threading

# (2-3)獲取當前主機IP
PORT = 5050
# SERVER = "192.168.1.26"
SERVER = socket.gethostbyname(socket.gethostname())
print(SERVER)  #127.0.0.1

# (2-4)綁定socket位址
ADDR = (SERVER, PORT)

# 創建一個新的socket
# AF_INET表示使用IPv4地址,SOCK_STREAM表示使用TCP協議
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 將伺服器綁定到指定的IP地址和埠號
server.bind(ADDR)

def handle_client(conn, addr):
    pass

def start():
    # (2-5)server accept設置
    # start() 函式是一個無限迴圈,它會等待客戶端的連接。
    # 當有客戶端連接時,server.accept()會返回一個新的socket物件(conn)和客戶端的地址(addr)。
    # 你可以在handle_client(conn, addr)函式中處理客戶端的請求。
    server.listen()
    while True:
        conn, addr = server.accept()

print("[STARTING] server已經啟動....")
start()

(2-6) 啟動多線程

每當有新的客戶端連接時,它會創建一個新的線程來處理該客戶端的請求。

檔案: server.py

import socket
import threading

# (2-3)獲取當前主機IP
PORT = 5050
# SERVER = "192.168.1.26"
SERVER = socket.gethostbyname(socket.gethostname())
print(SERVER)  #127.0.0.1

# (2-4)綁定socket位址
ADDR = (SERVER, PORT)

# 創建一個新的socket
# AF_INET表示使用IPv4地址,SOCK_STREAM表示使用TCP協議
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 將伺服器綁定到指定的IP地址和埠號
server.bind(ADDR)

def handle_client(conn, addr):
    pass

def start():
    # (2-5)server accept設置
    # start() 函式是一個無限迴圈,它會等待客戶端的連接。
    # 當有客戶端連接時,server.accept()會返回一個新的socket物件(conn)和客戶端的地址(addr)。
    # 你可以在handle_client(conn, addr)函式中處理客戶端的請求。
    server.listen()
    while True:
        conn, addr = server.accept()
        # (2-6)啟動多線程
        # 將conn和addr傳遞給handle_client函式作為參數
        thread = threading.Thread(target=handle_client, args=(conn, addr))
        # 啟動線程,開始執行 handle_client 函式
        thread.start()
        # 打印出當前活動的線程數量(不包括主線程)
        print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}")

print("[STARTING] server已經啟動....")
start()

(2-7) 處理客戶端的連接

檔案: server.py

import socket
import threading

# (2-3)獲取當前主機IP
PORT = 5050
# SERVER = "192.168.1.26"
SERVER = socket.gethostbyname(socket.gethostname())
print(SERVER)  #127.0.0.1

# (2-4)綁定socket位址
ADDR = (SERVER, PORT)

# 創建一個新的socket
# AF_INET表示使用IPv4地址,SOCK_STREAM表示使用TCP協議
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 將伺服器綁定到指定的IP地址和埠號
server.bind(ADDR)

def handle_client(conn, addr):
    # (2-7)用於處理客戶端的連接
    # 顯示客戶端的地址(addr)
    print(f"[NEW ONNECTION] {addr} 已連接.")

    connected = True
    while connected:
        # 在收到客戶端的消息前,不會運行。用於接收客戶端發送的數據。一旦有數據到達,它會返回該數據。
        msg = conn.recv() #阻塞運行碼

def start():
    # (2-5)server accept設置
    # start() 函式是一個無限迴圈,它會等待客戶端的連接。
    # 當有客戶端連接時,server.accept()會返回一個新的socket物件(conn)和客戶端的地址(addr)。
    # 你可以在handle_client(conn, addr)函式中處理客戶端的請求。
    server.listen()
    while True:
        conn, addr = server.accept()
        # (2-6)啟動多線程
        # 將conn和addr傳遞給handle_client函式作為參數
        thread = threading.Thread(target=handle_client, args=(conn, addr))
        # 啟動線程,開始執行 handle_client 函式
        thread.start()
        # 打印出當前活動的線程數量(不包括主線程)
        print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}")

print("[STARTING] server已經啟動....")
start()

(2-8) 字節格式解碼為字符串
  1. msg_length = conn.recv(HEADER).decode(FORMAT)
    • 這行程式碼會從客戶端接收一個標頭,該標頭指示了即將接收的消息的長度。
    • HEADER 是一個常數,通常是一個固定的整數,用於指定標頭的大小。
    • conn.recv(HEADER) 會阻塞運行,直到接收到指定大小的數據。
    • decode(FORMAT) 用於將接收到的字節數據解碼為字符串。
  2. msg_length = int(msg_length)
    • 將接收到的消息長度轉換為整數。
  3. msg = conn.recv(msg_length).decode(FORMAT)
    • 這行程式碼會接收實際的消息,其長度由上一步計算得出。
    • conn.recv(msg_length) 會阻塞運行,直到接收到指定長度的數據。
    • decode(FORMAT) 用於將接收到的字節數據解碼為字符串。
  4. print(f"[{addr}] {msg}")
    • 這行程式碼會打印出客戶端的地址(addr)和接收到的消息(msg)。

檔案: server.py

import socket
import threading

# (2-8)字節格式解碼為字符串
# 固定長度的標頭
HEADER = 64
FORMAT = 'utf-8'

# (2-3)獲取當前主機IP
PORT = 5050
# SERVER = "192.168.1.26"
SERVER = socket.gethostbyname(socket.gethostname())
print(SERVER)  #127.0.0.1

# (2-4)綁定socket位址
ADDR = (SERVER, PORT)

# 創建一個新的socket
# AF_INET表示使用IPv4地址,SOCK_STREAM表示使用TCP協議
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 將伺服器綁定到指定的IP地址和埠號
server.bind(ADDR)

def handle_client(conn, addr):
    # (2-7)用於處理客戶端的連接
    # 顯示客戶端的地址(addr)
    print(f"[NEW ONNECTION] {addr} 已連接.")

    connected = True
    while connected:
        # 在收到客戶端的消息前,不會運行。用於接收客戶端發送的數據。一旦有數據到達,它會返回該數據。
        # msg = conn.recv() #阻塞運行碼
        
        # (2-8)字節格式解碼為字符串
        # 消息長度等於conn點收到的標頭。 因為每次我們發送消息時,
        # 都需要編碼為字節格式,所以將字節格式解碼為字符串。
        msg_length = conn.recv(HEADER).decode(FORMAT)
        msg_length = int(msg_length)
        msg = conn.recv(msg_length).decode(FORMAT)
        print(f"[{addr}] {msg}")


def start():
    # (2-5)server accept設置
    # start() 函式是一個無限迴圈,它會等待客戶端的連接。
    # 當有客戶端連接時,server.accept()會返回一個新的socket物件(conn)和客戶端的地址(addr)。
    # 你可以在handle_client(conn, addr)函式中處理客戶端的請求。
    server.listen()
    while True:
        conn, addr = server.accept()
        # (2-6)啟動多線程
        # 將conn和addr傳遞給handle_client函式作為參數
        thread = threading.Thread(target=handle_client, args=(conn, addr))
        # 啟動線程,開始執行 handle_client 函式
        thread.start()
        # 打印出當前活動的線程數量(不包括主線程)
        print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}")

print("[STARTING] server已經啟動....")
start()

(2-9) 自動斷開連接

在接收到特定消息(DISCONNECT_MESSAGE)時,程式碼會自動斷開與客戶端的連接,將 connected 設置為 False

檔案: server.py

import socket
import threading

# (2-8)字節格式解碼為字符串
# 固定長度的標頭
HEADER = 64
FORMAT = 'utf-8'

# (2-9)自動斷開連接
DISCONNECT_MESSAGE = "!DISCONNECT"

# (2-3)獲取當前主機IP
PORT = 5050
# SERVER = "192.168.1.26"
SERVER = socket.gethostbyname(socket.gethostname())
print(SERVER)  #127.0.0.1

# (2-4)綁定socket位址
ADDR = (SERVER, PORT)

# 創建一個新的socket
# AF_INET表示使用IPv4地址,SOCK_STREAM表示使用TCP協議
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 將伺服器綁定到指定的IP地址和埠號
server.bind(ADDR)

def handle_client(conn, addr):
    # (2-7)用於處理客戶端的連接
    # 顯示客戶端的地址(addr)
    print(f"[NEW ONNECTION] {addr} 已連接.")

    connected = True
    while connected:
        # 在收到客戶端的消息前,不會運行。用於接收客戶端發送的數據。一旦有數據到達,它會返回該數據。
        # msg = conn.recv() #阻塞運行碼
        
        # (2-8)字節格式解碼為字符串
        # 消息長度等於conn點收到的標頭。 因為每次我們發送消息時,
        # 都需要編碼為字節格式,所以將字節格式解碼為字符串。
        msg_length = conn.recv(HEADER).decode(FORMAT)
        msg_length = int(msg_length)
        msg = conn.recv(msg_length).decode(FORMAT)
        
        # (2-9)自動斷開連接
        if msg == DISCONNECT_MESSAGE:
            connected = False
        print(f"[{addr}] {msg}")

    conn.close()

def start():
    # (2-5)server accept設置
    # start() 函式是一個無限迴圈,它會等待客戶端的連接。
    # 當有客戶端連接時,server.accept()會返回一個新的socket物件(conn)和客戶端的地址(addr)。
    # 你可以在handle_client(conn, addr)函式中處理客戶端的請求。
    server.listen()
    while True:
        conn, addr = server.accept()
        # (2-6)啟動多線程
        # 將conn和addr傳遞給handle_client函式作為參數
        thread = threading.Thread(target=handle_client, args=(conn, addr))
        # 啟動線程,開始執行 handle_client 函式
        thread.start()
        # 打印出當前活動的線程數量(不包括主線程)
        print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}")

print("[STARTING] server已經啟動....")
start()

03. 客戶端

32:40

(3-1) 帶入同SERVER參數

檔案: client.py

import socket

# (3-1)帶入同SERVER參數
# 輸入跟SERVER端相同的參數
HEADER = 64
PORT = 5050
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"
# SERVER的IP要根據實際運行的輸入
SERVER = "192.168.201.72"
ADDR = (SERVER, PORT)

(3-2) 建立客戶端連接到指定的服務器地址

我們獲得了連接 1。

建立一個客戶端(client)的套接字(socket)並連接到指定的服務器地址(ADDR)。讓我們來看看它的功能:

  1. socket.socket(socket.AF_INET, socket.SOCK_STREAM):這行程式碼創建了一個新的套接字對象。其中,socket.AF_INET 表示使用 IPv4 地址族,socket.SOCK_STREAM 表示使用 TCP 協議。這個套接字將用於與服務器進行通信。
  2. client.connect(ADDR):這行程式碼連接到指定的服務器地址(ADDR)。它建立了客戶端與服務器之間的通信通道,以便後續的數據交換。

建立一個客戶端套接字並連接到指定的服務器地址,以便進行通信。

檔案: client.py

import socket

# (3-1)帶入同SERVER參數
# 輸入跟SERVER端相同的參數
HEADER = 64
PORT = 5050
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"
# SERVER的IP要根據實際運行的輸入
SERVER = "192.168.201.72"
ADDR = (SERVER, PORT)

# (3-2)建立客戶端連接到指定的服務器地址
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(ADDR)

檔案: server.py

import socket
import threading

# (2-8)字節格式解碼為字符串
# 固定長度的標頭
HEADER = 64
FORMAT = 'utf-8'

# (2-9)自動斷開連接
DISCONNECT_MESSAGE = "!DISCONNECT"

# (2-3)獲取當前主機IP
PORT = 5050
# SERVER = "192.168.1.26"
SERVER = socket.gethostbyname(socket.gethostname())
print(SERVER)  #127.0.0.1

# (2-4)綁定socket位址
ADDR = (SERVER, PORT)

# 創建一個新的socket
# AF_INET表示使用IPv4地址,SOCK_STREAM表示使用TCP協議
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 將伺服器綁定到指定的IP地址和埠號
server.bind(ADDR)

def handle_client(conn, addr):
    # (2-7)用於處理客戶端的連接
    # 顯示客戶端的地址(addr)
    print(f"[NEW ONNECTION] {addr} 已連接.")

    connected = True
    while connected:
        # 在收到客戶端的消息前,不會運行。用於接收客戶端發送的數據。一旦有數據到達,它會返回該數據。
        # msg = conn.recv() #阻塞運行碼
        
        # (2-8)字節格式解碼為字符串
        # 消息長度等於conn點收到的標頭。 因為每次我們發送消息時,
        # 都需要編碼為字節格式,所以將字節格式解碼為字符串。
        msg_length = conn.recv(HEADER).decode(FORMAT)

        # (3-2)建立客戶端連接到指定的服務器地址
        # 判斷是否有收到msg_length。因為第一次不會收到任何內容。
        if msg_length:

            msg_length = int(msg_length)
            msg = conn.recv(msg_length).decode(FORMAT)
            
            # (2-9)自動斷開連接
            if msg == DISCONNECT_MESSAGE:
                connected = False
            print(f"[{addr}] {msg}")

    conn.close()

def start():
    # (2-5)server accept設置
    # start() 函式是一個無限迴圈,它會等待客戶端的連接。
    # 當有客戶端連接時,server.accept()會返回一個新的socket物件(conn)和客戶端的地址(addr)。
    # 你可以在handle_client(conn, addr)函式中處理客戶端的請求。
    server.listen()
    print(f"[LISTENING] Server is listening on {SERVER}")
    while True:
        conn, addr = server.accept()
        # (2-6)啟動多線程
        # 將conn和addr傳遞給handle_client函式作為參數
        thread = threading.Thread(target=handle_client, args=(conn, addr))
        # 啟動線程,開始執行 handle_client 函式
        thread.start()
        # 打印出當前活動的線程數量(不包括主線程)
        print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}")

print("[STARTING] server已經啟動....")
start()

(3-3) 將訊息傳送給server端

send 之所以分兩次傳送,是為了確保訊息的正確接收。

讓我們來看看為什麼這麼做:

  1. 訊息長度傳送:首先,程式碼將訊息的長度(msg_length)以字串形式傳送給客戶端。這樣客戶端就知道接下來要接收多少位元組的訊息。這是一個必要的步驟,因為不同的訊息可能有不同的長度。
  2. 訊息本體傳送:接著,程式碼再次使用 client.send 函式,將編碼後的訊息本體傳送給客戶端。這樣客戶端就能夠解碼並處理訊息。

總之,分兩次傳送的目的是確保訊息的完整性和正確性。第一次傳送訊息長度,第二次傳送訊息本體,以確保客戶端能夠正確接收並處理訊息。

檔案: client.py

import socket

# (3-1)帶入同SERVER參數
# 輸入跟SERVER端相同的參數
HEADER = 64
PORT = 5050
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"
# SERVER的IP要根據實際運行的輸入
SERVER = "192.168.201.72"
ADDR = (SERVER, PORT)

# (3-2)建立客戶端連接到指定的服務器地址
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(ADDR)

# (3-3)將訊息傳送給server端
def send(msg):
    #  以指定的編碼格式(FORMAT)進行編碼
    message = msg.encode(FORMAT)
    # 計算訊息的長度,並將其轉換為字串,再次以相同的編碼格式進行編碼
    msg_length = len(message)
    send_length = str(msg_length).encode(FORMAT)
    # 補足訊息長度,使其達到指定的標頭長度(HEADER)
    send_length += b' ' * (HEADER - len(send_length))

    # 使用 client.send 函式兩次,分別傳送訊息的長度和編碼後的訊息本體
    client.send(send_length)
    client.send(message)

send("Hello World!")

(3-4) 一次傳送多組訊息

檔案: client.py

import socket

# (3-1)帶入同SERVER參數
# 輸入跟SERVER端相同的參數
HEADER = 64
PORT = 5050
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"
# SERVER的IP要根據實際運行的輸入
SERVER = "192.168.201.72"
ADDR = (SERVER, PORT)

# (3-2)建立客戶端連接到指定的服務器地址
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(ADDR)

# (3-3)將訊息傳送給server端
def send(msg):
    #  以指定的編碼格式(FORMAT)進行編碼
    message = msg.encode(FORMAT)
    # 計算訊息的長度,並將其轉換為字串,再次以相同的編碼格式進行編碼
    msg_length = len(message)
    send_length = str(msg_length).encode(FORMAT)
    # 補足訊息長度,使其達到指定的標頭長度(HEADER)
    send_length += b' ' * (HEADER - len(send_length))

    # 使用 client.send 函式兩次,分別傳送訊息的長度和編碼後的訊息本體
    client.send(send_length)
    client.send(message)

# (3-4)一次傳送多組訊息
send("Hello World!")
send("Hello Everyone!")
send("Hello Tim!")

send(DISCONNECT_MESSAGE)

04. 從伺服端向客戶端發送消息

42:48

(4-1) Server 回覆 Client

我們希望 Server 在收到訊息後,回覆給 client 收到的訊息。

檔案: server.py

import socket
import threading

# (2-8)字節格式解碼為字符串
# 固定長度的標頭
HEADER = 64
FORMAT = 'utf-8'

# (2-9)自動斷開連接
DISCONNECT_MESSAGE = "!DISCONNECT"

# (2-3)獲取當前主機IP
PORT = 5050
# SERVER = "192.168.1.26"
SERVER = socket.gethostbyname(socket.gethostname())
print(SERVER)  #127.0.0.1

# (2-4)綁定socket位址
ADDR = (SERVER, PORT)

# 創建一個新的socket
# AF_INET表示使用IPv4地址,SOCK_STREAM表示使用TCP協議
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 將伺服器綁定到指定的IP地址和埠號
server.bind(ADDR)

def handle_client(conn, addr):
    # (2-7)用於處理客戶端的連接
    # 顯示客戶端的地址(addr)
    print(f"[NEW ONNECTION] {addr} 已連接.")

    connected = True
    while connected:
        # 在收到客戶端的消息前,不會運行。用於接收客戶端發送的數據。一旦有數據到達,它會返回該數據。
        # msg = conn.recv() #阻塞運行碼
        
        # (2-8)字節格式解碼為字符串
        # 消息長度等於conn點收到的標頭。 因為每次我們發送消息時,
        # 都需要編碼為字節格式,所以將字節格式解碼為字符串。
        msg_length = conn.recv(HEADER).decode(FORMAT)

        # (3-2)建立客戶端連接到指定的服務器地址
        # 判斷是否有收到msg_length。 因為第一次不會收到任何內容。
        if msg_length:

            msg_length = int(msg_length)
            msg = conn.recv(msg_length).decode(FORMAT)
            
            # (2-9)自動斷開連接
            if msg == DISCONNECT_MESSAGE:
                connected = False
            print(f"[{addr}] {msg}")
            
            # (4-1)server送訊息給client
            # conn.send("Msg Received".encode(FORMAT))
            conn.send("訊息已收到".encode(FORMAT))

    conn.close()

def start():
    # (2-5)server accept設置
    # start() 函式是一個無限迴圈,它會等待客戶端的連接。
    # 當有客戶端連接時,server.accept()會返回一個新的socket物件(conn)和客戶端的地址(addr)。
    # 你可以在handle_client(conn, addr)函式中處理客戶端的請求。
    server.listen()
    print(f"[LISTENING] Server is listening on {SERVER}")
    while True:
        conn, addr = server.accept()
        # (2-6)啟動多線程
        # 將conn和addr傳遞給handle_client函式作為參數
        thread = threading.Thread(target=handle_client, args=(conn, addr))
        # 啟動線程,開始執行 handle_client 函式
        thread.start()
        # 打印出當前活動的線程數量(不包括主線程)
        print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}")

print("[STARTING] server已經啟動....")
start()

理論上 recv(2048) 的值應該跟 HEAD 長度相同,但也可以留下更大的空間。

檔案: client.py

import socket

# (3-1)帶入同SERVER參數
# 輸入跟SERVER端相同的參數
HEADER = 64
PORT = 5050
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"
# SERVER的IP要根據實際運行的輸入
SERVER = "192.168.201.72"
ADDR = (SERVER, PORT)

# (3-2)建立客戶端連接到指定的服務器地址
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(ADDR)

# (3-3)將訊息傳送給server端
def send(msg):
    #  以指定的編碼格式(FORMAT)進行編碼
    message = msg.encode(FORMAT)
    # 計算訊息的長度,並將其轉換為字串,再次以相同的編碼格式進行編碼
    msg_length = len(message)
    send_length = str(msg_length).encode(FORMAT)
    # 補足訊息長度,使其達到指定的標頭長度(HEADER)
    send_length += b' ' * (HEADER - len(send_length))

    # 使用 client.send 函式兩次,分別傳送訊息的長度和編碼後的訊息本體
    client.send(send_length)
    client.send(message)

    # (4-1)server送訊息給client
    # print(client.recv(2048))
    # 進行解碼確保不是字節
    print(client.recv(2048).decode(FORMAT))

# (3-4)一次傳送多組訊息
send("Hello World!")
send("Hello Everyone!")
send("Hello Tim!")

send(DISCONNECT_MESSAGE)

..