golang微服务结构Kratos完结Websocket谈天室
什么是WebSocket
WebSocket 协议主要为了处理根据 HTTP/1.x 的 Web 运用无法完结服务端向客户端主动推送的问题, 为了兼容现有的设施, WebSocket 协议运用与 HTTP 协议相同的端口, 并运用 HTTP Upgrade 机制来进行 WebSocket 握手, 当握手完结之后, 通讯两边便能够依照 WebSocket 协议的办法进行交互
WebSocket 运用 TCP 作为传输层协议, 与 HTTP 相似, WebSocket 也支持在 TCP 上层引入 TLS 层, 以建立加密数据传输通道, 即 WebSocket over TLS, WebSocket 的 URI 与 HTTP URI 的结构相似, 关于运用 80 端口的 WebSocket over TCP, 其 URI 的一般方式为 ws://host:port/path/query
关于运用 443 端口的 WebSocket over TLS, 其 URI 的一般方式为 wss://host:port/path/query
在 WebSocket 协议中, 帧 (frame) 是通讯两边数据传输的基本单元, 与其它网络协议相同, frame 由 Header 和 Payload 两部分构成, frame 有多种类型, frame 的类型由其头部的 Opcode 字段 (将在下面评论) 来指示, WebSocket 的 frame 能够分为两类, 一类是用于传输操控信息的 frame (如告诉对方关闭 WebSocket 衔接), 一类是用于传输运用数据的 frame, 运用 WebSocket 协议通讯的两边都需求首要进行握手, 只有当握手成功之后才开端运用 frame 传输数据
如何在Kratos下开发Websocket服务器
我根据 github.com/gorilla/web… 封装了一个简略的Websocket服务器,能够在Kratos下开发Websocket服务器。详细完结代码在:github.com/tx7do/krato…
能够根据它开发,也能够fork代码自己根据需求进行修正。
本篇文章适当于是一个echo示例。也便是收发信息。
开端写代码
界说API
syntax = "proto3";
package chatroom.v1;
option go_package = "api/chatroom/v1;v1";
service ChatRoomService {
}
enum MessageType {
Chat = 0;
}
message ChatMessage {
string message = 1;
string sender = 2;
string timestamp = 3;
}
现在ChatRoomService
暂时只是用来占位,后续完结代码生成器插件时,或许能够派上用场。
MessageType
是一个Opcode,用于区别音讯类型,它是一个Uint32类型,不能够有重复数值。
ChatMessage
是网络协议的载体界说,它和MessageType.Chat
是一对。
注册Websocket服务器
func NewWebsocketServer(c *conf.Server, _ log.Logger, svc *service.ChatRoomService) *websocket.Server {
srv := websocket.NewServer(
websocket.WithAddress(c.Websocket.Addr),
websocket.WithPath(c.Websocket.Path),
websocket.WithConnectHandle(svc.OnWebsocketConnect),
websocket.WithCodec(encoding.GetCodec("json")),
)
svc.SetWebsocketServer(srv)
srv.RegisterMessageHandler(websocket.MessageType(v1.MessageType_Chat),
func(sessionId websocket.SessionID, payload websocket.MessagePayload) error {
switch t := payload.(type) {
case *v1.ChatMessage:
return svc.OnChatMessage(sessionId, t)
default:
return errors.New("invalid payload type")
}
},
func() websocket.Any { return &v1.ChatMessage{} },
)
return srv
}
需求留意的是websocket.WithCodec
是注册编解码器,这里运用的是json编解码器。通常来说,大约也就Json和Protobuf两种编解码器用的会比较多。注册进去之后,底层会主动的将数据编解码。
处理音讯
func (s *ChatRoomService) OnChatMessage(sessionId websocket.SessionID, msg *v1.ChatMessage) error {
s.ws.Broadcast(websocket.MessageType(v1.MessageType_Chat), msg)
//s.ws.SendMessage(sessionId, websocket.MessageType(v1.MessageType_Chat), msg)
return nil
}
websocket.SessionID
本质上是一个String类型的UUID,用于标识一个衔接。
用于发送音讯的办法有两个:s.ws.SendMessage
和s.ws.Broadcast
,前者只发送给指定的SessionID,后者发送给所有的SessionID。
这样,服务器就算搭起来了,是不是很简略。
完结JavaScript客户端
Js要完结一个websocket客户端是很简略的,只需求短短十数行代码:
var ws = new WebSocket("wss://echo.websocket.org");
ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
ws.onclose = function(evt) {
console.log("Connection closed.");
};
可是,因为我在Kratos-Transport的Websocket底层完结里边封装了一个简略的运用层协议。故而在完结Websocket的Js客户端的时分,需求完结该运用层协议的编解码。
其实要说起来这个协议的界说,也是很简略的:
音讯类型(4字节) | 包载体
别的,需求说到一点:假如Websocket协议的完结是依照完整的Websocket的RFC文档界说来完结的话,Websocket协议现已完结了分包、粘包的处理,所以在运用层就不需求考虑这些问题了。否则假如在TCP/UDP开端封装协议的话,或者没有彻底完结RFC文档,那就要杂乱太多了。
编码
function sendMessage(id, payload) {
const strPayload = JSON.stringify(payload);
const payloadBuff = new TextEncoder().encode(strPayload);
let buff = new Uint8Array(4 + payloadBuff.byteLength);
let dv = new DataView(buff.buffer);
dv.setInt32(0, id);
buff.set(payloadBuff, 4);
console.log(ab2str(buff))
ws.send(dv.buffer);
}
解码
ws.onmessage = function (event) {
const dv = new DataView(event.data);
const messageType = dv.getInt32(0);
handleMessage(messageType, event.data.slice(4));
};
引荐运用TypeScript,代码看起来更加清爽一些。
假如载体为Json编码,网上有工具能够将Protobuf协议生成TypeScript代码:brandonxiang.github.io/pb-to-types…
转换后的代码是这样的:
export enum MessageType {
Chat = 0,
}
export interface ChatMessage {
message?: string;
sender?: string;
timestamp?: string;
}
虽然说JS对类型并没有太多的束缚,可是实际上,强规约还是会带来许多的优点的。特别是在多人协作的时分,让每一个人都能够充分的了解协议的含义。
现在我们就能够来发送谈天音讯了:
function sendChatMessage(message) {
let packet = {
message: message,
sender: "",
timestamp: "",
};
sendMessage(MessageType.Chat, packet);
}
但假如运用Protobuf的二进制编码,那需求做的事情相对就比较多一点。我在此就不再赘述。
实例代码
- github.com/tx7do/krato…
- github.com/go-kratos/e…
参考资料
- RFC 6455 – The WebSocket Protocol
- wikipedia – WebSocket
- HTML5 WebSocket
- MDN – WebSocket
- WebSocket 协议解析 [RFC 6455]
- WebSocket 教程