Skip to content

Latest commit

 

History

History
271 lines (212 loc) · 7.31 KB

sys-tcp-server.md

File metadata and controls

271 lines (212 loc) · 7.31 KB

TCP服务器

目录

  • 配置文件
  • 一个基本的消息收发例子
  • 注册路由
  • 拦截器
  • 服务认证
  • 更多

HotGo基于GoFrame的TCP服务器组件,提供了一个简单而灵活的方式快速搭建基于TCP的服务应用。集成了许多常用功能,如长连接、服务认证、路由分发、RPC消息、拦截器和数据绑定等,大大简化和规范了服务器开发流程。

配置文件

  • 配置文件:server/manifest/config/config.yaml
tcp:
  # 服务器
  server:
    address: ":8099"
  # 客户端
  client:
    # 定时任务
    cron:
      group: "cron"                                                 # 分组名称
      name: "cron1"                                                 # 客户端名称
      address: "127.0.0.1:8099"                                     # 服务器地址
      appId: "1002"                                                 # 应用名称
      secretKey: "hotgo"                                            # 密钥
    # 系统授权
    auth:
      group: "auth"                                                 # 分组名称
      name: "auth1"                                                 # 客户端名称
      address: "127.0.0.1:8099"                                     # 服务器地址
      appId: "mengshuai"                                            # 应用名称
      secretKey: "123456"                                           # 密钥
  • 可以看到,除了服务器配置外,还有两个客户端配置cronauth
  • cron是HotGo内置的定时任务服务,和http服务通过RPC通讯以实现和后台交互,使其可以独立、集群部署。
  • auth可以为第三方平台提供授权服务。如果你需要他,可以将它部署在第三方程序中,在重要的位置进行授权验证。

一个基本的消息收发测试用例

  • 文件路径:server/internal/library/network/tcp/tcp_example_test.go
package tcp_test

import (
	"context"
	"fmt"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gogf/gf/v2/test/gtest"
	"hotgo/internal/library/network/tcp"
	"testing"
	"time"
)

var T *testing.T // 声明一个全局的 *testing.T 变量

type TestMsgReq struct {
	Name string `json:"name"`
}

type TestMsgRes struct {
	tcp.ServerRes
}

type TestRPCMsgReq struct {
	Name string `json:"name"`
}

type TestRPCMsgRes struct {
	tcp.ServerRes
}

func onTestMsg(ctx context.Context, req *TestMsgReq) {
	fmt.Printf("服务器收到消息 ==> onTestMsg:%+v\n", req)
	conn := tcp.ConnFromCtx(ctx)
	gtest.C(T, func(t *gtest.T) {
		t.AssertNE(conn, nil)
	})

	res := new(TestMsgRes)
	res.Message = fmt.Sprintf("你的名字:%v", req.Name)
	conn.Send(ctx, res)
}

func onResponseTestMsg(ctx context.Context, req *TestMsgRes) {
	fmt.Printf("客户端收到响应消息 ==> TestMsgRes:%+v\n", req)
	err := req.GetError()
	gtest.C(T, func(t *gtest.T) {
		t.AssertNil(err)
	})
}

func onTestRPCMsg(ctx context.Context, req *TestRPCMsgReq) (res *TestRPCMsgRes, err error) {
	fmt.Printf("服务器收到消息 ==> onTestRPCMsg:%+v\n", req)
	res = new(TestRPCMsgRes)
	res.Message = fmt.Sprintf("你的名字:%v", req.Name)
	return
}

func startTCPServer() {
	serv := tcp.NewServer(&tcp.ServerConfig{
		Name: "hotgo",
		Addr: ":8002",
	})

	// 注册路由
	serv.RegisterRouter(
		onTestMsg,
	)

	// 注册RPC路由
	serv.RegisterRPCRouter(
		onTestRPCMsg,
	)

	// 服务监听
	err := serv.Listen()
	gtest.C(T, func(t *gtest.T) {
		t.AssertNil(err)
	})
}

