即時通訊WebSocket 和Socket.IO

WebSocket

HTML5定義了WebSocket協議,能更好的節省服務器資源和帶寬,而且可以更實時地進行通信。html

在2008年誕生,2011年成爲國際標準。python

如今基本全部瀏覽器都已經支持了。程序員

WebSocket是一種在單個TCP鏈接上進行全雙工通訊的協議。在WebSocket API中,瀏覽器和服務器只須要完成一次握手(不是指創建TCP鏈接的那個三次握手,是指在創建TCP鏈接後傳輸一次握手數據),二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。web

 

 

Websocket使用ws或wss的統一資源標誌符,相似於HTTPS,其中wss表示在TLS之上的Websocket。如:後端

ws://example.com/wsapi wss://secure.example.com/

Websocket使用和 HTTP 相同的 TCP 端口,能夠繞過大多數防火牆的限制。默認狀況下,Websocket協議使用80端口;運行在TLS之上時,默認使用443端口。api

 

握手協議

WebSocket 是獨立的、建立在 TCP 上的協議。 報文瀏覽器

Websocket 經過 HTTP/1.1 協議的101狀態碼進行握手。服務器

爲了建立Websocket鏈接,須要經過瀏覽器發出請求,以後服務器進行迴應,這個過程一般稱爲「握手」(handshaking)。websocket

一個典型的Websocket握手請求以下:多線程

客戶端請求

 
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

服務器迴應

 
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/
  • Connection必須設置Upgrade,表示客戶端但願鏈接升級。
  • Upgrade字段必須設置Websocket,表示但願升級到Websocket協議。
  • Sec-WebSocket-Key是隨機的字符串,服務器端會用這些數據來構造出一個SHA-1的信息摘要。把「Sec-WebSocket-Key」加上一個特殊字符串「258EAFA5-E914-47DA-95CA-C5AB0DC85B11」,而後計算SHA-1摘要,以後進行BASE-64編碼,將結果作爲「Sec-WebSocket-Accept」頭的值,返回給客戶端。如此操做,能夠儘可能避免普通HTTP請求被誤認爲Websocket協議。
  • Sec-WebSocket-Version 表示支持的Websocket版本。RFC6455要求使用的版本是13,以前草案的版本均應當棄用。
  • Origin字段是可選的,一般用來表示在瀏覽器中發起此Websocket鏈接所在的頁面,相似於Referer。可是,與Referer不一樣的是,Origin只包含了協議和主機名稱。
  • 其餘一些定義在HTTP協議中的字段,如Cookie等,也能夠在Websocket中使用。

優勢

  • 較少的控制開銷。在鏈接建立後,服務器和客戶端之間交換數據時,用於協議控制的數據包頭部相對較小。在不包含擴展的狀況下,對於服務器到客戶端的內容,此頭部大小隻有2至10字節(和數據包長度有關);對於客戶端到服務器的內容,此頭部還須要加上額外的4字節的掩碼。相對於HTTP請求每次都要攜帶完整的頭部,此項開銷顯著減小了。
  • 更強的實時性。因爲協議是全雙工的,因此服務器能夠隨時主動給客戶端下發數據。相對於HTTP請求須要等待客戶端發起請求服務端才能響應,延遲明顯更少;即便是和Comet等相似的長輪詢比較,其也能在短期內更屢次地傳遞數據。
  • 保持鏈接狀態。與HTTP不一樣的是,Websocket須要先建立鏈接,這就使得其成爲一種有狀態的協議,以後通訊時能夠省略部分狀態信息。而HTTP請求可能須要在每一個請求都攜帶狀態信息(如身份認證等)。 更好的二進制支持。Websocket定義了二進制幀,相對HTTP,能夠更輕鬆地處理二進制內容。
  • 能夠支持擴展。Websocket定義了擴展,用戶能夠擴展協議、實現部分自定義的子協議。如部分瀏覽器支持壓縮等。
  • 更好的壓縮效果。相對於HTTP壓縮,Websocket在適當的擴展支持下,能夠沿用以前內容的上下文,在傳遞相似的數據時,能夠顯著地提升壓縮率。
  • 沒有同源限制,客戶端能夠與任意服務器通訊。
  • 能夠發送文本,也能夠發送二進制數據。
 

 

 

Socket.IO

1 簡介

Socket.IO 本是一個面向實時 web 應用的 JavaScript 庫,如今已成爲擁有衆多語言支持的Web即時通信應用的框架。

Socket.IO 主要使用WebSocket協議。可是若是須要的話,Socket.io能夠回退到幾種其它方法,例如Adobe Flash Sockets,JSONP拉取,或是傳統的AJAX拉取,而且在同時提供徹底相同的接口。儘管它能夠被用做WebSocket的包裝庫,它仍是提供了許多其它功能,好比廣播至多個套接字,存儲與不一樣客戶有關的數據,和異步IO操做。

Socket.IO 不等價於 WebSocket,WebSocket只是Socket.IO實現即時通信的其中一種技術依賴,並且Socket.IO還在實現WebSocket協議時作了一些調整。

優勢:

Socket.IO 會自動選擇合適雙向通訊協議,僅僅須要程序員對套接字的概念有所瞭解。

有Python庫的實現,能夠在Python實現的Web應用中去實現IM後臺服務。

缺點:

