Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question]: 请教一下关于v2版本TCP粘包的问题 #495

Closed
3 tasks done
devzwy opened this issue Aug 24, 2023 · 4 comments
Closed
3 tasks done

[Question]: 请教一下关于v2版本TCP粘包的问题 #495

devzwy opened this issue Aug 24, 2023 · 4 comments
Labels
help wanted Extra attention is needed question Further information is requested waiting for response waiting for the response from commenter

Comments

@devzwy
Copy link

devzwy commented Aug 24, 2023

Actions I've taken before I'm here

  • I've thoroughly read the documentations about this problem but still have no answer.
  • I've searched the Github Issues/Discussions but didn't find any similar problems that have been solved.
  • I've searched the internet for this problem but didn't find anything helpful.

Questions with details

// OnTraffic 游戏服务器回传数据
func (server *GameServer) OnTraffic(c gnet.Conn) (action gnet.Action) {
	data, err := c.Next(-1)
}

请教下这里会有粘包问题出现,读取到的data不全,用咱们gnet框架需要手动处理粘包吗?
有没有类似Netty框架的socketChannel.pipeline().addLast("decoder", LengthFieldBasedFrameDecoder(1024 * 1024, 8, 2))这样的神器可以使用?我看其他的问题中似乎是v1版本的解码器的概念,在V2版本中似乎没有发现。
我是go的小白,还望大佬不吝赐教。
我当前使用的版本是:github.com/panjf2000/gnet/v2 v2.3.1

Code snippets (optional)

// OnTraffic 游戏服务器回传数据
func (server *GameServer) OnTraffic(c gnet.Conn) (action gnet.Action) {

	data, err := c.Next(-1)

	if err != nil {
		return gnet.Close
	}

	if len(data) < 10 {
		logger.Debugf("粘包1")
		return gnet.None
	}

	var msg beans.CustomMessage
	msg.AllData = data
	msg.Time = tools.ByteArrayToInt(data[4:8])
	msg.Size = tools.ByteArrayToInt(data[8:10])
	msg.Head = tools.ByteArrayToInt(data[10:12])
	if len(data) < int(msg.Size)+10 { // 数据不够,粘包
		logger.Debugf("粘包2")
		return gnet.None
	}

	if msg.Size == 0 {
		msg.Data = []byte{}
	} else {
		// 减去包头
		msg.Data = data[12 : 12+msg.Size-2]
	}

	player, err := GetConnectManager().getConnectManagerByServerConn(c)

	if err != nil {
		return gnet.Close
	}

	player.onServiceRespData(msg)
	return gnet.None
}
@devzwy devzwy added help wanted Extra attention is needed question Further information is requested labels Aug 24, 2023
@panjf2000
Copy link
Owner

v1 确实支持 codec,但是 v2 已经移除了 codec,详情可以看这里,因为 gnet 是一种轻量级的网络框架,而非 netty 那样大而全的框架,框架层的一些设置和使用上并不会很复杂,所以引入 codec 显得有点多余,其实最核心的就是 OnTraffic 回调函数,所以在该函数里给与用户最大的自由度,通过 Conn 的公开方法可以进行所需的所有操作,至于说通过 codec 进行分包和组包,完全可以在用户层自己封装一个函数,实现一样的功能,成本和 v1 那样设置一个 codec 接口实现并没有什么差别,可以参考例子

@panjf2000 panjf2000 added the waiting for response waiting for the response from commenter label Aug 24, 2023
@devzwy
Copy link
Author

devzwy commented Aug 25, 2023

@panjf2000 感谢回复,已经参考解码器实现了自己的粘包功能。但是在Windows和Linux平台测试时发现了端口占用导致段时间内无法重新启动监听的问题,在Mac平台下是正常的。

  • Mac(正常 ) 13.4.1 (c) (22F770820d)
  • Linux(异常) centos:7.6.1810
  • Windows(异常) 10

复现流程:启动8000端口监听,客户端链接到8000端口,然后关闭监听(无论客户端是否主动断开链接)就会出现段时间内无法再次启动监听的问题,报错是端口占用,但是使用lsof -i:8000是查不到有这个端口占用的进程,需要等待大约几十秒后才能再次启动。以下是我的部分代码。

func (cm *ConnectManager) StartGateway() {
	logger.Printf("启动中...")

	gConfig := config.GetConfig()

	// 启动线路、启动安全验证
	for _, gameLineData := range gConfig.GameLineConfig.Lines {
		go cm.start(gameLineData)
	}

	go cm.start(gConfig.AAAConfig.Aaa)

	// 创建一个信号通道
	signalCh := make(chan os.Signal, 1)

	// 监听指定的信号,例如 SIGINT(Ctrl+C)和 SIGTERM
	signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)

	// 阻塞等待信号
	<-signalCh

	// 停止监听
	cm.stopGateway()

	// 断开数据库链接
	UnInitDB()

	logger.Infof("网关已退出完成")
}
func (cm *ConnectManager) start(config beans.PortConfigBean) {
	server := &GatewayServer{
		PortConfig: config,
	}
	cm.gatewayServices = append(cm.gatewayServices, server)
	logger.Debugf("启动 %s (%d)", config.Name, config.ProxyPort)
	err := gnet.Run(server, fmt.Sprintf("tcp://:%d", server.PortConfig.ProxyPort), func(opts *gnet.Options) {
		opts.TCPNoDelay = gnet.TCPNoDelay
		opts.Multicore = true
		opts.SocketRecvBuffer = 1024 * 1024
		opts.SocketSendBuffer = 1024 * 1024
	})
	if err != nil {
		logger.Fatalf("%s(%d)启动失败,%s", config.Name, config.ProxyPort, err.Error())
	}

}
func (cm *ConnectManager) stopGateway() {

	for _, player := range cm.connections {
		player.sendCenterTip("#Y服务器断开链接", false)
		player.ClientConn.Close()
		player.ServerConn.Close()
	}

	for _, server := range cm.gatewayServices {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		logger.Debugf("关闭%s(%d)", server.PortConfig.Name, server.PortConfig.ProxyPort)
		server.Engine.Stop(ctx)
	}
}

期待大佬回复。

@panjf2000
Copy link
Owner

可以看这里 #485 (comment)

@devzwy
Copy link
Author

devzwy commented Aug 26, 2023

感谢。

@devzwy devzwy closed this as completed Aug 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed question Further information is requested waiting for response waiting for the response from commenter
Projects
None yet
Development

No branches or pull requests

2 participants