本文是WebSocket系列文章的第2篇,第1篇主要讲述概念原理。本文从实战角度介绍如何使用WebSocket。
本文实战项目来自Mastering WebSockets With Go。实现了一个精简版的Web聊天系统,前端采用 HTML+JS,后端用Go实现。注意:本文对原项目做了一点UI颜色调整
在本地构建部署后效果如下,先要登录后创建WebSocket连接,然后可以向聊天室发送信息。
下面是javascript创建WebSocket代码,直接通过构造函数 new WebSocket创建一个实例。
conn = new WebSocket("wss://" + document.location.host + "/ws?otp="+ otp);
// Onopen
conn.onopen = function (evt) {
document.getElementById("connection-header").innerHTML = "Connected to Websocket: true";
}
conn.onclose = function(evt) {
// Set disconnected
document.getElementById("connection-header").innerHTML = "Connected to Websocket: false";
}
// Add a listener to the onmessage event
conn.onmessage = function (evt) {
console.log(evt);
// parse websocket message as JSON
const eventData = JSON.parse(evt.data);
// Assign JSON data to new Event Object
const event = Object.assign(new Event, eventData);
// Let router manage message
routeEvent(event);
}
WebSocket接收一个URL参数,执行完下面语句后,就会与对应的host建立连接。
conn = new WebSocket("wss://" + document.location.host + "/ws?otp="+ otp)
WebSocket实例对象的onopen属性,在上面的连接建立成功后进行回调执行。
conn.onopen = function (evt) {
document.getElementById("connection-header").innerHTML = "Connected to Websocket: true";
}
这里会在连接成功后,页面显示 "Connected to Websocket: true"
收到来自服务端数据后的回调函数, 处理逻辑在routeEvent
.
conn.onmessage = function (evt) {
console.log(evt);
// parse websocket message as JSON
const eventData = JSON.parse(evt.data);
// Assign JSON data to new Event Object
const event = Object.assign(new Event, eventData);
// Let router manage message
routeEvent(event);
}
routeEvent先进行一些参数校验,然后构造messageEvent丢给appendChatMessage。
function routeEvent(event) {
if (event.type === undefined) {
alert("no 'type' field in event");
}
switch (event.type) {
case "new_message":
// Format payload
const messageEvent = Object.assign(new NewMessageEvent, event.payload);
appendChatMessage(messageEvent);
break;
default:
alert("unsupported message type");
break;
}
}
真正显示处理在appendChatMessage
函数。在消息前加上日期时间,并拼接到聊天室之前内容的尾部。
function appendChatMessage(messageEvent) {
var date = new Date(messageEvent.sent);
// format message
const formattedMsg = `${date.toLocaleString()}: ${messageEvent.message}`;
// Append Message
textarea = document.getElementById("chatmessages");
textarea.innerHTML = textarea.innerHTML + "\n" + formattedMsg;
textarea.scrollTop = textarea.scrollHeight;
}
WebSocket连接关闭后的回调函数。在页面上显示"Connected to Websocket: false"。
conn.onclose = function(evt) {
// Set disconnected
document.getElementById("connection-header").innerHTML = "Connected to Websocket: false";
}
send方法向服务端发送数据。这里把payload数据封装成Event json格式发送给后端。
function sendEvent(eventName, payload) {
// Create a event Object with a event named send_message
const event = new Event(eventName, payload);
// Format as JSON and send
conn.send(JSON.stringify(event));
}
服务端采用的是开源的 github.com/gorilla/websocket。是用Go语言实现的WebSocket库。该库简单易用、性能比较高。
调用websocket.Upgrader对象的Upgrade方法将普通的HTTP连接升级为WebSocket连接。函数签名如下,直接将http的w和r传递给它即可。
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error)
url路由为/ws即走WebSocket协议,可以看到serveWS中调用 conn, err := websocketUpgrader.Upgrade(w, r, nil)
将HTTP请求升级为WebSocket.
func (m *Manager) serveWS(w http.ResponseWriter, r *http.Request) {
otp := r.URL.Query().Get("otp")
if otp == "" {
w.WriteHeader(http.StatusUnauthorized)
return
}
if !m.otps.VerifyOTP(otp) {
w.WriteHeader(http.StatusUnauthorized)
return
}
log.Println("New connection")
conn, err := websocketUpgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
client := NewClient(conn, m)
m.addClient(client)
go client.readMessages()
go client.writeMessages()
}
调用ReadMessage读取客户端发送的数据,该方法返回3个参数,第一个参数表示读取的数据类型,第二参数是读取到的数据,第三个参数为error类型。
业务层面数据类型主要是 TextMessage 和 BinaryMessage,分别表示读取到数据是文本数据还是二进制数据。
调用WriteMessage向客户端发送数据,需要传入两个参数,第一个参数表示数据类型,这里传输的是文本数据。第二个参数传数据内容。
服务端用短短的几百行代码实现了一个完整的WebSocket服务器框架,实现结构图如下,兼具安全性和扩展性,值得借鉴模仿。
以下几点内容值得学习: