[三国第三章] :网关

目录


一、

什么是服务网关?

服务网关 = 路由转发 + 过滤器

路由转发:接收一切外界请求,转发到后端的服务上去。

过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、监控、限流、负载均衡等。
引入网关后的请求流程为:
客户端发起请求

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
	}
}

关于加密:网关需要加密,登录服务器不需要加密,否则数据解析不出来

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值