《Python核心程式設計》練習題之2-8:更新上一個練習的解決方案,修改它以使你的聊天服務現在成為全雙工模式,意味著通訊兩端都可以傳送並接收訊息,並且二者相互獨立
阿新 • • 發佈:2020-12-10
雙工模式,意味著收訊息不耽誤發訊息,發訊息不耽誤收訊息,收發雙工,說的直白一點,這就是讓我寫一個精簡版的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()
因為收發共用一個顯示器,所以,真執行起來,可能不是很好看,但絕對是同時收發了。如果真做成程式,需要收訊息在一個輸入框裡,發訊息在一個顯示框裡。實際執行效果如下: