一、网络层
网络游戏客户端除了全局登录使用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、限制最大的包大小,收到超过最大限制的包,则踢掉连接。
 
网络通信介绍到这里,接下来聊聊业务的服务机制和rpc机制。

go语言游戏服务端开发(二)——网络通信的更多相关文章

  1. go语言游戏服务端开发(三)——服务机制

    五邑隐侠,本名关健昌,12年游戏生涯. 本教程以Go语言为例.   P2P网络为服务进程间.服务进程与客户端间通信提供了便利,在这个基础上可以搭建服务. 在服务层,通信包可以通过定义协议号来确定该包怎 ...

  2. go语言游戏服务端开发(一)——架构

    五邑隐侠,本名关健昌,12年游戏生涯. 本教程以Go语言为例.   网络游戏程序分为客户端和服务端.客户端负责图形渲染.交互和一些简单校验处理,服务端负责业务逻辑处理.数据存储. 我们开发一个游戏de ...

  3. go语言游戏服务端开发(四)——RPC机制

    五邑隐侠,本名关健昌,12年游戏生涯. 本教程以Go语言为例. RPC指远程方法调用,游戏里引入RPC目的是降低跨进程交互的复杂度. 游戏业务设计为多go routine,一个玩家一个go routi ...

  4. Swift3.0服务端开发(二) 静态文件添加、路由配置以及表单提交

    今天博客中就来聊一下Perfect框架的静态文件的添加与访问,路由的配置以及表单的提交.虽然官网上有聊静态文件的访问的部分,但是在使用Perfect框架来访问静态文件时还是有些点需要注意的,这些关键点 ...

  5. 转: 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端

    from: http://ybak.iteye.com/blog/1853335 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端 游戏服 ...

  6. Swift3.0服务端开发(一) 完整示例概述及Perfect环境搭建与配置(服务端+iOS端)

    本篇博客算是一个开头,接下来会持续更新使用Swift3.0开发服务端相关的博客.当然,我们使用目前使用Swift开发服务端较为成熟的框架Perfect来实现.Perfect框架是加拿大一个创业团队开发 ...

  7. 为什么多数游戏服务端是用 C++ 来写

    早年开发游戏必须用C++,这没得说,2000-2004年,java还没有nio,其他动态语言不抗重负,只能C/C++能开发出完整可用的游戏服务端.直到2005年,韩国的游戏很多都还是纯C++写服务端, ...

  8. 游戏服务端pomelo完整安装配置过程

    版权声明:本文为博主原创文章,转载或又一次发表请先与我联系. https://blog.csdn.net/jonahzheng/article/details/27658985 游戏服务端pomelo ...

  9. 俯瞰 Java 服务端开发

    原文首发于 github ,欢迎 star . Java 服务端开发是一个非常宽广的领域,要概括其全貌,即使是几本书也讲不完,该文将会提到许多的技术及工具,但不会深入去讲解,旨在以一个俯瞰的视角去探寻 ...

随机推荐

  1. Xmind-xss漏洞复现并上线Coblat-strike

    前言:本漏洞由WebRay烽火台实验室发现,Xmind2020存在xss漏洞,并且可以进行任意代码执行,目前官方还未进行补丁修复,所以请目前Xmind用户警惕Xmind文件. 本博客讲复现漏洞及上线到 ...

  2. Android开发失业六个月了,无限的焦虑

    最近到网上看到这样一个帖子: Android开发,坐标魔都:目前为止已经失业六个月,找工作期间,尤其是最近两天确实心态不好.要么没有面试,要么给的工资不符合预期( hr 压价太狠了,原先说的 19k, ...

  3. git的实用命令(撤回,合并)

    前言 在用开发项目的时候,经常会写着写着会发现写错的时候,人生没有后悔药,但是git有啊,大不了从头再来嘛. git的一些撤销操作 代码还没有存到暂存区 当我们修改了一个文件,还没有执行git add ...

  4. 21JavaScript笔记(1)

    JavaScript 基于对象和事件驱动 简单描述性语言 函数优先 解释型(即时编译型) 具有安全性的脚本语言 1.js组成 核心语法(ECMAScript):开放的.标准的脚本语言规范,主要包含了语 ...

  5. Git-07-分支管理

    创建与合并分支 为什么要创建分支? 假设你准备开发一个新功能,但是需要两周才能完成, 第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了. 如果等代码全部写完 ...

  6. CTF--[BJDCTF2020]Cookie is so stable 1(SSTI)

    从hint.php可以找到提示,要求观察cookies 打开flag.php可以看到需要输入用户名,多次试验后发现输入的用户名会以cookies的方式储存 使用dirsearch扫描没有发现什么有用的 ...

  7. VulnHub靶场渗透之:Gigachad

    环境搭建 VulnHub是一个丰富的实战靶场集合,里面有许多有趣的实战靶机. 本次靶机介绍: http://www.vulnhub.com/entry/gigachad-1,657/ 下载靶机ova文 ...

  8. null的坑 和 比较运算符、相等运算符的隐式转换问题 (在javascript中,null>=0 为真,null<=0 为真,null==0却为假,null到底是什么?)

    null在关系运算中的坑 & 关系运算符的隐式转换问题 注意: 比较运算符 和 相等运算符 的 ECMAscript 语法实现不同. 比较运算符 和 相等运算符 对数据进行了隐式转换, 相当于 ...

  9. 字符串匹配--Regex

    利用Regix实现字符串匹配 Eg:匹配嵌入到[]中的字符 string pattern = Regex.Escape("[") + "(.*?)]"; str ...

  10. git新建分支及提交代码到分支

    二.创建分支并提交代码到分支 上述添加成员的方式非常简单,但是如果说每一个小组成员都可以对仓库push内容,就涉及到一个代码的安全和冲突问题了,当多个成员同时在线编辑时容易出现冲突,假设A的代码是有问 ...