更新游戏服务器框架/组件包-KISS-新增集群示例工程

KISS - Keep It Simple & Stupid

MIT licensed
Go Report Card

  • KISS原则 是指在设计当中应当注重简约的原则

  • 作者水平有限,欢迎交流和指点,qq群: 817937655

安装

  • go get github.com/nothollyhigh/kiss/...

KISS可以用来做什么?

  • 有的人喜欢"框架"这个词,KISS的定位是提供一些基础组件方便搭积木实现架构方案,组件不限于项目类型

  • 作者主要从事游戏和web服务器开发,常用来构建游戏服务器,一些示例:

  1. 单进程服务器示例
  1. 服务器集群示例,另:集群是不同功能服务的拆分和实现,每个游戏的需求都可能不一样,请根据实际需求自行设计和实现
  1. kissgate网关,支持kiss格式的tcp/websocket连接反向代理到tcp服务,支持线路检测、负载均衡、realip等,常用来做游戏集群的网关,kiss协议格式详见 net包

KISS组件包简介

一、net,网络包

  1. Tcp
    可以用做游戏服务器,支持自定义协议格式、压缩、加密等

  2. Websocket
    可以用做游戏服务器,支持自定义协议格式、压缩、加密等

  3. Rpc
    可以灵活使用任意序列化、反序列化,给用户更多自由,如protobuf、json、msgpack、gob等
    支持服务端异步处理,服务端不必须在方法中处理完调用结果,可以异步处理结束后再发送结果
    不像GRPC等需要生成协议、按格式写那么多额外的代码,用法上像写 net/http 包的路由一样简单

  4. Http
    支持优雅退出、pprof等

二、log,日志包

  • 自己实现 log 包之前,我简单尝试过标准库的 log 和一些三方的日志包,但是对日志文件落地不太友好,
    比如日志文件按目录拆分、文件按时间和size切分、日志位置信息等

  • KISS 的 log 包日志支持:

  1. 日志位置信息,包括文件、行数
  2. 支持文件日志,支持bufio
  3. 文件日志按时间拆分目录
  4. 文件日志按时间格式切分
  5. 文件日志按size切分
  6. 支持钩子对日志做结构化或其他自定义处理

三、sync包

  • 开启debug支持死锁告警
    web相关无状态服务通常不需要锁,游戏逻辑多耦合,不小心可能导致死锁,可以用这个包来排查

  • WaitSession用法类似标准库的WaitGroup,但是可以指定session进行等待,支持超时

  • 详见 sync

四、timer,定时器

  • 标准库的 time.AfterFunc 触发时会创建一个协程来调用回调函数,大量定时器短时间内集中触发时开销较大

  • KISS 的 timer 是小堆实现的,一个实例只需要一个协程就可以管理大量定时器,支持同步异步接口
    主要用于优化标准库 time.AfterFunc 的协程开销,但要注意线头阻塞的问题
    耗时较长的定时器回调建议仍使用 time.AfterFunc

  • 详见 timer

五、event,事件包

  • 进程内的发布订阅组件,观察者模式,可以用于模块间解耦

  • 详见 event

六、util,杂货铺

  • 最常用的 Go,HandlePanic,Safe,处理panic,打印异常调用栈信息

  • Workers,任务池,用于控制一定数量的协程异步处理任务
    WorkersLink,也是任务池,但在Workers基础上做了一点扩展,支持异步处理的顺序组装

  • Qps,方便统计、打印一些qps功能

  • 详见 util

共 6 个回复


nothollyhigh

tcp echo server

package main

import (
	"github.com/nothollyhigh/kiss/log"
	"github.com/nothollyhigh/kiss/net"
	"time"
)

var ()

const (
	addr = ":8888"

	CMD_ECHO = uint32(1)
)

func onEcho(client *net.TcpClient, msg net.IMessage) {
	log.Info("tcp server onEcho from %v: %v", client.Conn.RemoteAddr().String(), string(msg.Body()))
	client.SendMsg(msg)
}

func main() {
	server := net.NewTcpServer("Echo")

	// 初始化协议号
	server.Handle(CMD_ECHO, onEcho)

	server.Serve(addr, time.Second*5)
}

tcp echo client

package main

import (
	"fmt"
	"github.com/nothollyhigh/kiss/log"
	"github.com/nothollyhigh/kiss/net"
	"time"
)

var (
	addr = "127.0.0.1:8888"

	CMD_ECHO = uint32(1)
)

func onConnected(c *net.TcpClient) {
	log.Info("TcpClient OnConnected")
}

func onEcho(client *net.TcpClient, msg net.IMessage) {
	log.Debug("tcp client onEcho from %v: %v", client.Conn.RemoteAddr().String(), string(msg.Body()))
}

