1. 程式人生 > 其它 >《Python核心程式設計》練習題之2-8:更新上一個練習的解決方案,修改它以使你的聊天服務現在成為全雙工模式,意味著通訊兩端都可以傳送並接收訊息,並且二者相互獨立

《Python核心程式設計》練習題之2-8:更新上一個練習的解決方案,修改它以使你的聊天服務現在成為全雙工模式,意味著通訊兩端都可以傳送並接收訊息,並且二者相互獨立

技術標籤:筆記python多執行緒socket

雙工模式,意味著收訊息不耽誤發訊息,發訊息不耽誤收訊息,收發雙工,說的直白一點,這就是讓我寫一個精簡版的QQ程式啊。這我想了想,那不得引入多執行緒嗎?一個執行緒既做收,也做發,那必然要一個一個來,前面那個走不了,後面那個就得等,實現不了雙工,因此我覺得必須使用多執行緒。而多執行緒是書上第四章講的東西,卻在第二章出這個題,不知道是為啥。
伺服器程式碼如下:

from socket import *
from threading import Thread


class MyThread(Thread):
    """Thread類的子類,socket收發訊息都例項化這個類來進行多執行緒併發"""
def __init__(self, func, args, name=''): """ func引數是此執行緒要呼叫的函式名稱 args引數是此執行緒要呼叫的函式的引數 name我也不知道是幹啥用的,Thread類的初始化是有這個引數的,既然繼承了Thread類,就帶上這個引數 """ Thread.__init__(self) self.func = func self.args = args self.
name = name def run(self): """執行緒啟動時,將預設呼叫這個方法。這個函式的內容即執行func函式""" # 注意func內的引數前面帶有*號。因為func函式可能不止一個引數,因此使用*號引數,傳入的是一個元組。如果有2個及以上的引數,直接用()傳遞 # 接上,如果只有1個引數,需要在引數後加逗號。比如func函式只有1個引數10,則在例項化MyThread類時,args的位置應寫為(10,) self.func(*self.args)
def recv_message(work_socket, buffsize): """ 功能: 收訊息的函式,此函式講永遠執行下去,暫時沒有設計退出的方法 work_socket引數是socket型別的,是socket通訊建立成功後,進行通訊的socket buffsize引數是work_socket每次收訊息的快取位元組數 """ # 檢查work_socket型別是否為socket型別。做此校驗有2個好處,1個是能夠使程式更健壯, # 另1個是方便下面在使用這個引數時pycharm能夠自動關聯socket類的相關方法 if not isinstance(work_socket, socket): raise TypeError while True: recv_data = work_socket.recv(buffsize) print('接收到訊息: ', recv_data.decode('utf-8')) def send_message(work_socket): """ 功能:收訊息的函式,此函式講永遠執行下去,暫時沒有設計退出的方法 work_socket引數是socket型別的,是socket通訊建立成功後,進行通訊的socket """ # 檢查work_socket型別是否為socket型別。做此校驗有2個好處,1個是能夠使程式更健壯, # 另1個是方便下面在使用這個引數時pycharm能夠自動關聯socket類的相關方法 if not isinstance(work_socket, socket): raise TypeError while True: send_data = input('請輸入要傳送的訊息:') work_socket.send(send_data.encode('utf-8')) def main(): """ 這個函式將建立並啟動伺服器socket,實現同時收發訊息 這裡把啟動伺服器寫到main函式裡,而不是直接寫,是因為我們在寫客戶端程式時會引用這個模組裡的MyThread類、send_message、recv_message 如果不寫在函式裡,當客戶端模組引用這個伺服器模組時,就會預設執行函式外的程式碼,這會帶來異常 """ HOST = '' PORT = 18685 BUFFSIZE = 1024 ADDR = (HOST, PORT) # 建立伺服器的socket並開始監聽 server_socket = socket(AF_INET, SOCK_STREAM) server_socket.bind(ADDR) server_socket.listen(1) print('伺服器監聽已開啟,等待連線...') work_socket, addr = server_socket.accept() # 將伺服器socket收訊息的功能放入執行緒t1.此處func = recv_message, args = (work_socket, BUFFSIZE) t1 = MyThread(recv_message, (work_socket, BUFFSIZE)) # 將伺服器socket發訊息的功能放入執行緒t2,此處func = send_message, args = (work_socket,) t2 = MyThread(send_message, (work_socket,)) # 啟動執行緒 t1.start() t2.start() if __name__ == '__main__': main()

客戶端程式碼如下(注意我本地的伺服器模組名稱是tsTservTW.py):

from socket import *

# 引入伺服器的模組,(我本地的伺服器模組的名稱叫tsTservTW.py)直接複用伺服器模組的執行緒類和收發訊息函式
from tsTservTW import MyThread, recv_message, send_message

HOST = '127.0.0.1'
PORT = 18685
BUFFSIZE = 1024
ADDR = (HOST, PORT)

client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect(ADDR)

# 將客戶端收訊息的功能放入執行緒t1,此處func = recv_message, args = (client_socket, BUFFSIZE)
t1 = MyThread(recv_message, (client_socket, BUFFSIZE))

# 將客戶端socket發訊息的功能放入執行緒t2,此處func = send_message, args = (client_socket,)
t2 = MyThread(send_message, (client_socket,))

# 啟動執行緒
t1.start()
t2.start()

因為收發共用一個顯示器,所以,真執行起來,可能不是很好看,但絕對是同時收發了。如果真做成程式,需要收訊息在一個輸入框裡,發訊息在一個顯示框裡。實際執行效果如下:
這個是客戶端的截圖
在這裡插入圖片描述