zoukankan      html  css  js  c++  java
  • go语言游戏服务端开发(二)——网络通信

    五邑隐侠,本名关健昌,12年游戏生涯。 本教程以Go语言为例。
     
    一、网络层
    网络游戏客户端除了全局登录使用http请求外,一般通过socket长连接与服务端保持连接。go语言的net包提供网络socket长连接相关操作。
    对于服务端,一般经历 Listen、Accept两个步骤实现与客户端连接。
    func main() {
        l, err := net.Listen("tcp4", ":8080")
        if err != nil {
            return
        }
        for {
            c, err := l.Accept()
            if err != nil {
                break
            }
            fmt.Println("accept connect: ", c.RemoteAddr())
        }
    }

    客户端通过 Dial 连接服务端

    func main() {
        c, err := net.DialTimeout("tcp4", "127.0.0.1:8080", time.Second*time.Duration(8))
        if err != nil {
            return
        }
        fmt.Println("connect with: ", c.LocalAddr())
    }
    连接 c(net.Conn类型)的主要方法是 Read、Write,
    func Read(b []byte) (n int, err error)
    func Write(b []byte) (n int, err error)

    一般连接建立后,每个连接分别创建读和写两个 go rountine 进行读循环和写循环。

    func (c *Conn) open() {
        go c.readLoop()
        go c.writeLoop()
    }

    虽然go底层是基于epoll边缘触发,但是并没有暴露接口通知什么时候有可读数据、可写等。为了避免轮询,一般在 go rountine 里阻塞的读、写。由于net.Conn只提供 Close() error,没办法只停止读,等待写结束再关闭连接。一般读做超时处理,超时后如果有关闭标记,则不再尝试读。

    func SetReadDeadline(t time.Time) error

    连接层需要通知使用者连接的状态,所以引入连接监听 interface

    type ConnListener interface {
        OnConnOpen(c net.Conn) error
        OnConnClose(c net.Conn) error
        OnConnError(c net.Conn, err error)
        OnConnRead(c net.Conn) error
        OnConnWrite(c net.Conn) error
    }

    使用者只需要实现自己的监听者监听连接的各个生命周期,由读写的 go rountine 驱动业务逻辑执行。

     
    二、P2P层
    网络层提供简单的连接打开、关闭和读写操作。为了建立服务端进程之间,以及服务端进程与客户端之间通信的基础,引入P2P层(端对端层)。
    P2P层包含端信息 P2pEnd、协议包P2pPack、P2P网络P2pNet
    1、P2pEnd定义这个端的类型、编号(例如:游戏分服编号1,网关服编号3)、写队列。还包含一些防御信息用于对连接进行监控
    type P2pEnd struct {
        EndType          uint8
        EndNo            uint16
        QueWritePacks    chan *P2pPack
    }

    2、P2pPack定义包信息,包括源类型、编号,目标类型、编号,数据,这有利于包的路由。除此以外还有一些控制信息做更精细的处理。

    type P2pPack struct {
        SrcEnd uint8
        SrcNo  uint16
        DstEnd uint8
        DstNo  uint16
        Payload []byte
    }

    这里的包格式是通用包格式,Payload里包含业务包包头,根据业务需求定义自己的包格式。

    3、P2pNet是一个端对端网络,维护该通信端所有的连接。作为一个通信端,它首先有自己的端类型、编号

    type P2pNet struct {
        endType       uint8
        endNo         uint16
    }

    然后要记录其他端与连接的互相映射

    type P2pNet struct {
        endType       uint8
        endNo         uint16
        mapConn2End   map[net.Conn]*P2pEnd
        mapId2Conn    map[uint32]net.Conn
    }

    所有连接接收到的包放到一个chan里,方便做分发处理

    type P2pNet struct {
        endType       uint8
        endNo         uint16
        mapConn2End   map[net.Conn]*P2pEnd
        mapId2Conn    map[uint32]net.Conn
        queReadPacks  chan *ReadPackWrap
    }

    还有一些其他的控制信息。

    在P2P层维护的是端与端之间的连接,所以需要提供注册协议,用于向服务方告知自己的端类型和编号。
    func (r *P2pNet) Register(dstEnd uint8, dstNo uint16) error
    服务方在 OnConnOpen(c net.Conn) error(自动分配编号) 或者 OnConnRead(c net.Conn) error 得到的包是注册包时(由协议指定编号)对连接进行绑定。

    除了注册协议,底层的心跳 ping、pong 也在P2P层处理,还有一些防御相关的处理,对业务层透明。

    这样建立起一张端对端通信网。这张网的底层基于网络层做通信,通过实现 ConnListener 驱动连接读写,读包放到 P2pNet.queReadPacks,写包放到对应端的 P2pEnd.QueWritePacks。
    为了驱动业务逻辑,类似网络层,在P2P层也引入监听的 interface,使用者通过实现该interface来驱动业务逻辑
    type P2pListener interface {
        OnP2pConn(p2p *P2pNet, endType uint8, endNo uint16)
        OnP2pCall(p2p *P2pNet, pack *P2pPack)
        OnP2pClose(p2p *P2pNet, endType uint8, endNo uint16)
        OnP2pError(p2p *P2pNet, err error)
    }

    三、关于防御

    连接层的防御一般就是检测异常连接,把异常连接踢掉,避免占用socket资源。
    1、最简单的,通过心跳来判断连接是否活跃,清除非活跃连接复用这部分socket。连接可以分为主动活跃和被动活跃两种模式。主动活跃的连接,会主动发心跳包过来,通过频率去检测心跳包,如果超时都没收到心跳包,可以踢掉。被动活跃连接,需要定时给它发心跳报活,避免被对方踢掉。
    2、没有业务包的连接。如果一个连接从连接开始,只发心跳包,限定时间内从来不发业务包,这个连接要踢掉。
    基于1、2点,连接从连接开始必须在限定时间内发业务包,后续必须通过发业务包或者心跳包来维护连接。
    3、限制 IP 关联的连接数,超过上限则踢掉连接。一般同个局域网的玩家 IP 会一样,但是也可能是服务器在被攻击。现在有些游戏上线,会被模拟玩家连接撑满服务,导致真实玩家无法进入游戏。通过加 IP 关联的连接数限制来增加攻击成本。
    4、发包频率检测,例如我们设定最大15帧/s,每隔2分钟检测一次,如果请求包间隔平均时间小于66ms,可以踢掉。
    5、限制最大的包大小,收到超过最大限制的包,则踢掉连接。
    6、设置写超时(例如5秒),超时则踢掉连接,避免客户端一直阻塞不接收导致缓存的包越来越多
     
    业务层可以做这些限制:
    1、连接验证,没做登录或者重连,直接发其他业务请求包,踢掉连接
    2、登录失败,踢掉连接
     
    网络通信介绍到这里,接下来聊聊业务的服务机制和rpc机制。
  • 相关阅读:
    JFreeChart生成图片
    itext生成Word
    itext生成PDF
    物理模型name与comment互相转化
    表单序列化为Json(只限input)
    c#多线程同步之EventWaitHandle的应用
    C#多线程之异步编程
    Java环境变量设置
    sharepoint 2013实践
    WPF研究之道——数据驱动UI
  • 原文地址:https://www.cnblogs.com/niudanshui/p/15303017.html
Copyright © 2011-2022 走看看