您好,登錄后才能下訂單哦!
這篇文章主要講解了Python Tornado如何實現(xiàn)WEB服務器Socket服務器共存并實現(xiàn)交互,內(nèi)容清晰明了,對此有興趣的小伙伴可以學習一下,相信大家閱讀完之后會有幫助。
1、背景
最近有個項目,需要搭建一個socket服務器,一個web服務器,然后實現(xiàn)兩個服務器之間的通訊交互。剛開始的方案是用Python中socket模塊實現(xiàn)一個多線程的socket服務器,然后用Flask實現(xiàn)一個web服務器,他們之前通過線程交互實現(xiàn)通訊。
但是在我看來這個方案有例外一個更好的解決方法,就是用Torndao框架。鑒于網(wǎng)上用Tornado實現(xiàn)一個程序同時實現(xiàn)web服務和socket服務器并且實現(xiàn)交互的文章幾乎沒有,所以記錄一下。覺得寫得好麻煩點個贊,寫得不好請指出,有疑問可以留言。
2、準備
2.1、環(huán)境部署
2.2、目錄結(jié)構(gòu)
目錄結(jié)構(gòu)如上圖,這個目錄結(jié)構(gòu)包括文件命名只是我的個人習慣。其實目錄結(jié)構(gòu)不固定,只要合理就行。另外,原本項目是前后分離的只需要實現(xiàn)API接口,所以我這里就沒有涉及到HTML的東西。
3、服務器的實現(xiàn)
3.1、Socket服務器實現(xiàn)
socket服務器部分實現(xiàn)主要靠 Tornado中的TCPServer類
3.1.1、 導入類
socket_server.py:
from tornado.iostream import IOStream # 這句可以沒有,只是作為參數(shù)的代碼提示 from tornado.tcpserver import TCPServer
3.1.2、 構(gòu)建一個Connecter類
socket_server.py:
class Connecter: clients = set() # 存放連接的客戶端 async def init(self, stream: IOStream, address: tuple): """ 注意這個不是構(gòu)造方法,這里不用構(gòu)造方法是為了方便后續(xù)的與web端相互通信 """ self.stream, self.address = stream, address self.clients.add(self) print("{address} 上線!".format(address=address)) self.stream.set_close_callback(self.onClose) # 客戶端離線的時候會被調(diào)用 await self.receive() # 接受消息 async def receive(self): """ 接受消息 """ while True: try: # 因為異步的原因??赡茉O(shè)備離線后還在接收消息,所以try一下,不讓錯誤打印出來,其實打印了錯誤也不影響程序運行 data = await self.stream.read_bytes(num_bytes=1024, partial=True) # num_bytes:每次最多接收字節(jié),partial:數(shù)據(jù)中斷后視為接收完成 print(data) # TODO:接收到數(shù)據(jù)的處理 except: pass def send(self, data): """ 發(fā)送消息 :param data: 消息內(nèi)容 """ self.stream.write(bytes(data.encode('utf8'))) def onClose(self): """ 客戶端離線 """ print("{address} 離線!".format(address=self.address)) self.clients.remove(self) # 在clients內(nèi)刪掉該客戶端
3.1.3、 構(gòu)建一個SocketServer類
socket_server.py:
class SocketServer(TCPServer): # 需要繼承TCPServer這個類 async def handle_stream(self, stream: IOStream, address: tuple): # 實現(xiàn)類里面的handle_stream方法 await Connecter().init(stream, address) # 每次有客戶端連入都實例化一個Connecter類
3.2、Web服務器實現(xiàn)
3.2.1、 實現(xiàn)一個requestHandler
web_test.py:
from tornado.web import RequestHandler # 導入RequestHandler類 class TestApiHandler(RequestHandler): # 繼承RequestHandler類 def get(self): # 實現(xiàn)GET方法,GET請求會執(zhí)行這個方法 pass def post(self): # 實現(xiàn)POST方法,POST請求會執(zhí)行這個方法 pass
3.2.2、 實現(xiàn)web app
web_server.py:
from tornado.web import Application # 導入Tornado的Application類 from .src.web_test import TestApiHandler # 導入我們自己寫的TestApiHandler類 def webServerApp(): # 構(gòu)造出webApp return Application([ (r'/api_test/', TestApiHandler), # 把/api_test/路由到TestApiHandler ])
3.3、程序入口
3.3.1、 導入web_server和socket_server,還有導入tornado的ioloop
main.py:
from web_server.web_server import webServerApp from socket_server.socket_server import SocketServer from tornado import ioloop from tornado.options import define, options
3.3.2、 定義默認端口
main.py
#這里用define定義端口,可以方便使用命令行參數(shù)的形式修改端口 define("socketPort", 8888, type=int) # socket默認使用8888端口 define("webPort", 8080, type=int) # web默認使用8080端口
3.3.3、 啟動代碼
main.py
def main(): socket_server = SocketServer() socket_server.listen(options.socketPort, '0.0.0.0') print("socket服務器啟動,端口:{port}".format(port=options.socketPort)) app = webServerApp() app.listen(options.webPort, '0.0.0.0') print("web服務器啟動,端口:{port}".format(port=options.webPort)) ioloop.IOLoop.current().start() if __name__ == '__main__': main()
4、服務器運行效果
到此,一個混合型的socket+web服務器已經(jīng)搭建好了。我們我們運行main.py文件可以看到打印的信息,socket和web都正常運行。
我在這里簡單地寫了一個socket客戶端測試,代碼如下:
import socket import datetime class Client: def __init__(self): with socket.create_connection(("127.0.0.1", 8888)) as sock: while True: msg = sock.recv(1024) if len(msg) > 0: print(msg) sock.send(bytes(str(datetime.datetime.now).encode('utf8'))) msg = [] if __name__ == "__main__": Client()
可以看到tornado異步的形式實現(xiàn)了多客戶端同時接入socket。同時也可以測試web接口是正常的,如下圖:
5、Web服務器與Socket服務器交互
重點來了,web和socket怎樣實現(xiàn)交互呢?其實很簡單。
5.2、 web >> socket
web_test.py -> TestApiHandler -> post:
5.2.1、 導入Connecter類
from socket_server.socket_server import Connecter
5.2.2、 實現(xiàn)請求接口發(fā)送消息到socket客戶端
def post(self): # 實現(xiàn)POST方法,POST請求會執(zhí)行這個方法 msg = self.get_argument("msg") # 得到post請求中的msg的值 ip = self.get_argument('ip') # 得到要發(fā)送的ip c = Connecter() # 實例化Connecter類 counter = 0 # 記錄發(fā)送到客戶端的個數(shù) for client in c.clients: # type:Connecter if client.address[0] == ip: # 根據(jù)ip發(fā)送 client.send(msg) # 發(fā)送消息 counter += 1 # 計數(shù)加1 self.write("{'send_counter':" + str(counter) + "}")
5.2.3、 效果
請求接口可以返回數(shù)據(jù),已經(jīng)成功發(fā)送一個客戶端
客戶端也能收到消息:
5.1、 socket >> web
其實socket發(fā)送的消息讓web馬上收到消息是不太現(xiàn)實的,但是我們可以把數(shù)據(jù)保存起來(可以是數(shù)據(jù)庫、全局變量、緩存……),然后通過api接口再把數(shù)據(jù)取出。另外還有一種方法是通過socket和websocket進行交互通訊,這種方法是推薦的方法,同樣的也可以用Tornado去實現(xiàn),感興趣可以去研究一下也很簡單。如何有需要我提供socket、websocket、web三個端都互相交互的例子可以留言。
這里為了簡單一點,我使用一個類作為全局變量來保存數(shù)據(jù),然后用接口訪問,拿出這個類的值來演示一下效果。
5.1.1、 聲明類作為全局變量
socket_data_processing.py
class SocketData: msg = ""
5.1.2、 接受到的消息保存到這個類里面的msg
socket_server.py -> Connecter -> receive
async def receive(self): """ 接受消息 """ while True: try: # 因為異步的原因??赡茉O(shè)備離線后還在接收消息,所以try一下,不讓錯誤打印出來,其實打印了錯誤也不影響程序運行 data = await self.stream.read_bytes(num_bytes=1024, partial=True) # num_bytes:每次最多接收字節(jié),partial:數(shù)據(jù)中斷后視為接收完成 print(data) from .src.socket_data_processing import SocketData SocketData.msg = data.decode('utf8') except: pass
5.1.3、 用get方法顯示socket顯示回來的數(shù)據(jù)
web_test.py -> TestApiHandler -> get:
def get(self): # 實現(xiàn)GET方法,GET請求會執(zhí)行這個方法 from socket_server.src.socket_data_processing import SocketData self.write(SocketData.msg)
5.1.4、 效果
可以看到,從socket傳過來的字符串,被我通過Api讀取到了。
看完上述內(nèi)容,是不是對Python Tornado如何實現(xiàn)WEB服務器Socket服務器共存并實現(xiàn)交互有進一步的了解,如果還想學習更多內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。