func main() {
	autoReconn := true
	netengine := net.NewTcpEngine()

	// 初始化协议号
	netengine.Handle(CMD_ECHO, onEcho)

	client, err := net.NewTcpClient(addr, netengine, nil, autoReconn, onConnected)
	if err != nil {
		log.Panic("NewTcpClient failed: %v, %v", client, err)
	}

	for i := 0; true; i++ {
		err = client.SendMsg(net.NewMessage(CMD_ECHO, []byte(fmt.Sprintf("hello %v", i))))
		if err != nil {
			log.Error("tcp client echo failed: %v", err)
		}
		time.Sleep(time.Second)
	}
}
# 0

nothollyhigh

websocket echo server

package main

import (
	"github.com/nothollyhigh/kiss/log"
	"github.com/nothollyhigh/kiss/net"
)

var (
	addr = ":8888"

	CMD_ECHO = uint32(1)
)

func onEcho(client *net.WSClient, msg net.IMessage) {
	log.Info("ws server onEcho from %v: %v", client.Conn.RemoteAddr().String(), string(msg.Body()))
	client.SendMsg(msg)
}

func main() {
	server, err := net.NewWebsocketServer("echo", addr)
	if err != nil {
		log.Panic("NewWebsocketServer failed: %v", err)
	}

	// 初始化http ws路由
	server.HandleWs("/ws/echo")

	// 初始化协议号
	server.Handle(CMD_ECHO, onEcho)

	server.Serve()
}

websocket echo client

package main

import (
	"fmt"
	"github.com/nothollyhigh/kiss/log"
	"github.com/nothollyhigh/kiss/net"
	"time"
)

var (
	addr = "ws://localhost:8888/ws/echo"

	CMD_ECHO = uint32(1)
)

func onEcho(client *net.WSClient, msg net.IMessage) {
	log.Debug("ws client onEcho from %v: %v", client.Conn.RemoteAddr().String(), string(msg.Body()))
}

func main() {
	client, err := net.NewWebsocketClient(addr)
	if err != nil {
		log.Panic("NewWebsocketClient failed: %v, %v", err, time.Now())
	}

	// 初始化协议号
	client.Handle(CMD_ECHO, onEcho)

	for i := 0; true; i++ {
		err = client.SendMsg(net.NewMessage(CMD_ECHO, []byte(fmt.Sprintf("hello %v", i))))
		if err != nil {
			log.Error("ws client echo failed: %v", err)
			break
		}
		time.Sleep(time.Second)
	}
}

# 1

nothollyhigh

rpc server

package main

import (
	"github.com/nothollyhigh/kiss/log"
	"github.com/nothollyhigh/kiss/net"
	"time"
)

var (
	addr = "0.0.0.0:8888"
)

type HelloRequest struct {
	Message string
}

type HelloResponse struct {
	Message string
}

// Hello方法
func Hello(ctx *net.RpcContext) {
	req := &HelloRequest{}

	err := ctx.Bind(req)
	if err != nil {
		log.Error("Hello failed: %v", err)
		return
	}

	// 直接回包
	// err = ctx.Write(&HelloResponse{Message: req.Message})
	// if err != nil {
	// 	log.Error("Hello failed: %v", err)
	// 	return
	// }
	// log.Info("HelloRequest: %v", req.Message)

	// 支持异步回包
	go func() {
		err = ctx.Write(&HelloResponse{Message: req.Message})
		if err != nil {
			log.Error("Hello failed: %v", err)
			return
		}

		log.Info("HelloRequest: %v", req.Message)
	}()
}

func main() {
	server := net.NewRpcServer("Rpc")

	// 初始化方法,类似http初始化路由
	server.HandleRpcMethod("Hello", Hello)

	// 启动服务
	server.Serve(addr, time.Second*5)
}

rpc client

package main

import (
	"github.com/nothollyhigh/kiss/log"
	"github.com/nothollyhigh/kiss/net"
	"time"
)

var (
	addr = "0.0.0.0:8888"
)

type HelloRequest struct {
	Message string
}

type HelloResponse struct {
	Message string
}

func onConnected(c *net.TcpClient) {
	log.Info("RpcClient OnConnected")
}

func main() {
	engine := net.NewTcpEngine()
	client, err := net.NewRpcClient(addr, engine, nil, onConnected)
	if err != nil {
		log.Panic("NewReqClient Error: %v", err)
	}

	for {
		req := &HelloRequest{Message: "kiss"}
		rsp := &HelloResponse{}

		// 调用Hello方法
		err = client.Call("Hello", req, rsp, time.Second*3)
		if err != nil {
			log.Error("Hello failed: %v", err)
		} else {
			log.Info("HelloResponse: %v", rsp.Message)
		}

		time.Sleep(time.Second)
	}
}
# 2

lesliehuang

我偷偷来水一下,顺便看看IV能不能插入视频。
下面(可能会)是我这个非常菜的PV随便做的蝴蝶~
重庆时时彩幸运飞艇北京pk10视频床是我自建的,完善以后应该会对论坛里的小伙伴们开放~

# 5