// 一个基本的消息收发
func TestSendMsg(t *testing.T) {
	T = t
	go startTCPServer()

	ctx := gctx.New()
	client := tcp.NewClient(&tcp.ClientConfig{
		Addr: "127.0.0.1:8002",
	})

	// 注册路由
	client.RegisterRouter(
		onResponseTestMsg,
	)

	go func() {
		err := client.Start()
		gtest.C(T, func(t *gtest.T) {
			t.AssertNil(err)
		})
	}()

	// 确保服务都启动完成
	time.Sleep(time.Second * 1)

	// 拿到客户端的连接
	conn := client.Conn()
	gtest.C(T, func(t *gtest.T) {
		t.AssertNE(conn, nil)
	})

	// 向服务器发送tcp消息,不会阻塞程序执行
	err := conn.Send(ctx, &TestMsgReq{Name: "Tom"})
	gtest.C(T, func(t *gtest.T) {
		t.AssertNil(err)
	})

	// 向服务器发送rpc消息,会等待服务器响应结果,直到拿到结果或响应超时才会继续
	var res TestRPCMsgRes
	if err = conn.RequestScan(ctx, &TestRPCMsgReq{Name: "Tony"}, &res); err != nil {
		gtest.C(T, func(t *gtest.T) {
			t.AssertNil(err)
		})
	}

	fmt.Printf("客户端收到RPC消息响应 ==> TestRPCMsgRes:%+v\n", res)
	time.Sleep(time.Second * 1)
}

注册路由

  • 从上面的例子可以看到,不管是普通TCP消息和RPC消息的请求/响应结构体都采用类似GF框架的规范路由的结构,请求XxxRes/响应XxxRes的格式,是不是很亲切?

拦截器

  • 不管是服务端还是客户端,在初始化时都可以注册多个拦截器来满足更多场景的服务开发,下面是一个使用例子:
package main

import (
	"context"
	"github.com/gogf/gf/v2/frame/g"
	"hotgo/internal/library/network/tcp"
)

func main()  {
	serv = tcp.NewServer(&tcp.ServerConfig{
		Name: "hotgo",
		Addr: ":8002",
	})
	
	// 注册拦截器
	// 执行顺序是从前到后,即Interceptor -> Interceptor2 -> Interceptor3。如果中间有任意一个抛出错误,则会中断后续处理
	serv.RegisterInterceptor(Interceptor, Interceptor2, Interceptor3)

	// 服务监听
	if err := serv.Listen(); err != nil {
		if !serv.IsClose() {
			g.Log().Warningf(ctx, "TCPServer Listen err:%v", err)
		}
	}
}

func  Interceptor(ctx context.Context, msg *tcp.Message) (err error) {
	// 可以在拦截器中通过上下文拿到连接
	conn := tcp.ConnFromCtx(ctx)

	// 拿到原始请求消息
	g.Dump(msg)
	
	// 如果想要中断后续处理只需返回一个错误即可,但注意两种情况
	// tcp消息:如果你还想对该消息进行回复应在拦截器中进行处理,例如:conn.Send(ctx, 回复消息内容)
	// rpc消息:返回一个错误后系统会将错误自动回复到rpc响应中,无需单独处理
	return
}

func  Interceptor2(ctx context.Context, msg *tcp.Message) (err error) {
	// ...
	return
}

func  Interceptor3(ctx context.Context, msg *tcp.Message) (err error) {
	// ...
	return
}

服务认证

  • 一般情况下,建议客户端连接到服务器时都通过授权许可证的方式进行登录认证,当初始化客户端配置认证数据时,连接成功后会自动进行登录认证。
	// 创建客户端配置
	clientConfig := &tcp.ClientConfig{
		Addr:          "127.0.0.1:8002",
		AutoReconnect: true,
		// 认证数据
		// 认证数据可以在后台-系统监控-在线服务-许可证列表中添加,同一个授权支持多个服务使用,但多个服务不能使用相同的名称进行连接
		Auth: &tcp.AuthMeta{
			Name:      "服务名称",
			Group:     "服务分组",
			AppId:     "APPID",
			SecretKey: "SecretKey",
		},
	}

	// 初始化客户端
	client = tcp.NewClient(clientConfig)

更多

TCP服务器源码路径:server/internal/library/network/tcp

更多文档请参考:https://goframe.org/pages/viewpage.action?pageId=1114625