1. 程式人生 > 程式設計 >Python Tornado實現WEB伺服器Socket伺服器共存並實現互動的方法

Python Tornado實現WEB伺服器Socket伺服器共存並實現互動的方法

1、背景

最近有個專案,需要搭建一個socket伺服器,一個web伺服器,然後實現兩個伺服器之間的通訊互動。剛開始的方案是用Python中socket模組實現一個多執行緒的socket伺服器,然後用Flask實現一個web伺服器,他們之前通過執行緒互動實現通訊。
但是在我看來這個方案有例外一個更好的解決方法,就是用Torndao框架。鑑於網上用Tornado實現一個程式同時實現web服務和socket伺服器並且實現互動的文章幾乎沒有,所以記錄一下。覺得寫得好麻煩點個贊,寫得不好請指出,有疑問可以留言。

2、準備

2.1、環境部署

  • Python3.x
  • pip3 install Tornado

2.2、目錄結構

Python Tornado實現WEB伺服器Socket伺服器共存並實現互動的方法

目錄結構如上圖,這個目錄結構包括檔案命名只是我的個人習慣。其實目錄結構不固定,只要合理就行。另外,原本專案是前後分離的只需要實現API介面,所以我這裡就沒有涉及到HTML的東西。

3、伺服器的實現

3.1、Socket伺服器實現

socket伺服器部分實現主要靠 Tornado中的TCPServer類

3.1.1、 匯入類

socket_server.py:

from tornado.iostream import IOStream  # 這句可以沒有,只是作為引數的程式碼提示
from tornado.tcpserver import TCPServer

3.1.2、 構建一個Connecter類

socket_server.py:

class Connecter:

  clients = set()   # 存放連線的客戶端

  async def init(self,stream: IOStream,address: tuple):
    """
    注意這個不是構造方法,這裡不用構造方法是為了方便後續的與web端相互通訊
    """
    self.stream,self.address = stream,address
    self.clients.add(self)
    print("{address} 上線!".format(address=address))
    self.stream.set_close_callback(self.onClose) # 客戶端離線的時候會被呼叫
    await self.receive()    # 接受訊息

  async def receive(self):
    """
    接受訊息
    """
    while True:
      try:  # 因為非同步的原因。可能裝置離線後還在接收訊息,所以try一下,不讓錯誤打印出來,其實列印了錯誤也不影響程式執行
        data = await self.stream.read_bytes(num_bytes=1024,partial=True)  # num_bytes:每次最多接收位元組,partial:資料中斷後視為接收完成
        print(data)
        # TODO:接收到資料的處理
      except: 
        pass

  def send(self,data):
    """
    傳送訊息
    :param data: 訊息內容
    """
    self.stream.write(bytes(data.encode('utf8')))

  def onClose(self):
    """
    客戶端離線
    """
    print("{address} 離線!".format(address=self.address))
    self.clients.remove(self) # 在clients內刪掉該客戶端

3.1.3、 構建一個SocketServer類

socket_server.py:

class SocketServer(TCPServer):   # 需要繼承TCPServer這個類
  async def handle_stream(self,address: tuple):  # 實現類裡面的handle_stream方法
    await Connecter().init(stream,address)   # 每次有客戶端連入都例項化一個Connecter類

3.2、Web伺服器實現

3.2.1、 實現一個requestHandler

web_test.py:

from tornado.web import RequestHandler   # 匯入RequestHandler類


class TestApiHandler(RequestHandler):    # 繼承RequestHandler類

  def get(self):   # 實現GET方法,GET請求會執行這個方法
    pass

  def post(self):   # 實現POST方法,POST請求會執行這個方法
    pass

3.2.2、 實現web app

web_server.py:

from tornado.web import Application     # 匯入Tornado的Application類
from .src.web_test import TestApiHandler  # 匯入我們自己寫的TestApiHandler類


def webServerApp():   # 構造出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定義埠,可以方便使用命令列引數的形式修改埠
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伺服器已經搭建好了。我們我們執行main.py檔案可以看到列印的資訊,socket和web都正常執行。

Python Tornado實現WEB伺服器Socket伺服器共存並實現互動的方法

我在這裡簡單地寫了一個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()

Python Tornado實現WEB伺服器Socket伺服器共存並實現互動的方法

可以看到tornado非同步的形式實現了多客戶端同時接入socket。同時也可以測試web介面是正常的,如下圖:

Python Tornado實現WEB伺服器Socket伺服器共存並實現互動的方法

5、Web伺服器與Socket伺服器互動

重點來了,web和socket怎樣實現互動呢?其實很簡單。

5.2、 web >> socket

web_test.py -> TestApiHandler -> post:

5.2.1、 匯入Connecter類

from socket_server.socket_server import Connecter

5.2.2、 實現請求介面傳送訊息到socket客戶端

 def post(self):   # 實現POST方法,POST請求會執行這個方法
    msg = self.get_argument("msg") # 得到post請求中的msg的值
    ip = self.get_argument('ip')  # 得到要傳送的ip
    c = Connecter()   # 例項化Connecter類
    counter = 0   # 記錄傳送到客戶端的個數
    for client in c.clients:  # type:Connecter
      if client.address[0] == ip:   # 根據ip傳送
        client.send(msg)  # 傳送訊息
        counter += 1    # 計數加1
    self.write("{'send_counter':" + str(counter) + "}")

5.2.3、 效果

請求介面可以返回資料,已經成功傳送一個客戶端

Python Tornado實現WEB伺服器Socket伺服器共存並實現互動的方法

客戶端也能收到訊息:

Python Tornado實現WEB伺服器Socket伺服器共存並實現互動的方法

5.1、 socket >> web

其實socket傳送的訊息讓web馬上收到訊息是不太現實的,但是我們可以把資料儲存起來(可以是資料庫、全域性變數、快取……),然後通過api介面再把資料取出。另外還有一種方法是通過socket和websocket進行互動通訊,這種方法是推薦的方法,同樣的也可以用Tornado去實現,感興趣可以去研究一下也很簡單。如何有需要我提供socket、websocket、web三個端都互相互動的例子可以留言。
這裡為了簡單一點,我使用一個類作為全域性變數來儲存資料,然後用介面訪問,拿出這個類的值來演示一下效果。

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:  # 因為非同步的原因。可能裝置離線後還在接收訊息,所以try一下,不讓錯誤打印出來,其實列印了錯誤也不影響程式執行
        data = await self.stream.read_bytes(num_bytes=1024,partial=True)  # num_bytes:每次最多接收位元組,partial:資料中斷後視為接收完成
        print(data)
        from .src.socket_data_processing import SocketData
        SocketData.msg = data.decode('utf8')
      except:
        pass

5.1.3、 用get方法顯示socket顯示回來的資料

web_test.py -> TestApiHandler -> get:

 def get(self):   # 實現GET方法,GET請求會執行這個方法
    from socket_server.src.socket_data_processing import SocketData
    self.write(SocketData.msg)

5.1.4、 效果

Python Tornado實現WEB伺服器Socket伺服器共存並實現互動的方法

可以看到,從socket傳過來的字串,被我通過Api讀取到了。

6、完整程式碼GitHub:https://github.com/JohnDoe1996/socket-web

到此這篇關於Python Tornado實現WEB伺服器Socket伺服器共存並實現互動的方法的文章就介紹到這了,更多相關Python WEB伺服器Socket伺服器共存互動內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!