first commit
This commit is contained in:
254
net/clientconn.go
Normal file
254
net/clientconn.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-think/openssl"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/gorilla/websocket"
|
||||
"log/slog"
|
||||
"slgserver/constant"
|
||||
"slgserver/util"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 客户端连接
|
||||
type ClientConn struct {
|
||||
wsSocket *websocket.Conn // 底层websocket
|
||||
isClosed bool
|
||||
Seq int64
|
||||
onClose func(conn*ClientConn)
|
||||
onPush func(conn*ClientConn, body*RspBody)
|
||||
//链接属性
|
||||
property map[string]interface{}
|
||||
//保护链接属性修改的锁
|
||||
propertyLock sync.RWMutex
|
||||
syncCtxs map[int64]*syncCtx
|
||||
syncLock sync.RWMutex
|
||||
handshakeChan chan bool
|
||||
handshake bool
|
||||
}
|
||||
|
||||
func NewClientConn(wsSocket *websocket.Conn) *ClientConn {
|
||||
conn := &ClientConn{
|
||||
wsSocket: wsSocket,
|
||||
isClosed: false,
|
||||
property: make(map[string]interface{}),
|
||||
Seq: 0,
|
||||
syncCtxs: make(map[int64]*syncCtx),
|
||||
handshakeChan: make(chan bool),
|
||||
}
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
func (this *ClientConn) waitHandshake() bool{
|
||||
if this.handshake == false{
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
select {
|
||||
case _ = <-this.handshakeChan:{
|
||||
slog.Info("recv handshakeChan")
|
||||
return true
|
||||
}
|
||||
case <-ctx.Done():{
|
||||
slog.Info("recv handshakeChan timeout")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *ClientConn) Start() bool{
|
||||
this.handshake = false
|
||||
go this.wsReadLoop()
|
||||
return this.waitHandshake()
|
||||
}
|
||||
|
||||
func (this *ClientConn) Addr() string {
|
||||
return this.wsSocket.RemoteAddr().String()
|
||||
}
|
||||
|
||||
func (this *ClientConn) Push(name string, data interface{}) {
|
||||
rsp := &WsMsgRsp{Body: &RspBody{Name: name, Msg: data, Seq: 0}}
|
||||
this.write(rsp.Body)
|
||||
}
|
||||
|
||||
func (this *ClientConn) Send(name string, data interface{}) *RspBody{
|
||||
this.syncLock.Lock()
|
||||
sync := newSyncCtx()
|
||||
this.Seq += 1
|
||||
seq := this.Seq
|
||||
req := ReqBody{Name: name, Msg: data, Seq: seq}
|
||||
this.syncCtxs[this.Seq] = sync
|
||||
this.syncLock.Unlock()
|
||||
|
||||
rsp := &RspBody{Code: constant.OK, Name: name, Seq: seq }
|
||||
err := this.write(req)
|
||||
if err != nil{
|
||||
sync.cancel()
|
||||
}else{
|
||||
r := sync.wait()
|
||||
if r == nil{
|
||||
rsp.Code = constant.ProxyConnectError
|
||||
}else{
|
||||
rsp = r
|
||||
}
|
||||
}
|
||||
|
||||
this.syncLock.Lock()
|
||||
delete(this.syncCtxs, seq)
|
||||
this.syncLock.Unlock()
|
||||
|
||||
return rsp
|
||||
}
|
||||
|
||||
func (this *ClientConn) wsReadLoop() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
e := fmt.Sprintf("%v", err)
|
||||
slog.Error("wsReadLoop error", "err", e)
|
||||
this.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
// 读一个message
|
||||
_, data, err := this.wsSocket.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
data, err = util.UnZip(data)
|
||||
if err != nil {
|
||||
slog.Error("wsReadLoop UnZip error", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
//需要检测是否有加密
|
||||
body := &RspBody{}
|
||||
if secretKey, err := this.GetProperty("secretKey"); err == nil {
|
||||
key := secretKey.(string)
|
||||
d, err := util.AesCBCDecrypt(data, []byte(key), []byte(key), openssl.ZEROS_PADDING)
|
||||
if err != nil {
|
||||
slog.Error("AesDecrypt error", "error", err)
|
||||
}else{
|
||||
data = d
|
||||
}
|
||||
}
|
||||
|
||||
if err := util.Unmarshal(data, body); err == nil {
|
||||
if body.Seq == 0 {
|
||||
if body.Name == HandshakeMsg{
|
||||
h := Handshake{}
|
||||
mapstructure.Decode(body.Msg, &h)
|
||||
slog.Info("client 收到握手协议", "data", string(data))
|
||||
if h.Key != ""{
|
||||
this.SetProperty("secretKey", h.Key)
|
||||
}else{
|
||||
this.RemoveProperty("secretKey")
|
||||
}
|
||||
this.handshake = true
|
||||
this.handshakeChan <- true
|
||||
}else{
|
||||
//推送,需要推送到指定的代理连接
|
||||
if this.onPush != nil{
|
||||
this.onPush(this, body)
|
||||
}else{
|
||||
slog.Warn("clientconn not deal push")
|
||||
}
|
||||
}
|
||||
}else{
|
||||
this.syncLock.RLock()
|
||||
s, ok := this.syncCtxs[body.Seq]
|
||||
this.syncLock.RUnlock()
|
||||
if ok {
|
||||
s.outChan <- body
|
||||
}else{
|
||||
slog.Warn("seq not found sync",
|
||||
"seq", body.Seq,
|
||||
"msgName", body.Name)
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
slog.Error("wsReadLoop Unmarshal error", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
this.Close()
|
||||
}
|
||||
|
||||
|
||||
func (this *ClientConn) write(msg interface{}) error{
|
||||
data, err := util.Marshal(msg)
|
||||
if err == nil {
|
||||
if secretKey, err:= this.GetProperty("secretKey"); err == nil {
|
||||
key := secretKey.(string)
|
||||
slog.Info("secretKey", "secretKey", key)
|
||||
data, _ = util.AesCBCEncrypt(data, []byte(key), []byte(key), openssl.ZEROS_PADDING)
|
||||
}
|
||||
}else {
|
||||
slog.Error("wsWriteLoop Marshal body error", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if data, err := util.Zip(data); err == nil{
|
||||
if err := this.wsSocket.WriteMessage(websocket.BinaryMessage, data); err != nil {
|
||||
this.Close()
|
||||
return err
|
||||
}
|
||||
}else{
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *ClientConn) Close() {
|
||||
this.wsSocket.Close()
|
||||
if !this.isClosed {
|
||||
this.isClosed = true
|
||||
if this.onClose != nil{
|
||||
this.onClose(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//设置链接属性
|
||||
func (this *ClientConn) SetProperty(key string, value interface{}) {
|
||||
this.propertyLock.Lock()
|
||||
defer this.propertyLock.Unlock()
|
||||
|
||||
this.property[key] = value
|
||||
}
|
||||
|
||||
//获取链接属性
|
||||
func (this *ClientConn) GetProperty(key string) (interface{}, error) {
|
||||
this.propertyLock.RLock()
|
||||
defer this.propertyLock.RUnlock()
|
||||
|
||||
if value, ok := this.property[key]; ok {
|
||||
return value, nil
|
||||
} else {
|
||||
return nil, errors.New("no property found")
|
||||
}
|
||||
}
|
||||
|
||||
//移除链接属性
|
||||
func (this *ClientConn) RemoveProperty(key string) {
|
||||
this.propertyLock.Lock()
|
||||
defer this.propertyLock.Unlock()
|
||||
|
||||
delete(this.property, key)
|
||||
}
|
||||
|
||||
func (this *ClientConn) SetOnClose(hookFunc func (*ClientConn)) {
|
||||
this.onClose = hookFunc
|
||||
}
|
||||
|
||||
func (this *ClientConn) SetOnPush(hookFunc func (*ClientConn, *RspBody)) {
|
||||
this.onPush = hookFunc
|
||||
}
|
||||
70
net/conn.go
Normal file
70
net/conn.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ReqBody struct {
|
||||
Seq int64 `json:"seq"`
|
||||
Name string `json:"name"`
|
||||
Msg interface{} `json:"msg"`
|
||||
Proxy string `json:"proxy"`
|
||||
}
|
||||
|
||||
type RspBody struct {
|
||||
Seq int64 `json:"seq"`
|
||||
Name string `json:"name"`
|
||||
Code int `json:"code"`
|
||||
Msg interface{} `json:"msg"`
|
||||
}
|
||||
|
||||
type WsMsgReq struct {
|
||||
Body *ReqBody
|
||||
Conn WSConn
|
||||
}
|
||||
|
||||
type WsMsgRsp struct {
|
||||
Body* RspBody
|
||||
}
|
||||
|
||||
const HandshakeMsg = "handshake"
|
||||
const HeartbeatMsg = "heartbeat"
|
||||
|
||||
type Handshake struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type Heartbeat struct {
|
||||
CTime int64 `json:"ctime"`
|
||||
STime int64 `json:"stime"`
|
||||
}
|
||||
|
||||
type WSConn interface {
|
||||
SetProperty(key string, value interface{})
|
||||
GetProperty(key string) (interface{}, error)
|
||||
RemoveProperty(key string)
|
||||
Addr() string
|
||||
Push(name string, data interface{})
|
||||
}
|
||||
|
||||
type syncCtx struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
outChan chan *RspBody
|
||||
}
|
||||
|
||||
func newSyncCtx() *syncCtx {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
return &syncCtx{ctx: ctx, cancel: cancel, outChan: make(chan *RspBody)}
|
||||
}
|
||||
|
||||
func (this* syncCtx) wait() *RspBody{
|
||||
defer this.cancel()
|
||||
select {
|
||||
case data := <- this.outChan:
|
||||
return data
|
||||
case <-this.ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
202
net/connMgr.go
Normal file
202
net/connMgr.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
"log/slog"
|
||||
"slgserver/server/slgserver/conn"
|
||||
"slgserver/server/slgserver/pos"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var ConnMgr = Mgr{}
|
||||
var cid int64 = 0
|
||||
|
||||
type Mgr struct {
|
||||
cm sync.RWMutex
|
||||
um sync.RWMutex
|
||||
rm sync.RWMutex
|
||||
|
||||
connCache map[int64]WSConn
|
||||
userCache map[int]WSConn
|
||||
roleCache map[int]WSConn
|
||||
}
|
||||
|
||||
func (this *Mgr) NewConn(wsSocket *websocket.Conn, needSecret bool) *ServerConn {
|
||||
this.cm.Lock()
|
||||
defer this.cm.Unlock()
|
||||
|
||||
id := atomic.AddInt64(&cid, 1)
|
||||
if this.connCache == nil {
|
||||
this.connCache = make(map[int64]WSConn)
|
||||
}
|
||||
|
||||
if this.userCache == nil {
|
||||
this.userCache = make(map[int]WSConn)
|
||||
}
|
||||
|
||||
if this.roleCache == nil {
|
||||
this.roleCache = make(map[int]WSConn)
|
||||
}
|
||||
|
||||
c := NewServerConn(wsSocket, needSecret)
|
||||
c.SetProperty("cid", id)
|
||||
this.connCache[id] = c
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (this *Mgr) UserLogin(conn WSConn, session string, uid int) {
|
||||
this.um.Lock()
|
||||
defer this.um.Unlock()
|
||||
|
||||
oldConn, ok := this.userCache[uid]
|
||||
if ok {
|
||||
if conn != oldConn {
|
||||
slog.Warn("rob login",
|
||||
"uid", uid,
|
||||
"oldAddr", oldConn.Addr(),
|
||||
"newAddr", conn.Addr())
|
||||
|
||||
//这里需要通知旧端被抢登录
|
||||
oldConn.Push("robLogin", nil)
|
||||
}
|
||||
}
|
||||
this.userCache[uid] = conn
|
||||
conn.SetProperty("session", session)
|
||||
conn.SetProperty("uid", uid)
|
||||
}
|
||||
|
||||
func (this *Mgr) UserLogout(conn WSConn) {
|
||||
this.removeUser(conn)
|
||||
}
|
||||
|
||||
func (this *Mgr) removeUser(conn WSConn) {
|
||||
this.um.Lock()
|
||||
uid, err := conn.GetProperty("uid")
|
||||
if err == nil {
|
||||
//只删除自己的conn
|
||||
id := uid.(int)
|
||||
c, ok := this.userCache[id]
|
||||
if ok && c == conn {
|
||||
delete(this.userCache, id)
|
||||
}
|
||||
}
|
||||
this.um.Unlock()
|
||||
|
||||
this.rm.Lock()
|
||||
rid, err := conn.GetProperty("rid")
|
||||
if err == nil {
|
||||
//只删除自己的conn
|
||||
id := rid.(int)
|
||||
c, ok := this.roleCache[id]
|
||||
if ok && c == conn {
|
||||
delete(this.roleCache, id)
|
||||
}
|
||||
}
|
||||
this.rm.Unlock()
|
||||
|
||||
conn.RemoveProperty("session")
|
||||
conn.RemoveProperty("uid")
|
||||
conn.RemoveProperty("role")
|
||||
conn.RemoveProperty("rid")
|
||||
}
|
||||
|
||||
func (this *Mgr) RoleEnter(conn WSConn, rid int) {
|
||||
this.rm.Lock()
|
||||
defer this.rm.Unlock()
|
||||
conn.SetProperty("rid", rid)
|
||||
this.roleCache[rid] = conn
|
||||
}
|
||||
|
||||
func (this *Mgr) RemoveConn(conn WSConn) {
|
||||
this.cm.Lock()
|
||||
cid, err := conn.GetProperty("cid")
|
||||
if err == nil {
|
||||
delete(this.connCache, cid.(int64))
|
||||
conn.RemoveProperty("cid")
|
||||
}
|
||||
this.cm.Unlock()
|
||||
|
||||
this.removeUser(conn)
|
||||
}
|
||||
|
||||
func (this *Mgr) PushByRoleId(rid int, msgName string, data interface{}) bool {
|
||||
if rid <= 0 {
|
||||
return false
|
||||
}
|
||||
this.rm.Lock()
|
||||
defer this.rm.Unlock()
|
||||
conn, ok := this.roleCache[rid]
|
||||
if ok {
|
||||
conn.Push(msgName, data)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Mgr) Count() int {
|
||||
this.cm.RLock()
|
||||
defer this.cm.RUnlock()
|
||||
|
||||
return len(this.connCache)
|
||||
}
|
||||
|
||||
func (this *Mgr) Push(pushSync conn.PushSync) {
|
||||
|
||||
proto := pushSync.ToProto()
|
||||
belongRIds := pushSync.BelongToRId()
|
||||
isCellView := pushSync.IsCellView()
|
||||
x, y := pushSync.Position()
|
||||
cells := make(map[int]int)
|
||||
|
||||
//推送给开始位置
|
||||
if isCellView {
|
||||
cellRIds := pos.RPMgr.GetCellRoleIds(x, y, 8, 6)
|
||||
for _, rid := range cellRIds {
|
||||
//是否能出现在视野
|
||||
if can := pushSync.IsCanView(rid, x, y); can {
|
||||
this.PushByRoleId(rid, pushSync.PushMsgName(), proto)
|
||||
cells[rid] = rid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//推送给目标位置
|
||||
tx, ty := pushSync.TPosition()
|
||||
if tx >= 0 && ty >= 0 {
|
||||
var cellRIds []int
|
||||
if isCellView {
|
||||
cellRIds = pos.RPMgr.GetCellRoleIds(tx, ty, 8, 6)
|
||||
} else {
|
||||
cellRIds = pos.RPMgr.GetCellRoleIds(tx, ty, 0, 0)
|
||||
}
|
||||
|
||||
for _, rid := range cellRIds {
|
||||
if _, ok := cells[rid]; !ok {
|
||||
if can := pushSync.IsCanView(rid, tx, ty); can {
|
||||
this.PushByRoleId(rid, pushSync.PushMsgName(), proto)
|
||||
cells[rid] = rid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//推送给自己
|
||||
for _, rid := range belongRIds {
|
||||
if _, ok := cells[rid]; !ok {
|
||||
this.PushByRoleId(rid, pushSync.PushMsgName(), proto)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *Mgr) pushAll(msgName string, data interface{}) {
|
||||
|
||||
this.rm.Lock()
|
||||
defer this.rm.Unlock()
|
||||
for _, conn := range this.roleCache {
|
||||
conn.Push(msgName, data)
|
||||
}
|
||||
}
|
||||
68
net/proxyClient.go
Normal file
68
net/proxyClient.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gorilla/websocket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProxyClient struct {
|
||||
proxy string
|
||||
conn *ClientConn
|
||||
}
|
||||
|
||||
func (this*ProxyClient) Connect() error {
|
||||
var dialer = websocket.Dialer{
|
||||
Subprotocols: []string{"p1", "p2"},
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
HandshakeTimeout: 30 * time.Second,
|
||||
}
|
||||
ws, _, err := dialer.Dial(this.proxy, nil)
|
||||
if err == nil{
|
||||
this.conn = NewClientConn(ws)
|
||||
if this.conn.Start() == false{
|
||||
return errors.New("handshake fail")
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (this*ProxyClient) Send(msgName string, msg interface{}) (*RspBody, error){
|
||||
if this.conn != nil {
|
||||
return this.conn.Send(msgName, msg), nil
|
||||
}
|
||||
return nil, errors.New("conn not found")
|
||||
}
|
||||
|
||||
|
||||
func (this *ProxyClient) SetOnPush(hookFunc func (*ClientConn, *RspBody)) {
|
||||
if this.conn != nil {
|
||||
this.conn.SetOnPush(hookFunc)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ProxyClient) SetOnClose(hookFunc func (*ClientConn)) {
|
||||
if this.conn != nil {
|
||||
this.conn.SetOnClose(hookFunc)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ProxyClient) SetProperty(key string, value interface{}) {
|
||||
if this.conn != nil {
|
||||
this.conn.SetProperty(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ProxyClient) Close() {
|
||||
if this.conn != nil {
|
||||
this.conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func NewProxyClient(proxy string) *ProxyClient {
|
||||
return & ProxyClient{
|
||||
proxy: proxy,
|
||||
}
|
||||
}
|
||||
|
||||
95
net/router.go
Normal file
95
net/router.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HandlerFunc func(req *WsMsgReq, rsp *WsMsgRsp)
|
||||
type MiddlewareFunc func(HandlerFunc) HandlerFunc
|
||||
|
||||
type Group struct {
|
||||
prefix string
|
||||
hMap map[string]HandlerFunc
|
||||
hMapMidd map[string][]MiddlewareFunc
|
||||
middleware []MiddlewareFunc
|
||||
}
|
||||
|
||||
func (this *Group) AddRouter(name string, handlerFunc HandlerFunc, middleware ...MiddlewareFunc) {
|
||||
this.hMap[name] = handlerFunc
|
||||
this.hMapMidd[name] = middleware
|
||||
}
|
||||
|
||||
func (this *Group) Use(middleware ...MiddlewareFunc) *Group {
|
||||
this.middleware = append(this.middleware, middleware...)
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Group) applyMiddleware(name string) HandlerFunc {
|
||||
|
||||
h, ok := this.hMap[name]
|
||||
if !ok {
|
||||
//通配符
|
||||
h, ok = this.hMap["*"]
|
||||
}
|
||||
|
||||
if ok {
|
||||
for i := len(this.middleware) - 1; i >= 0; i-- {
|
||||
h = this.middleware[i](h)
|
||||
}
|
||||
|
||||
for i := len(this.hMapMidd[name]) - 1; i >= 0; i-- {
|
||||
h = this.hMapMidd[name][i](h)
|
||||
}
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (this *Group) exec(name string, req *WsMsgReq, rsp *WsMsgRsp) {
|
||||
slog.Debug("route exec", "group", this.prefix, "name", name)
|
||||
h := this.applyMiddleware(name)
|
||||
if h == nil {
|
||||
slog.Warn("Group has not",
|
||||
"msgName", req.Body.Name)
|
||||
} else {
|
||||
h(req, rsp)
|
||||
}
|
||||
if dd, err := json.Marshal(rsp.Body); err == nil {
|
||||
slog.Debug("route response", "group", this.prefix, "name", name, "size", len(dd))
|
||||
}
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
groups []*Group
|
||||
}
|
||||
|
||||
func (this *Router) Group(prefix string) *Group {
|
||||
g := &Group{prefix: prefix,
|
||||
hMap: make(map[string]HandlerFunc),
|
||||
hMapMidd: make(map[string][]MiddlewareFunc),
|
||||
}
|
||||
|
||||
this.groups = append(this.groups, g)
|
||||
return g
|
||||
}
|
||||
|
||||
func (this *Router) Run(req *WsMsgReq, rsp *WsMsgRsp) {
|
||||
name := req.Body.Name
|
||||
msgName := name
|
||||
sArr := strings.Split(name, ".")
|
||||
prefix := ""
|
||||
if len(sArr) == 2 {
|
||||
prefix = sArr[0]
|
||||
msgName = sArr[1]
|
||||
}
|
||||
|
||||
for _, g := range this.groups {
|
||||
if g.prefix == prefix {
|
||||
g.exec(msgName, req, rsp)
|
||||
} else if g.prefix == "*" {
|
||||
g.exec(msgName, req, rsp)
|
||||
}
|
||||
}
|
||||
}
|
||||
137
net/server.go
Normal file
137
net/server.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gorilla/websocket"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"slgserver/config"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// http升级websocket协议的配置
|
||||
var wsUpgrader = websocket.Upgrader{
|
||||
// 允许配置的 CORS 请求
|
||||
CheckOrigin: checkOrigin,
|
||||
}
|
||||
|
||||
var allowedOrigins = loadAllowedOrigins()
|
||||
|
||||
type server struct {
|
||||
addr string
|
||||
router *Router
|
||||
needSecret bool
|
||||
beforeClose func(WSConn)
|
||||
httpServer *http.Server
|
||||
}
|
||||
|
||||
func NewServer(addr string, needSecret bool) *server {
|
||||
s := server{
|
||||
addr: addr,
|
||||
needSecret: needSecret,
|
||||
}
|
||||
return &s
|
||||
}
|
||||
|
||||
func (this *server) Router(router *Router) {
|
||||
this.router = router
|
||||
}
|
||||
|
||||
func (this *server) Start() {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", this.wsHandler)
|
||||
|
||||
this.httpServer = &http.Server{
|
||||
Addr: this.addr,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
slog.Info("server starting", "addr", this.addr)
|
||||
|
||||
go func() {
|
||||
if err := this.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
slog.Error("server start failed", "error", err, "addr", this.addr)
|
||||
}
|
||||
}()
|
||||
|
||||
this.waitForShutdown()
|
||||
}
|
||||
|
||||
func (this *server) waitForShutdown() {
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
slog.Info("server shutting down...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := this.httpServer.Shutdown(ctx); err != nil {
|
||||
slog.Error("server shutdown error", "error", err)
|
||||
} else {
|
||||
slog.Info("server shutdown gracefully")
|
||||
}
|
||||
}
|
||||
|
||||
func (this *server) SetOnBeforeClose(hookFunc func(WSConn)) {
|
||||
this.beforeClose = hookFunc
|
||||
}
|
||||
|
||||
func (this *server) wsHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
|
||||
wsSocket, err := wsUpgrader.Upgrade(resp, req, nil)
|
||||
if err != nil {
|
||||
slog.Warn("websocket upgrade failed", "error", err, "remote", req.RemoteAddr)
|
||||
return
|
||||
}
|
||||
|
||||
conn := ConnMgr.NewConn(wsSocket, this.needSecret)
|
||||
slog.Info("client connect", "addr", wsSocket.RemoteAddr().String())
|
||||
|
||||
conn.SetRouter(this.router)
|
||||
conn.SetOnClose(ConnMgr.RemoveConn)
|
||||
conn.SetOnBeforeClose(this.beforeClose)
|
||||
conn.Start()
|
||||
conn.Handshake()
|
||||
|
||||
}
|
||||
|
||||
func checkOrigin(r *http.Request) bool {
|
||||
if len(allowedOrigins) == 0 {
|
||||
return true
|
||||
}
|
||||
origin := strings.ToLower(strings.TrimSpace(r.Header.Get("Origin")))
|
||||
if origin == "" {
|
||||
return true
|
||||
}
|
||||
if _, ok := allowedOrigins[origin]; ok {
|
||||
return true
|
||||
}
|
||||
slog.Warn("origin not allowed", "origin", origin)
|
||||
return false
|
||||
}
|
||||
|
||||
func loadAllowedOrigins() map[string]struct{} {
|
||||
origins := config.GetString("server.allowed_origins", "")
|
||||
if origins == "" {
|
||||
return nil
|
||||
}
|
||||
originMap := make(map[string]struct{})
|
||||
items := strings.Split(origins, ",")
|
||||
for _, item := range items {
|
||||
val := strings.ToLower(strings.TrimSpace(item))
|
||||
if val == "" {
|
||||
continue
|
||||
}
|
||||
if val == "*" {
|
||||
return nil
|
||||
}
|
||||
originMap[val] = struct{}{}
|
||||
}
|
||||
return originMap
|
||||
}
|
||||
287
net/serverconn.go
Normal file
287
net/serverconn.go
Normal file
@@ -0,0 +1,287 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slgserver/util"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-think/openssl"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// 客户端连接
|
||||
type ServerConn struct {
|
||||
wsSocket *websocket.Conn // 底层websocket
|
||||
outChan chan *WsMsgRsp // 写队列
|
||||
isClosed bool
|
||||
needSecret bool
|
||||
Seq int64
|
||||
router *Router
|
||||
beforeClose func(conn WSConn)
|
||||
onClose func(conn WSConn)
|
||||
//链接属性
|
||||
property map[string]interface{}
|
||||
//保护链接属性修改的锁
|
||||
propertyLock sync.RWMutex
|
||||
closeChan chan struct{}
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
func NewServerConn(wsSocket *websocket.Conn, needSecret bool) *ServerConn {
|
||||
conn := &ServerConn{
|
||||
wsSocket: wsSocket,
|
||||
outChan: make(chan *WsMsgRsp, 1000),
|
||||
isClosed: false,
|
||||
property: make(map[string]interface{}),
|
||||
needSecret: needSecret,
|
||||
Seq: 0,
|
||||
closeChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
// 开启异步
|
||||
func (this *ServerConn) Start() {
|
||||
go this.wsReadLoop()
|
||||
go this.wsWriteLoop()
|
||||
}
|
||||
|
||||
func (this *ServerConn) Addr() string {
|
||||
return this.wsSocket.RemoteAddr().String()
|
||||
}
|
||||
|
||||
func (this *ServerConn) Push(name string, data interface{}) {
|
||||
rsp := &WsMsgRsp{Body: &RspBody{Name: name, Msg: data, Seq: 0}}
|
||||
this.enqueue(rsp)
|
||||
}
|
||||
|
||||
func (this *ServerConn) Send(name string, data interface{}) {
|
||||
this.Seq += 1
|
||||
rsp := &WsMsgRsp{Body: &RspBody{Name: name, Msg: data, Seq: this.Seq}}
|
||||
this.enqueue(rsp)
|
||||
}
|
||||
|
||||
func (this *ServerConn) wsReadLoop() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
e := fmt.Sprintf("%v", err)
|
||||
slog.Error("wsReadLoop error", "err", e)
|
||||
this.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-this.closeChan:
|
||||
return
|
||||
default:
|
||||
}
|
||||
// 读一个message
|
||||
_, data, err := this.wsSocket.ReadMessage()
|
||||
slog.Debug("ws read message", "size", len(data))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
data, err = util.UnZip(data)
|
||||
if err != nil {
|
||||
slog.Error("wsReadLoop UnZip error", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
body := &ReqBody{}
|
||||
if this.needSecret {
|
||||
//检测是否有加密,没有加密发起Handshake
|
||||
if secretKey, err := this.GetProperty("secretKey"); err == nil {
|
||||
key := secretKey.(string)
|
||||
d, err := util.AesCBCDecrypt(data, []byte(key), []byte(key), openssl.ZEROS_PADDING)
|
||||
if err != nil {
|
||||
slog.Error("AesDecrypt error", "error", err)
|
||||
this.Handshake()
|
||||
} else {
|
||||
data = d
|
||||
}
|
||||
} else {
|
||||
slog.Info("secretKey not found client need handshake", "error", err)
|
||||
this.Handshake()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := util.Unmarshal(data, body); err == nil {
|
||||
req := &WsMsgReq{Conn: this, Body: body}
|
||||
rsp := &WsMsgRsp{Body: &RspBody{Name: body.Name, Seq: req.Body.Seq}}
|
||||
|
||||
if req.Body.Name == HeartbeatMsg {
|
||||
h := &Heartbeat{}
|
||||
mapstructure.Decode(body.Msg, h)
|
||||
h.STime = time.Now().UnixNano() / 1e6
|
||||
rsp.Body.Msg = h
|
||||
} else {
|
||||
if this.router != nil {
|
||||
this.router.Run(req, rsp)
|
||||
}
|
||||
}
|
||||
this.outChan <- rsp
|
||||
} else {
|
||||
slog.Error("wsReadLoop Unmarshal error", "error", err)
|
||||
this.Handshake()
|
||||
}
|
||||
}
|
||||
|
||||
this.Close()
|
||||
}
|
||||
|
||||
func (this *ServerConn) wsWriteLoop() {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
slog.Error("wsWriteLoop error")
|
||||
this.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-this.closeChan:
|
||||
return
|
||||
case msg := <-this.outChan:
|
||||
if msg == nil {
|
||||
continue
|
||||
}
|
||||
// 写给websocket
|
||||
this.write(msg.Body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ServerConn) write(msg interface{}) error {
|
||||
data, err := util.Marshal(msg)
|
||||
slog.Debug("ws write message", "size", len(data))
|
||||
if err == nil {
|
||||
if this.needSecret {
|
||||
if secretKey, err := this.GetProperty("secretKey"); err == nil {
|
||||
key := secretKey.(string)
|
||||
data, _ = util.AesCBCEncrypt(data, []byte(key), []byte(key), openssl.ZEROS_PADDING)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
slog.Error("wsWriteLoop Marshal body error", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if data, err := util.Zip(data); err == nil {
|
||||
if err := this.wsSocket.WriteMessage(websocket.BinaryMessage, data); err != nil {
|
||||
this.Close()
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *ServerConn) Close() {
|
||||
this.closeOnce.Do(func() {
|
||||
this.wsSocket.Close()
|
||||
close(this.closeChan)
|
||||
if !this.isClosed {
|
||||
this.isClosed = true
|
||||
|
||||
if this.beforeClose != nil {
|
||||
this.beforeClose(this)
|
||||
}
|
||||
|
||||
if this.onClose != nil {
|
||||
this.onClose(this)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 设置链接属性
|
||||
func (this *ServerConn) SetProperty(key string, value interface{}) {
|
||||
this.propertyLock.Lock()
|
||||
defer this.propertyLock.Unlock()
|
||||
|
||||
this.property[key] = value
|
||||
}
|
||||
|
||||
// 获取链接属性
|
||||
func (this *ServerConn) GetProperty(key string) (interface{}, error) {
|
||||
this.propertyLock.RLock()
|
||||
defer this.propertyLock.RUnlock()
|
||||
|
||||
if value, ok := this.property[key]; ok {
|
||||
return value, nil
|
||||
} else {
|
||||
return nil, errors.New("no property found")
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ServerConn) SetRouter(router *Router) {
|
||||
this.router = router
|
||||
}
|
||||
|
||||
func (this *ServerConn) SetOnClose(hookFunc func(WSConn)) {
|
||||
this.onClose = hookFunc
|
||||
}
|
||||
|
||||
func (this *ServerConn) SetOnBeforeClose(hookFunc func(WSConn)) {
|
||||
this.beforeClose = hookFunc
|
||||
}
|
||||
|
||||
// 移除链接属性
|
||||
func (this *ServerConn) RemoveProperty(key string) {
|
||||
this.propertyLock.Lock()
|
||||
defer this.propertyLock.Unlock()
|
||||
|
||||
delete(this.property, key)
|
||||
}
|
||||
|
||||
// 握手协议
|
||||
func (this *ServerConn) Handshake() {
|
||||
|
||||
secretKey := ""
|
||||
if this.needSecret {
|
||||
key, err := this.GetProperty("secretKey")
|
||||
if err == nil {
|
||||
secretKey = key.(string)
|
||||
} else {
|
||||
secretKey = util.RandSeq(16)
|
||||
}
|
||||
}
|
||||
|
||||
handshake := &Handshake{Key: secretKey}
|
||||
body := &RspBody{Name: HandshakeMsg, Msg: handshake}
|
||||
if data, err := util.Marshal(body); err == nil {
|
||||
if secretKey != "" {
|
||||
this.SetProperty("secretKey", secretKey)
|
||||
} else {
|
||||
this.RemoveProperty("secretKey")
|
||||
}
|
||||
|
||||
slog.Info("handshake secretKey",
|
||||
"secretKey", secretKey)
|
||||
|
||||
if data, err = util.Zip(data); err == nil {
|
||||
this.wsSocket.WriteMessage(websocket.BinaryMessage, data)
|
||||
}
|
||||
|
||||
} else {
|
||||
slog.Error("handshake Marshal body error", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ServerConn) enqueue(rsp *WsMsgRsp) {
|
||||
select {
|
||||
case <-this.closeChan:
|
||||
return
|
||||
case this.outChan <- rsp:
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user