之前完成了一個簡單的聊天服務器,連接服務器使用的是系統自帶nc
命令,接下來就是通過自己實現TCPClient
.
客戶端與服務器功能大致相仿,相對與服務器只是少了轉發消息環節。
首先,定義TCPClient
類,主要初始化host
、port
、stream
屬性。
class SimpleTCPClient:
def __init__(self, host, port):
self._host = host
self._port = port
self._stream = None
self.EOF = b' end'
剛創建client
實例時還未與服務器連接,所以_stream
初始值為None
。EOF
設置為消息的結尾,當讀到這個標識時表示一條消息輸出完畢。
def get_stream(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
self._stream = tornado.iostream.IOStream(sock)
self._stream.set_close_callback(self.on_close)
獲取socket
,通過tornado.iostream.IOStream
創建_stream
。
socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
中,其中第一個參數是namespace
即使用的地址,這里的AF_INET
表示使用IPv4
地址,第二個參數是style
,即socket的連接類型,這里使用的SOCK_STREAM
是流式類型基於TCP。第三個參數是protocol
指使用的協議類型,一般情況下使用0
表示系統根據情況決定。
def connect(self):
self.get_stream()
self._stream.connect((self._host, self._port), self.start)
定義連接服務器的方法,先獲取_stream
然后連接服務器地址和指定端口,最后注冊回調函數就是開始客戶端運行的函數。
def start(self):
t1 = threading.Thread(target = self.read_msg)
t2 = threading.Thread(target = self.send_msg)
t1.daemon, t2.daemon = True, True
t1.start()
t2.start()
使用多線程同時通知收發消息。這里存在一個問題,使用多線程時,如果退出程序就必須要結束線程,否則會拋出異常,但是程序何時結束取決於用戶。為了解決這個問題,將線程設置為daemon
線程,daemon
線程可以在主程序結束時自動結束。
def read_msg(self):
self._stream.read_until(self.EOF, self.show_msg)
def show_msg(self, data):
print(to_unicode(data))
self.read_msg()
接受並顯示消息。當數據中讀取到結束標識,調用打印消息的方法,消息打印完畢后再調用讀取方法,以此保持接收消息的狀態。
def send_msg(self):
while True:
data = input()
self._stream.write(bytes(data) + self.EOF)
發送消息。使用while
循環保持輸入狀態,當輸入完成講消息轉換為byte
型,與消息結束標識拼接之后發送。
def on_close(self):
print('exit ...')
quit()
用戶退出時關閉_stream
會激活這個函數。
if __name__ == '__main__':
try:
client = SimpleTCPClient('localhost', 8888)
client.connect()
tornado.ioloop.IOLoop.instance().start()
到這里TCPClient
基本完成。
之前絞盡腦汁不知道該如何解決同時保持收發信息的狀態,也有想過通過多線程的方式,但是轉念一想tornado
時單線程的,擔心會有問題,然后就一直在糾結。
其實,編程不應該怕報錯或者崩潰,遇到問題解決問題這是提高自己的途徑,不應該對未知的感到擔心或害怕,多嘗試,大不了從頭寫過!