目录
一、
什么是服务网关?
服务网关 = 路由转发 + 过滤器
路由转发:接收一切外界请求,转发到后端的服务上去。
过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、监控、限流、负载均衡等。
引入网关后的请求流程为:
客户端发起请求
1、
网关要做的内容:
/**
1.登录功能 account.login 需要通过网关 转发 登录服务器
2. 网关(websocket的客户端) 如何和 登录服务器(websocket服务端)交互
3. 网关又和游戏客户端 进行交互,网关是 websocket的服务端
4. websocket的服务端 已经实现了
5. websocket的客户端(待实现)
6. 网关 : 代理服务器 (代理地址 代理的连接通道) 客户端连接(websocket连接)
7. 路由:路由 接收所有的请求(*) 网关的 websocket服务端的功能
8. 握手协议 检测第一次建立连接的时候 授信
/
网关代理服务器实现:创建一个websocket server接收127.0.0.1:8004请求,controller层定义网关处理器,这个处理器包括一个代理map,map的key是代理地址,value还是一个map,map的key是客户端id,value是代理客户端.
处理器放行请求为""的路由,从网关代理服务端缓存map中查找是否有"客户端id"的代理客户端.如果没有则为这个客户端id新建代理客户端,并连接代理服务器,循环从代理服务器读取消息.如果是握手消息并且握手成功,则将代理客户端连接缓存进网关代理服务器map中. (握手过程代理客户端发送请求,如果代理服务端要求加密,且请求中不带秘钥或解密错误,则代理服务端会发送加密key给客户端,客户端解密后将秘钥保存到本地的的Property中,以后每次请求都将数据加密,代理服务端成功解密后就会将结果数据返回. )如果不是握手消息,则根据代理服务器响应的序列号找到网关代理服务器的上下文ctx,通过ctx的channel将数据返回到客户端.
如果有"客户端id"对应的代理客户端缓存,则直接通过该代理客户端向代理服务器转发请求,然后等待响应即可
网关代理客户端实现:代理客户端结构体包括代理地址和客户端连接,
根据请求前缀中的服务名创建代理客户端,并连接代理服务器.
具体实现:
0.创建代理客户端处理器即网关
/*0.创建代理客户端处理器即网关*/
type Handler struct {
proxyMutex sync.Mutex
//代理地址 -》客户端连接(游戏客户端的id -》连接)
proxyMap map[string]map[int64]*net.ProxyClient //map的key存放代理地址,value还是一个map,此map的key是客户端id,value是连接通道
loginProxy string
gameProxy string
}
1.创建*路由组
// 1.创建*路由组
func (h *Handler) Router(r *net.Router) {
h.loginProxy = config.File.MustValue("gate_server", "login_proxy", "ws://127.0.0.1:8003")
h.gameProxy = config.File.MustValue("gate_server", "game_proxy", "ws://127.0.0.1:8001")
g := r.Group("*")
g.AddRouter("*", h.all)
}
2.放行请求为*的路由
func (r *Router) Run(req *WsMsgReq, rsp *WsMsgRsp) {
//req.Body.Name 路径 登录业务 account.login (account组标识)login 路由标识
strs := strings.Split(req.Body.Name, ".")
prefix := ""
name := ""
if len(strs) == 2 {
prefix = strs[0]
name = strs[1]
}
for _, g := range r.group {
if g.prefix == prefix {
g.exec(name, req, rsp)
} else if g.prefix == "*" { //2.放行请求为*的路由
g.exec(name, req, rsp)
}
}
}
3.如果找不到请求 执行*的函数
func (g *group) exec(name string, req *WsMsgReq, rsp *WsMsgRsp) {
h := g.handlerMap[name]
if h != nil {
h(req, rsp)
} else { //3.如果找不到请求 执行*的函数
h = g.handlerMap["*"]
if h != nil {
h(req, rsp)
} else {
log.Println("路由未定义")
}
}
}
4.代理转发所有请求
5.定义代理客户端ProxyClient及其持有的连接ClientConn结构体
6.创建转发到对应服务器(account登录服务器)的代理客户端,由代理客户端连接到对应的websocket服务,然后代理客户端不停的接收消息,等待握手成功.
/*- 5.定义代理客户端ProxyClient及其持有的连接ClientConn结构体*/
// 代理客户端
type ProxyClient struct {
proxy string //代理地址
conn *ClientConn //代理客户端持有的连接
}
func (h *Handler) all(req *net.WsMsgReq, rsp *net.WsMsgRsp) {
//4.代理转发所有请求
//account 转发
name := req.Body.Name
proxyStr := ""
if isAccount(name) {
proxyStr = h.loginProxy
}
if proxyStr == "" {
rsp.Body.Code = constant.ProxyNotInConnect
return
}
h.proxyMutex.Lock()
_, ok := h.proxyMap[proxyStr]
if !ok {
h.proxyMap[proxyStr] = make(map[int64]*net.ProxyClient)
}
h.proxyMutex.Unlock()
//客户端id
c, err := req.Conn.GetProperty("cid")
if err != nil {
log.Println("cid未取到", err)
rsp.Body.Code = constant.InvalidParam
return
}
cid := c.(int64)
proxy := h.proxyMap[proxyStr][cid]
if proxy == nil {
proxy = net.NewProxyClient(proxyStr) //6.创建转发到对应服务器(account登录服务器)的代理客户端
err := proxy.Connect() //由代理服务器连接到对应的websocket服务
if err != nil {
h.proxyMutex.Lock()
delete(h.proxyMap[proxyStr], cid)
h.proxyMutex.Unlock()
rsp.Body.Code = constant.ProxyConnectError
return
}
h.proxyMap[proxyStr][cid] = proxy
proxy.SetProperty("cid", cid)
proxy.SetProperty("proxy", proxyStr)
proxy.SetProperty("gateConn", req.Conn)
proxy.SetOnPush(h.onPush)
}
rsp.Body.Seq = req.Body.Seq
rsp.Body.Name = req.Body.Name
r, err := proxy.Send(req.Body.Name, req.Body.Msg)//9.向代理服务器(如登录服务器)发送消息
if r != nil {
rsp.Body.Code = r.Code
rsp.Body.Msg = r.Msg
} else {
rsp.Body.Code = constant.ProxyConnectError
return
}
}
7.代理客户端不停的接收消息,等待握手成功
func (c *ProxyClient) Connect() error {
//去连接 websocket服务端
//通过Dialer连接websocket服务器
var dialer = websocket.Dialer{
Subprotocols: []string{"p1", "p2"},
ReadBufferSize: 1024,
WriteBufferSize: 1024,
HandshakeTimeout: 30 * time.Second,
}
ws, _, err := dialer.Dial(c.proxy, nil)
if err == nil {
c.conn = NewClientConn(ws)
if !c.conn.Start() { //- 7.代理客户端不停的接收消息,等待握手成功
return errors.New("握手失败")
}
}
return err
}
func (c *ClientConn) Start() bool {
c.handshake = false
go c.wsReadLoop()
return c.waitHandShake()
}
func (c *ClientConn) waitHandShake() bool{
if !c.handshake {//如果还没有握手才等待握手
ctx,cancel := context.WithTimeout(context.Background(),5*time.Second)
defer cancel()
select {
case <- c.handshakeChan:
log.Println("握手成功了...")
return true
case <- ctx.Done():
log.Println("握手超时了...")
return false
}
}
return true
}
8.代理客户端收到消息 解析消息,处理握手或别的一些请求
func (c *ClientConn) wsReadLoop() {
defer func() {
if err := recover(); err != nil {
log.Println("客户端捕捉到异常", err)
c.Close()
}
}()
for {
_, data, err := c.wsConn.ReadMessage()
if err != nil {
log.Println("收消息出现错误:", err)
break
}
//收到消息 解析消息 前端发送过来的消息 就是json格式
//a. data 解压 unzip
data, err = utils.UnZip(data)
if err != nil {
log.Println("解压数据出错,非法格式:", err)
continue
}
//b. 前端的消息 加密消息 进行解密
secretKey, err := c.GetProperty("secretKey")
if err == nil {
//有加密
key := secretKey.(string)
//客户端传过来的数据是加密的 需要解密
d, err := utils.AesCBCDecrypt(data, []byte(key), []byte(key), openssl.ZEROS_PADDING)
if err != nil {
log.Println("数据格式有误,解密失败:", err)
} else {
data = d
}
}
//c. 反序列化,data 转为body
body := &RspBody{} //body存储游戏或其他服务端响应的数据
err = json.Unmarshal(data, body)
if err != nil {
log.Println("数据格式有误,非法格式:", err)
} else {
//- 8.代理客户端收到消息 解析消息,处理握手或别的一些请求
if body.Seq == 0 {
if body.Name == HandshakeMsg {
//代理客户端获取秘钥,再发送给游戏服务端
hs := &Handshake{}
mapstructure.Decode(body.Msg, hs)
if hs.Key != "" {
c.SetProperty("secretKey", hs.Key)
} else {
c.RemoveProperty("secretKey")
}
c.handshake = true
c.handshakeChan <- true //发送握手成功
} else {
if c.onPush != nil {
c.onPush(c, body) //抛给代理客户端持有的其他连接处理?
}
}
} else {
c.syncCtxLock.RLock()
ctx, ok := c.syncCtxMap[body.Seq] //根据服务端响应的序列号,找到我们代理客户端持有的连接
c.syncCtxLock.RUnlock()
if ok {
ctx.outChan <- body //往代理服务端写 服务端返回的body数据
} else {
log.Println("no seq syncCtx find")
}
}
}
}
c.Close()
}
9.向代理服务器(如登录服务器)发送消息
入口在"6."
func (c *ClientConn) Send(name string, msg interface{}) *RspBody {
//把请求 发送给 代理服务器 登录服务器,等待返回
c.Seq += 1
seq := c.Seq
sc := NewSyncCtx()
c.syncCtxLock.Lock()
c.syncCtxMap[seq] = sc
c.syncCtxLock.Unlock()
rsp := &RspBody{Name: name, Seq: seq, Code: constant.OK}
//req请求
req := &ReqBody{Seq: seq, Name: name, Msg: msg}
err := c.write(req)//向服务器发数据
if err != nil {
sc.cancel()
} else {
r := sc.wait() //10.代理服务端接收并返回服务端数据
if r == nil {
rsp.Code = constant.ProxyConnectError
} else {
rsp = r
}
}
c.syncCtxLock.Lock()
delete(c.syncCtxMap, seq)
c.syncCtxLock.Unlock()
return rsp
}
10.代理服务端接收并返回服务端数据
// 10.代理服务端接收并返回服务端数据
func (s *syncCtx) wait() *RspBody {
select {
case msg := <-s.outChan:
return msg
case <-s.ctx.Done():
log.Println("代理服务响应消息超时了...")
return nil
}
}
关于加密:网关需要加密,登录服务器不需要加密,否则数据解析不出来