Browse Source

gnet gateway

master
tangmingyou 3 years ago
parent
commit
ab143b0c3f
  1. 6
      go.mod
  2. 13
      go.sum
  3. 161
      internal/gateway_ws/gnet_server/codec.go
  4. 23
      internal/gateway_ws/gnet_server/gnet_conn.go
  5. 184
      internal/gateway_ws/gnet_server/websocket.go
  6. 4
      internal/gateway_ws/gws_server/conn_handler.go

6
go.mod

@ -6,12 +6,14 @@ require (
github.com/bytedance/sonic v1.10.2
github.com/dsnet/golib/unitconv v1.0.2
github.com/gin-gonic/gin v1.9.1
github.com/gobwas/ws v1.3.2
github.com/golang/protobuf v1.5.3
github.com/google/uuid v1.4.0
github.com/gorilla/websocket v1.5.1
github.com/jhump/protoreflect v1.15.4
github.com/lxzan/gws v1.7.0
github.com/nats-io/nats.go v1.31.0
github.com/panjf2000/gnet/v2 v2.3.4
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/redis/go-redis/v9 v9.4.0
github.com/sirupsen/logrus v1.9.3
@ -39,6 +41,8 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
@ -65,6 +69,7 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.11 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
@ -80,5 +85,6 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

13
go.sum

@ -46,6 +46,12 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@ -106,6 +112,9 @@ github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/panjf2000/ants/v2 v2.8.2 h1:D1wfANttg8uXhC9149gRt1PDQ+dLVFjNXkCEycMcvQQ=
github.com/panjf2000/gnet/v2 v2.3.4 h1:+ASHt+Wxr0KIzlk5FsLBbegCc4US7iVCdZ1QbUyw17g=
github.com/panjf2000/gnet/v2 v2.3.4/go.mod h1:0mTLWq4zMEXyQ35BY094dNWYnXfIdDg0mOlmZJflaXE=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
@ -150,6 +159,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@ -240,6 +251,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

161
internal/gateway_ws/gnet_server/codec.go

@ -0,0 +1,161 @@
package gnet_server
import (
"bytes"
"fmt"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"github.com/panjf2000/gnet/v2"
"github.com/panjf2000/gnet/v2/pkg/logging"
"io"
)
type wsCodec struct {
upgraded bool // 链接是否升级
buf bytes.Buffer // 从实际socket中读取到的数据缓存
wsMsgBuf wsMessageBuf // ws 消息缓存
}
type wsMessageBuf struct {
firstHeader *ws.Header
curHeader *ws.Header
cachedBuf bytes.Buffer
}
type readWrite struct {
io.Reader
io.Writer
}
func (w *wsCodec) upgrade(c gnet.Conn) (ok bool, action gnet.Action) {
if w.upgraded {
ok = true
return
}
buf := &w.buf
tmpReader := bytes.NewReader(buf.Bytes())
oldLen := tmpReader.Len()
logging.Infof("do Upgrade")
hs, err := ws.Upgrade(readWrite{tmpReader, c})
skipN := oldLen - tmpReader.Len()
if err != nil {
if err == io.EOF || err == io.ErrUnexpectedEOF { //数据不完整
return
}
buf.Next(skipN)
logging.Infof("conn[%v] [err=%v]", c.RemoteAddr().String(), err.Error())
action = gnet.Close
return
}
buf.Next(skipN)
logging.Infof("conn[%v] upgrade websocket protocol! Handshake: %v", c.RemoteAddr().String(), hs)
if err != nil {
logging.Infof("conn[%v] [err=%v]", c.RemoteAddr().String(), err.Error())
action = gnet.Close
return
}
ok = true
w.upgraded = true
return
}
func (w *wsCodec) readBufferBytes(c gnet.Conn) gnet.Action {
size := c.InboundBuffered()
buf := make([]byte, size, size)
read, err := c.Read(buf)
if err != nil {
logging.Infof("read err! %w", err)
return gnet.Close
}
if read < size {
logging.Infof("read bytes len err! size: %d read: %d", size, read)
return gnet.Close
}
w.buf.Write(buf)
return gnet.None
}
func (w *wsCodec) Decode(c gnet.Conn) (outs []wsutil.Message, err error) {
fmt.Println("do Decode")
messages, err := w.readWsMessages()
if err != nil {
logging.Infof("Error reading message! %v", err)
return nil, err
}
if messages == nil || len(messages) <= 0 { //没有读到完整数据 不处理
return
}
for _, message := range messages {
if message.OpCode.IsControl() {
err = wsutil.HandleClientControlMessage(c, message)
if err != nil {
return
}
continue
}
if message.OpCode == ws.OpText || message.OpCode == ws.OpBinary {
outs = append(outs, message)
}
}
return
}
func (w *wsCodec) readWsMessages() (messages []wsutil.Message, err error) {
msgBuf := &w.wsMsgBuf
in := &w.buf
for {
if msgBuf.curHeader == nil {
if in.Len() < ws.MinHeaderSize { //头长度至少是2
return
}
var head ws.Header
if in.Len() >= ws.MaxHeaderSize {
head, err = ws.ReadHeader(in)
if err != nil {
return messages, err
}
} else { //有可能不完整,构建新的 reader 读取 head 读取成功才实际对 in 进行读操作
tmpReader := bytes.NewReader(in.Bytes())
oldLen := tmpReader.Len()
head, err = ws.ReadHeader(tmpReader)
skipN := oldLen - tmpReader.Len()
if err != nil {
if err == io.EOF || err == io.ErrUnexpectedEOF { //数据不完整
return messages, nil
}
in.Next(skipN)
return nil, err
}
in.Next(skipN)
}
msgBuf.curHeader = &head
err = ws.WriteHeader(&msgBuf.cachedBuf, head)
if err != nil {
return nil, err
}
}
dataLen := (int)(msgBuf.curHeader.Length)
if dataLen > 0 {
if in.Len() >= dataLen {
_, err = io.CopyN(&msgBuf.cachedBuf, in, int64(dataLen))
if err != nil {
return
}
} else { //数据不完整
fmt.Println(in.Len(), dataLen)
logging.Infof("incomplete data")
return
}
}
if msgBuf.curHeader.Fin { //当前 header 已经是一个完整消息
messages, err = wsutil.ReadClientMessage(&msgBuf.cachedBuf, messages)
if err != nil {
return nil, err
}
msgBuf.cachedBuf.Reset()
} else {
logging.Infof("The data is split into multiple frames")
}
msgBuf.curHeader = nil
}
}

23
internal/gateway_ws/gnet_server/gnet_conn.go

@ -0,0 +1,23 @@
package gnet_server
import (
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"github.com/panjf2000/gnet/v2"
)
type gnetConn struct {
conn gnet.Conn
uid string
wsCodec *wsCodec
}
func newGnetConn(conn gnet.Conn) *gnetConn {
return &gnetConn{
conn: conn,
}
}
func (c *gnetConn) Write(msg []byte) error {
return wsutil.WriteServerMessage(c.conn, ws.OpBinary, msg)
}

184
internal/gateway_ws/gnet_server/websocket.go

@ -0,0 +1,184 @@
package gnet_server
import (
"context"
"flag"
"fmt"
"github.com/gobwas/ws"
"log"
"sonet/pkg/protocol"
"sonet/pkg/protocol/session"
"sonet/pkg/utils/logger"
"sync/atomic"
"time"
"github.com/panjf2000/gnet/v2"
"github.com/panjf2000/gnet/v2/pkg/logging"
)
type GnetWsServer struct {
gnet.BuiltinEventEngine
addr string
multicore bool
eng gnet.Engine
connected int64
requests chan *protocol.Payload
}
func NewGnetWsServer() *GnetWsServer {
return &GnetWsServer{
requests: make(chan *protocol.Payload, 1024*16),
}
}
func (wss *GnetWsServer) Init() {
}
// dispatch 多个 goroutine 进行请求 dispatch
func (wss *GnetWsServer) dispatch(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case payload := <-wss.requests:
fmt.Println(payload.Header)
}
}
}
func (wss *GnetWsServer) OnBoot(eng gnet.Engine) gnet.Action {
wss.eng = eng
logging.Infof("echo server with multi-core=%t is listening on %s", wss.multicore, wss.addr)
return gnet.None
}
func (wss *GnetWsServer) OnOpen(c gnet.Conn) ([]byte, gnet.Action) {
// wsCodec, uid, gnetConn
conn := newGnetConn(c)
conn.wsCodec = new(wsCodec)
c.SetContext(conn)
// c.SetContext(new(wsCodec))
atomic.AddInt64(&wss.connected, 1)
return nil, gnet.None
}
func (wss *GnetWsServer) OnClose(c gnet.Conn, err error) (action gnet.Action) {
if err != nil {
logging.Warnf("error occurred on connection=%s, %v\n", c.RemoteAddr().String(), err)
}
atomic.AddInt64(&wss.connected, -1)
logging.Infof("conn[%v] disconnected", c.RemoteAddr().String())
return gnet.None
}
func (wss *GnetWsServer) OnTraffic(c gnet.Conn) (action gnet.Action) {
conn := c.Context().(*gnetConn)
if conn.wsCodec.readBufferBytes(c) == gnet.Close {
return gnet.Close
}
ok, action := conn.wsCodec.upgrade(c)
if !ok {
return
}
if conn.wsCodec.buf.Len() <= 0 {
return gnet.None
}
messages, err := conn.wsCodec.Decode(c)
if err != nil {
return gnet.Close
}
if messages == nil {
return
}
authorized := conn.uid != ""
for _, message := range messages {
if message.OpCode != ws.OpBinary {
logger.Info("receive message type: ", message.OpCode)
continue
}
// decode payload
payload, err := protocol.DecodeSo(message.Payload)
if err != nil {
logger.Errorf("decode message error: len=%d", len(message.Payload), err)
return gnet.Close
}
header := payload.Header
service, method := header.Svc, header.Target
// authorization
if !authorized {
if !(service == "Auth" && method == "Verify") {
writeError(conn, header.SeqId, session.UnauthorizedRequestError.Error())
return gnet.Close
}
}
// payload -> processing channel
select {
case wss.requests <- payload:
default:
writeError(conn, header.SeqId, "server busy")
}
//msgLen := len(message.Payload)
//if msgLen > 128 {
// logging.Infof("conn[%v] receive [op=%v] [msg=%v..., len=%d]", c.RemoteAddr().String(), message.OpCode, string(message.Payload[:128]), len(message.Payload))
//} else {
// logging.Infof("conn[%v] receive [op=%v] [msg=%v, len=%d]", c.RemoteAddr().String(), message.OpCode, string(message.Payload), len(message.Payload))
//}
//// This is the echo server
//err = wsutil.WriteServerMessage(c, message.OpCode, message.Payload)
//if err != nil {
// logging.Infof("conn[%v] [err=%v]", c.RemoteAddr().String(), err.Error())
// return gnet.Close
//}
}
return gnet.None
}
func (wss *GnetWsServer) OnTick() (delay time.Duration, action gnet.Action) {
logging.Infof("[connected-count=%v]", atomic.LoadInt64(&wss.connected))
return 10 * time.Second, gnet.None
}
func writeError(conn session.NetConn, seqId int32, errMsg string) {
header := &protocol.Header{}
payload := &protocol.Payload{Header: header}
header.Magic = protocol.Magic
header.Type = protocol.TypeError
header.Status = 50
header.SeqId = seqId
payload.Body = []byte(errMsg)
message, err := protocol.EncodeSo(payload)
if err != nil {
logger.Error("NetClient writeError encode error: ", err)
return
}
err = conn.Write(message)
if err != nil {
logger.Error("net conn write error msg fail: ", err)
return
}
return
}
func main() {
var port int
var multicore bool
// Example command: go run main.go --port 8080 --multicore=true
flag.IntVar(&port, "port", 9080, "server port")
flag.BoolVar(&multicore, "multicore", true, "multicore")
flag.Parse()
wss := &GnetWsServer{addr: fmt.Sprintf("tcp://127.0.0.1:%d", port), multicore: multicore}
// Start serving!
log.Println("server exits:", gnet.Run(wss, wss.addr, gnet.WithMulticore(multicore), gnet.WithReusePort(true), gnet.WithTicker(true)))
}

4
internal/gateway_ws/gws_server/conn_handler.go

@ -23,18 +23,16 @@ const (
)
type GwsHandler struct {
postalServerAddress string
grpcFactory *generic.GrpcGenericClientFactory
sessionStore session.Store // <string, *session.NetSubject> 当前连接用户,内存缓存
}
func NewGwsHandler(postalServerAddress string,
func NewGwsHandler(
grpcFactory *generic.GrpcGenericClientFactory,
sessionStore session.Store,
) *GwsHandler {
return &GwsHandler{
grpcFactory: grpcFactory,
postalServerAddress: postalServerAddress,
sessionStore: sessionStore,
}
}

Loading…
Cancel
Save