Websocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API 中,浏览器和服务器只需要完成一次握手,浏览器和服务器之间就形成了一条快速通道, 两者之间就直接可以创建持久性的连接,并进行双向数据传输
1、 websocket与http的区别
同:
- 建立在TCP之上,通过TCP协议来传输数据。
- 都是可靠性传输协议。
- 都是应用层协议。
异:
- WebSocket是HTML5中的协议,支持持久连接,HTTP不支持持久连接
- HTTP是单向协议,只能由客户端发起,做不到服务器主动向客户端推送信息。
2、为何使用Websocket
1)推送服务
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
2)大量前后端请求
面对大量长时间相同需求的请求,长连接可以减少每次请求所需时间,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
3、连接的创立
为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息Upgrade: WebSocket
表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
4、代码实现
前端
var ws = new WebSocket("ws://localhost:5000/api/ocr/ws"); # 建立连接
ws.send() # 发送
ws.receive() # 接收
后端
FastApi
由于fastapi的天生支持,实现一个websocket很简单
# fastapi websocket 内只能使用异步io
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message text was: {data}")
Flask
Flask本身不支持,但是可以借助第三方模块来实现(flask_sockets
)
from gevent import monkey
monkey.patch_all()
from flask import Flask
from gevent import pywsgi
from flask_sockets import Sockets
app = Flask(__name__)
sockets = Sockets(app)
@sockets.route('/test') # 指定路由
def echo_socket(ws):
while not ws.closed:
ws.send("hello") # 回传给clicent
message = ws.receive() # 接收到消息
if message is not None:
ws.send(json.dumps(message)) # 回传给clicent
else:
ws.send("failed")
if __name__ == "__main__":
from geventwebsocket.handler import WebSocketHandler
host = "0.0.0.0"
logger.info("gevent server staring on http://%s:%s" % (host, PORT))
WSGIServer((host, int(PORT)), flask_app, handler_class=WebSocketHandler).serve_forever()
readygo(gin)
gin也没有封装websocket相关内容,所以使用时需要借助github.com/gorilla/websocket
package echo
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"go.uber.org/zap"
)
var upgrader = websocket.Upgrader{}
func GetConn(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error) {
// 获取 websocket 长连接
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return nil, err
}
return c, nil
}
func Echo(c *gin.Context) {
conn, err := GetConn(c.Writer, c.Request)
if err != nil {
return
}
defer conn.Close()
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
break
}
// 根据接收数据类型确定信息是否正确
var resp []byte
switch messageType {
case websocket.TextMessage:
fmt.Printf("TextMessage: %s\n", message)
response = message
break
default:
fmt.Println("Unsupport messageType")
break
}
// 发送信息
err = conn.WriteMessage(messageType, response)
if err != nil {
break
}
}
}