Socket.io並非一個基本的、獨立的、可以回退到其它實時協議的WebSocket庫,它其實是一個依賴於其它實時傳輸協議的自定義實時傳輸協議的實現。該協議的協商部分使得支持標準WebSocket的客戶端不能直接鏈接到Socket.io服務器,而且支持Socket.io的客戶端也不能與非Socket.io框架的WebSocket或Comet服務器通訊。於是,Socket.io要求客戶端與服務器端均須使用該框架。

2 Python服務器端開發

安裝

 
 
pip install python-socketio
 

建立服務器

  • 方式1

    使用多進程多線程模式的WSGI服務器對接(如uWSGI、gunicorn)

     
       
      import socketio  
    
      # create a Socket.IO servers
      sio = socketio.Server()
    
      # 打包成WSGI應用,可使用WSGI服務器託管運行
      app = socketio.WSGIApp(sio)  # Flask  Django
     

    建立好app對象後,使用uWSGI、或gunicorn服務器運行此對象。

  • 方式2

    做爲Flask、Django 應用中的一部分

     
       
      from wsgi import app  # a Flask, Django, etc. application
      import socketio
    
      # create a Socket.IO server
      sio = socketio.Server()
    
      app = socketio.WSGIApp(sio, app)
     

    建立好app對象後,使用uWSGI、或gunicorn服務器運行此對象。

  • 方式3

    使用協程的方式運行 (推薦)

     
       
      import eventlet
      eventlet.monkey_patch()
    
      import socketio
      import eventlet.wsgi
    
      sio = socketio.Server(async_mode='eventlet')  # 指明在evenlet模式下
      app = socketio.Middleware(sio)
      eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
     
說明

由於服務器與客戶端進行即時通訊時,會盡量的使用長鏈接,因此若服務器採用多進程或多線程方式運行,受限於服務器能建立的進程或線程數,可以支持的併發鏈接客戶端不會很高,也就是服務器性能有限。採用協程方式運行服務器,能夠提高即時通訊服務器的性能。

事件處理

不一樣於HTTP服務的編寫方式,SocketIO服務編寫再也不以請求Request和響應Response來處理,而是對收發的數據以消息(message)來對待,收發的不一樣類別的消息數據又以事件(event)來區分。

本來HTTP服務編寫中處理請求、構造響應的視圖處理函數在SocketIO服務中改成編寫收發不一樣事件的事件處理函數。

1)事件處理方法

編寫事件處理方法,能夠接收指定的事件消息數據,並在處理方法中對消息數據進行處理。

 
 
@sio.on('connect')
def on_connect(sid, environ):
    """
    與客戶端創建好鏈接後被執行
    :param sid: string sid是socketio爲當前鏈接客戶端生成的識別id
    :param environ: dict 在鏈接握手時客戶端發送的握手數據(HTTP報文解析以後的字典)
    """
    pass

@sio.on('disconnect')
def on_disconnect(sid):
    """
    與客戶端斷開鏈接後被執行
    :param sid: string sid是斷開鏈接的客戶端id
    """
    pass

# 以字符串的形式表示一個自定義事件,事件的定義由先後端約定
@sio.on('my custom event')  
def my_custom_event(sid, data):
    """
    自定義事件消息的處理方法
    :param sid: string sid是發送此事件消息的客戶端id
    :param data: data是客戶端發送的消息數據
    """
    pass
 
注意
  • connect 爲特殊事件,當客戶端鏈接後自動執行
  • disconnect 爲特殊事件,當客戶端斷開鏈接後自動執行

  • connect、disconnect與自定義事件處理方法的函數傳入參數不一樣

2)發送事件消息

  • 羣發

     
       
    sio.emit('my event', {'data': 'foobar'})
     
  • 給指定用戶發送

     
       
    sio.emit('my event', {'data': 'foobar'}, room=user_sid)
     
  • 給一組用戶發送

    SocketIO提供了房間(room)來爲客戶端分組

    • sio.enter_room(sid, room_name)

      將鏈接的客戶端添加到一個room

       
           
        @sio.on('chat')
        def begin_chat(sid):
            sio.enter_room(sid, 'chat_users')
       

      注意:當客戶端鏈接後,socketio會自動將客戶端添加到以此客戶端sid爲名的room中

    • sio.leave_room(sid, room_name)

      將客戶端從一個room中移除

       
           
        @sio.on('exit_chat')
        def exit_chat(sid):
            sio.leave_room(sid, 'chat_users')
       
    • sio.rooms(sid)

      查詢sid客戶端所在的全部房間

      給一組用戶發送消息的示例

       
           
      @sio.on('my message')
      def message(sid, data):
        sio.emit('my reply', data, room='chat_users')
       

      也可在羣組發消息時跳過指定客戶端

       
           
      @sio.on('my message')
      def message(sid, data):
        sio.emit('my reply', data, room='chat_users', skip_sid=sid)
       
  • 使用send發送message事件消息

    對於'message'事件,可使用send方法

     
       
      sio.send({'data': 'foobar'})
      sio.send({'data': 'foobar'}, room=user_sid)

3 Python客戶端

 
import socketio

sio = socketio.Client()

@sio.on('connect')
def on_connect():
    pass

@sio.on('event')
def on_event(data):
    pass

sio.connect('http://10.211.55.7:8000')
sio.wait()
相關文章
相關標籤/搜索