socket、socketio、flask-socketio、WebSocket的區別與聯系
- socket 是通信的基礎,並不是一個協議,Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族和UDP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
- WebSocket 是html5新增加的一種通信協議,可以類比於http協議。常見的應用方式如彈幕、web在線游戲。
- socketio 是基於socket連接后(並沒有自己實現socket的鏈接而是復用了web框架或gevent、eventlet中的socket)對網絡輸入輸出流的處理,封裝了send、emit、namespace、asyncio 、訂閱等接口,同時擴展使用了redis、rabbitmq消息隊列的方式與其他進程通信。
- flask-socketio 是socketio對flask的適配,封裝了emit、send和關於room的操作。
select的鏈接、發送等底層操作還是在flask中做的,socketio對其做了抽象。使用threading模式時並沒有自己實現socket的鏈接而是復用了web框架的socket,也可以指定使用gevent和eventlet中的select多路復用已提高性能。
總結
HTTP、WebSocket 等應用層協議,都是基於 TCP 協議來傳輸數據的。我們可以把這些高級協議理解成對 TCP 的封裝。
既然大家都使用 TCP 協議,那么大家的連接和斷開,都要遵循 TCP 協議中的三次握手和四次揮手,只是在連接之后發送的內容不同,或者是斷開的時間不同。
對於 WebSocket 來說,它必須依賴 HTTP 協議進行一次握手 ,握手成功后,數據就直接從 TCP 通道傳輸,與 HTTP 無關了。
在源碼flask_socketio.SocketIO#run方法中可以看出 select 多路復用的幾種選擇。
if self.server.eio.async_mode == 'threading':
from werkzeug._internal import _log
_log('warning', 'WebSocket transport not available. Install '
'eventlet or gevent and gevent-websocket for '
'improved performance.')
app.run(host=host, port=port, threaded=True,
use_reloader=use_reloader, **kwargs)
# 這里的 app 就是 app = Flask(__name__)
elif self.server.eio.async_mode == 'eventlet':
def run_server():
import eventlet
import eventlet.wsgi
import eventlet.green
addresses = eventlet.green.socket.getaddrinfo(host, port)
if not addresses:
raise RuntimeError('Could not resolve host to a valid address')
eventlet_socket = eventlet.listen(addresses[0][4], addresses[0][0])
# If provided an SSL argument, use an SSL socket
ssl_args = ['keyfile', 'certfile', 'server_side', 'cert_reqs',
'ssl_version', 'ca_certs',
'do_handshake_on_connect', 'suppress_ragged_eofs',
'ciphers']
ssl_params = {k: kwargs[k] for k in kwargs if k in ssl_args}
if len(ssl_params) > 0:
for k in ssl_params:
kwargs.pop(k)
ssl_params['server_side'] = True # Listening requires true
eventlet_socket = eventlet.wrap_ssl(eventlet_socket,
**ssl_params)
eventlet.wsgi.server(eventlet_socket, app,
log_output=log_output, **kwargs)
if use_reloader:
run_with_reloader(run_server, extra_files=extra_files)
else:
run_server()
elif self.server.eio.async_mode == 'gevent':
from gevent import pywsgi
try:
from geventwebsocket.handler import WebSocketHandler
websocket = True
except ImportError:
websocket = False