iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0
Python

一些Python可以做的事系列 第 6

[Python] Socket + Threading

  • 分享至 

  • xImage
  •  

Threading

在 Python 中若要撰寫多執行緒(multithreading)的平行化程式,最基本的方式是使用 threading 這個模組來建立子執行緒。

模組 :

import threading
  1. 要先建立一個子執行緒的工作函數
# 子執行緒的工作函數
def job():
  for i in range(5):
    print("Child thread:", i)
    time.sleep(1)
  1. 再建立一個子執行緒
t = threading.Thread(target = job)
  1. 執行該子執行緒
t.start()

實作

大概就是一個多 client 端即時通訊系統,server 端處理連接並管理 client 端消息,而每個 cleint 端獨立接收和發送消息

步驟 :

  • 伺服器端:

    1. 初始化伺服器
    2. 接收並管理客戶端連接
    3. 為每個客戶端創建 thread 處理消息
    4. 提供命令操作界面
    5. 關閉伺服器及其所有連接
  • 客戶端:

    1. 初始化並連接到伺服器
    2. 發送名稱並接收連接成功消息
    3. 創建 thread 接收伺服器消息
    4. 提供界面讓用戶發送消息
    5. 關閉連接前確保資源釋放

Server 端

import socket
from threading import Thread
import uuid  # 用於生成唯一標識符

host = ''
port = 8808

socket_server = None  # 負責監聽的 socket
conn_pool = {}  # 連接池,使用字典來存放每個客戶端的標記和 socket 對象


# 初始化伺服器端
def init():
    global socket_server
    # 創建 socket 對象
    socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    socket_server.bind((host, port))  # 綁定地址
    socket_server.listen(5)  # 設置最大等待連接數
    print("Listening...")


# 接收新連接
def accept_client():
    while True:
        client, _ = socket_server.accept()  # 阻塞,等待客戶端連接

        # 請求客戶端發送名稱
        name = client.recv(1024).decode(encoding='utf8')

        # 生成唯一標識符
        client_id = str(uuid.uuid4())

        # 使用名稱和標識符存儲客戶端
        if name not in conn_pool:
            conn_pool[name] = {'id': client_id, 'socket': client}
            print(f'客戶端 {name} 已連接,ID: {client_id}')

            # 請求客戶端確認連接
            client.sendall("連接伺服器成功!".encode(encoding='utf8'))

            # 為每個客戶端創建一個獨立的線程進行管理
            thread = Thread(target=message_handle, args=(client, name))
            thread.daemon = True  # 設置為 daemon thread
            thread.start()
        else:
            client.sendall("名稱已被使用,請重新連接。".encode(encoding='utf8'))
            client.close()


# 訊息處理
def message_handle(client, name):
    while True:
        try:
            message = client.recv(1024)
            if len(message) == 0:
                break
            print(f"客戶端 {name} 消息:", message.decode(encoding='utf8'))
        except socket.error as e:
            print(f"\n接收消息錯誤:{e}")
            break
    client.close()
    if name in conn_pool:
        del conn_pool[name]  # 使用名稱刪除連接
        print(f"客戶端 {name} 下線了。")


if __name__ == '__main__':
    init()
    # 新開一個線程,用於接收新連接
    thread = Thread(target=accept_client)
    thread.daemon = True
    thread.start()

    while True:
        cmd = input("""
--------------------------
選擇操作:
1. 查看當前在線人數
2. 給指定客戶端發送消息
3. 關閉伺服器端
--------------------------
請輸入選擇 (1/2/3): 
""")

        if cmd == '1':
            print("~~~~~~~~~~~~~~~~~~~~~~~~~~")
            # 連接池的長度就是連接人數
            print("當前在線人數:", len(conn_pool))
        elif cmd == '2':
            print("~~~~~~~~~~~~~~~~~~~~~~~~~~")
            # 列出所有客戶端名稱
            print("當前在線客戶端名稱:", list(conn_pool.keys()))
            # name 是指定要傳送給哪個 Client 的名稱,msg 就是要傳送的訊息
            input_data = input("請輸入“客戶端名稱,訊息”的形式:")
            if ',' in input_data:
                name, msg = input_data.split(",", 1)
                if name in conn_pool:
                    conn_pool[name]['socket'].sendall(msg.encode(encoding='utf8'))
                else:
                    print("無效的客戶端名稱。")
            else:
                print("請按照“客戶端名稱,訊息”的形式輸入。")
        elif cmd == '3':
            socket_server.close()  # 關閉伺服器端
            break

Client 端

import socket
import threading

# 接收來自伺服器的消息並打印出來
def receive_messages(sock):

    while True:
        try:
            response = sock.recv(1024).decode('utf8')
            if response:
                print("\n伺服器回應:", response)
            else:
                # 如果沒有數據,伺服器可能已關閉連接
                print("\n伺服器已關閉連接")
                break
        except socket.error as e:
            print(f"\n接收消息錯誤:{e}")
            break

def main():

    # 創建 socket 對象並連接到伺服器
    socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_client.connect(('127.0.0.1', 8808))

    try:
        # 輸入並發送名稱
        name = input("請輸入您的名稱:")
        socket_client.send(name.encode(encoding='utf8'))

        # 接收並打印連接成功消息
        print(socket_client.recv(1024).decode(encoding='utf8'))

        # 創建線程來接收伺服器的消息
        recv_thread = threading.Thread(target=receive_messages, args=(socket_client,))
        recv_thread.daemon = True
        recv_thread.start()

        while True:
            # 發送消息
            msg = input("發送消息 (輸入 'exit' 退出):")
            if msg.lower() == 'exit':
                socket_client.send("exit".encode('utf8'))  # 通知伺服器客戶端將要退出
                break
            socket_client.send(msg.encode('utf8'))

    except Exception as e:
        print(f"發生錯誤:{e}")
    finally:
        # 確保在退出時關閉連接
        socket_client.close()

if __name__ == "__main__":
    main()

圖示 :

https://ithelp.ithome.com.tw/upload/images/20240815/20168345H9VyYITkSF.pnghttps://ithelp.ithome.com.tw/upload/images/20240815/20168345DvZUQMeTXR.pnghttps://ithelp.ithome.com.tw/upload/images/20240815/20168345NqrI229Qq5.pnghttps://ithelp.ithome.com.tw/upload/images/20240815/20168345cM0VQ0m7kO.png

參考資料 :
https://blog.csdn.net/qq_39687901/article/details/81531101
https://blog.gtwang.org/programming/python-threading-multithreaded-programming-tutorial/


上一篇
[Python] Socket
下一篇
[Python] ThreadPoolExecutor
系列文
一些Python可以做的事30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言