『网络游戏』GoLand服务器框架【01】

news2024/11/18 15:48:38

打开GoLand创建项目

编写Go程序:main.go

package main

import (
	"fmt"
	"newgame/game/gate"
	"os"
	"os/signal"
	"syscall"
	"time"
)

var (
	SinChan   = make(chan os.Signal, 1)
	closeChan chan struct{}
)

func main() {
	//信号通道,用于接收系统信号
	signal.Notify(SinChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	//逻辑更新定时器,每66毫秒更新一次逻辑
	logicTicker := time.NewTicker(66 * time.Millisecond)
	//关闭通道,用于通知程序退出
	closeChan := make(chan struct{})
	//退出通道,用于通知程序退出完成
	exitChan := make(chan struct{})
	go func() {
		defer func() {
			exitChan <- struct{}{}
		}()
		//程序初始化地方,可以初始化一些全局变量,或者启动一些协程
		gs := gate.NewGate()
		gs.Run()
		//程序大的循环,处理信号,逻辑更新。。。
		for {
			select {
			//接收到关闭通道,退出程序
			case <-closeChan:
				goto QUIT
				//接受到系统信号,处理信号
			case sig := <-SinChan:
				fmt.Println("receive signal:", sig)
				close(closeChan)
			case <-logicTicker.C:
				//逻辑更新
				//fmt.Println("logic update")
			}
		}
	QUIT:
		//处理程序退出逻辑
		return
	}()
	//等待程序退出
	<-exitChan
}

编写Go程序:server.go

package network

import (
	"net"
	"sync"
)

// Server网络服务器
type Server struct {
	callback  ConnCallback  //每一个连接的回调
	protocol  Protocol      //通用协议,用于解析网络协议,处理粘包问题,可以自定义
	exitChan  chan struct{} //退出信号,用于通知服务器退出
	waitGroup *sync.WaitGroup
	closeOnce sync.Once
	listener  net.Listener //监听器
}

// newServer 创建一个网络服务器
func NewServer(callback ConnCallback, protocol Protocol) *Server {
	return &Server{
		callback:  callback,
		protocol:  protocol,
		exitChan:  make(chan struct{}),
		waitGroup: &sync.WaitGroup{},
	}
}

type ConnectionCreator func(conn net.Conn, src *Server) *Connection

// start 启动服务器
func (s *Server) Start(listener net.Listener, create ConnectionCreator) {
	s.listener = listener
	s.waitGroup.Add(1)
	defer func() {
		s.waitGroup.Done()
	}()
	for {
		select {
		case <-s.exitChan:
			return
		default:
		}
		conn, err := listener.Accept()
		if err != nil {
			break
		}
		s.waitGroup.Add(1)
		go func() {
			create(conn, s).Do()
			s.waitGroup.Done()
		}()
	}
}

// Stop停止服务器
func (s *Server) Stop(wait bool) {
	s.closeOnce.Do(func() {
		close(s.exitChan)
		s.listener.Close()
	})
	if wait {
		s.waitGroup.Wait()
	}
}

编写Go程序:protocol.go

package network

import (
	"encoding/binary"
	"errors"
	"io"
)

// 网络消息序列化接口
type Packet interface {
	Serialize() []byte
}

// 网络协议读取的接口
type Protocol interface {
	ReadPacket(conn io.Reader) (Packet, error)
}

// 程序默认协议结构
type DefaultPacket struct {
	buff []byte
}

// 实现Packet接口
func (dp *DefaultPacket) Serialize() []byte {
	return dp.buff
}

// 获取消息体的二进制数据
func (db *DefaultPacket) GetBody() []byte {
	return db.buff[4:]
}

// 创建一个默认协议的消息包
func NewDefaultPacket(buff []byte) *DefaultPacket {
	p := &DefaultPacket{}
	p.buff = make([]byte, 4+len(buff))
	binary.BigEndian.PutUint32(p.buff[:4], uint32(len(buff)))
	//拷贝消息体
	copy(p.buff[4:], buff)
	return p
}

// 默认协议解析
type DefaultProtocol struct {
}

// 实现接口
func (dp *DefaultProtocol) ReadPacket(conn io.Reader) (Packet, error) {
	var (
		lengthBytes []byte = make([]byte, 4)
		length      uint32
	)
	//读取消息长度
	if _, err := io.ReadFull(conn, lengthBytes); err != nil {
		return nil, err
	}
	if length = binary.BigEndian.Uint32(lengthBytes); length > 1024 {
		return nil, errors.New("the size of packet is too large")
	}
	//读取消息体
	buff := make([]byte, length)
	if _, err := io.ReadFull(conn, buff); err != nil {
		return nil, err
	}
	return NewDefaultPacket(buff), nil
}

编写Go程序:protocol.go

package network

import (
	"errors"
	"net"
	"sync"
	"sync/atomic"
	"time"
)

// 定义错误
var (
	ErrConnClosing   = errors.New("Connection is closing")
	ErrWriteBlocking = errors.New("write packet is blocking")
	ErrReadBlocking  = errors.New("read packet is blocking")
)

type Connection struct {
	srv               *Server
	conn              net.Conn      //原始链接
	extraData         interface{}   //额外的数据
	closeOnce         sync.Once     //关闭链接
	closeFlag         int32         //关闭标志
	closeChan         chan struct{} //关闭的信号
	packetSendChan    chan Packet   //发送消息的通道
	callback          ConnCallback  //回调的接口
	packetReceiveChan chan Packet   //接收消息的通道
	fd                uint64        //链接的唯一标识
}

// ConnCallback 每一个网络链接回调的接口
type ConnCallback interface {
	//OnConnect 当有新的链接进来时的回调,true为接收,false拒绝
	OnConnect(*Connection) bool
	//OnMessage当读取到一个完整地游戏逻辑消息时被调用,true继续处理,false关闭
	OnMessage(*Connection, Packet) bool
	OnClose(*Connection)
}

func NewConnection(conn net.Conn, srv *Server) *Connection {
	c := &Connection{
		srv:               srv,
		callback:          srv.callback,
		conn:              conn,
		closeChan:         make(chan struct{}),
		packetSendChan:    make(chan Packet, 100),
		packetReceiveChan: make(chan Packet, 100),
	}
	if s, ok := conn.(*net.TCPConn); !ok {
		panic("conn is not")
	} else {
		c.fd = uint64(s.RemoteAddr().(*net.TCPAddr).Port)
	}
	return c
}

// GetFd 获取连接的唯一标识
func (c *Connection) GetFd() uint64 {
	return c.fd
}

// close关闭连接
func (c *Connection) Close() {
	c.closeOnce.Do(func() {
		atomic.StoreInt32(&c.closeFlag, 1)
		close(c.closeChan)
		close(c.packetSendChan)
		close(c.packetReceiveChan)
		c.conn.Close()
		c.callback.OnClose(c)
	})
}

// 判断链接是否关闭
func (c *Connection) IsClosed() bool {
	return atomic.LoadInt32(&c.closeFlag) == 1
}

// 设置连接的回调
func (c *Connection) SetCallback(callback ConnCallback) {
	c.callback = callback
}

// / AsyncWritePacket 异步写入一个数据包,如果超时则返回错误
func (c *Connection) AsyncWritePacket(p Packet, timeout time.Duration) (err error) {
	if c.IsClosed() {
		return ErrConnClosing
	}
	defer func() {
		if e := recover(); e != nil {
			err = ErrWriteBlocking
		}
	}()
	if timeout == 0 {
		select {
		case c.packetSendChan <- p:
			return nil
		default:
			return ErrWriteBlocking
		}
	} else {
		select {
		case c.packetSendChan <- p:
			return nil
		case <-time.After(timeout):
			return ErrWriteBlocking
		case <-c.closeChan:
			return ErrConnClosing
		}
	}
}
func (c *Connection) Do() {
	if !c.callback.OnConnect(c) {
		return
	}
	asyncDo(c.handLoop, c.srv.waitGroup)
	asyncDo(c.readLoop, c.srv.waitGroup)
	asyncDo(c.writeLoop, c.srv.waitGroup)
}
func asyncDo(fn func(), wg *sync.WaitGroup) {
	wg.Add(1)
	go func() {
		fn()
		wg.Done()
	}()
}
func (c *Connection) readLoop() {
	defer func() {
		recover()
		c.Close()
	}()
	for {
		select {
		case <-c.srv.exitChan:
			return
		case <-c.closeChan:
			return
		default:
		}
		c.conn.SetReadDeadline(time.Now().Add(time.Second * 180))
		p, err := c.srv.protocol.ReadPacket(c.conn)
		if err != nil {
			return
		}
		c.packetReceiveChan <- p
	}
}

// 写数据包到链接
func (c *Connection) writeLoop() {
	defer func() {
		recover()
		c.Close()
	}()
	for {
		select {
		case <-c.srv.exitChan:
			return
		case <-c.closeChan:
			return
		case p := <-c.packetSendChan:
			if c.IsClosed() {
				return
			}
			c.conn.SetWriteDeadline(time.Now().Add(time.Second * 180))
			if _, err := c.conn.Write(p.Serialize()); err != nil {
				return
			}
		}
	}
}
func (c *Connection) handLoop() {
	defer func() {
		recover()
		c.Close()
	}()
	for {
		select {
		case <-c.srv.exitChan:
			return
		case <-c.closeChan:
			return
		case p := <-c.packetReceiveChan:
			if c.IsClosed() {
				return
			}
			if !c.callback.OnMessage(c, p) {
				return
			}
		}
	}
}

编写Go程序:gate.go

package gate

import (
	"fmt"
	"net"
	"newgame/game/network"
)

// 网关服务
func NewGate() *Gate {
	return &Gate{}

}

type Gate struct {
	listener net.Listener
}

// Run启动网关服务器
func (g *Gate) Run() {
	l, e := net.Listen("tcp", ":10087")
	if e != nil {
		panic(e.Error())
	}
	g.listener = l
	fmt.Printf("Linten on %s\n", l.Addr().String())
	server := network.NewServer(g, &network.DefaultProtocol{})
	go server.Start(l, func(conn net.Conn, i *network.Server) *network.Connection {
		return network.NewConnection(conn, server)
	})
}

// Stop停止网关服务器
func (g *Gate) Stop() {
	err := g.listener.Close()
	if err != nil {
		panic(err.Error())
	}
}

// OnConnect 连接建立回调
func (g *Gate) OnConnect(conn *network.Connection) bool {
	fmt.Printf("new connection:%d\n", conn.GetFd())
	return true
}

// OnClose 连接关闭回调
func (g *Gate) OnClose(conn *network.Connection) {

}
func (g *Gate) OnMessage(conn *network.Connection, p network.Packet) bool {
	return true
}

安装telnet

确定安装即可

Windows + R 输入cmd 打开命令框

输入 telnet 127.0.0.1 10087

(其中10087是代码中所写)

GoLand输出显示Debug信息即服务器连接成功

其中输出的Debug信息(new connection:12345)在脚本:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2173838.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaEE】——线程池大总结

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c; 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;希望本文内容能够帮助到你&#xff01; 目录 引入&#xff1a;问题引入 一&#xff1a;解决方案 1&#xff1a;方案一——协程/纤程 &#xff08;1…

SwiftUI简明概念(3):Path.addArc的clockwise方向问题

一、画个下半圆 SwiftUI中绘制下半圆的一个方法是使用Path.addArc&#xff0c;示例代码如下&#xff1a; var body: some View {Path { path inpath.addArc(center: CGPoint(x: 200, y: 370), radius: 50, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 180.0), …

谷歌发布Imagen 3,超过SD3、DALL・E-3,谷歌发布新RL方法,性能提升巨大,o1模型已证明

谷歌发布Imagen 3&#xff0c;超过SD3、DALL・E-3&#xff0c;谷歌发布新RL方法&#xff0c;性能提升巨大&#xff0c;o1模型已证明。 谷歌DeepMind发布了全新文生图模型Imagen 3&#xff0c;在文本语义还原、色彩搭配、文本嵌入、图像细节、光影效果等方面相比第二代大幅度提升…

2024新版IDEA创建JSP项目

1. 创建项目 依次点击file->new->Project 配置如下信息并点击create创建项目 2. 配置Web项目 点击file->Project Structure 在点击Project Settings->Module右键右边模块名称->ADD->Web 点击Create Artifact 出现如下界面就表示配置完毕&#xff0c;…

3.整数二分

模板 package base;public class Bsearch {public int binary_search1(int l, int r){while (l<r){int mid (lr1)>>1;if(check(mid)) lmid;else r mid-1;}return l;}public int binary_search2(int l, int r){while (l<r){int mid (lr)>>1;if (check(mid…

Python酷库之旅-第三方库Pandas(129)

目录 一、用法精讲 576、pandas.DataFrame.merge方法 576-1、语法 576-2、参数 576-3、功能 576-4、返回值 576-5、说明 576-6、用法 576-6-1、数据准备 576-6-2、代码示例 576-6-3、结果输出 577、pandas.DataFrame.update方法 577-1、语法 577-2、参数 577-3、…

实操了 AI 大模型项目落地, 程序员成功转变为 AI 大模型工程师

根据《2024 年全球人工智能行业报告》最新的数据显示&#xff0c;全球 AI 市场预计将以每年超过 40% 的速度增长&#xff0c;到 2030 年市值将达到数万亿美元&#xff0c;这也是预示着在接下来的十年到十五年里&#xff0c;人工智能将获得巨大的发展红利。 在过去的一年多时间…

如何配置flutter(超详细的哦)

目录 首先先去官网下载zip包 下载下来之后就是解压 配置环境变量 winr查看是否配置成功 解决报错 [!] Android toolchain - develop for Android devices (Android SDK version 35.0.0)X cmdline-tools component is missing Android license status unknown 首先先去官…

docker pull 超时的问题如何解决

docker不能使用&#xff0c;使用之前的阿里云镜像失败。。。 搜了各种解决方法&#xff0c;感谢B站UP主 <iframe src"//player.bilibili.com/player.html?isOutsidetrue&aid113173361331402&bvidBV1KstBeEEQR&cid25942297878&p1" scrolling"…

力扣 简单 111.二叉树的最小深度

文章目录 题目介绍题解 题目介绍 题解 最小深度&#xff1a;从根节点到最近叶子结点的最短路径上节点数量 class Solution {public int minDepth(TreeNode root) {if (root null) {return 0;}int left minDepth(root.left);int right minDepth(root.right);// 如果 node 没…

处理not in gzip format异常

1、为什么会触发这个异常&#xff1f; 当我们使用GZIPInputStream的read方法进行读取数据时&#xff0c;它会自动处理gzip格式的压缩数据&#xff0c;将它解析成原始的二进制数据。但是&#xff0c;如果你没有将原始数据进行gzip压缩后传入GZIPInputStream流&#xff0c;进行r…

JavaEE——多线程Thread 类及常见方法

目录 一、Thread(String name) 二、是否后台线程 isDeamon() 三、是否存活 isAlive() 四、run()方法和start()方法的区别 五、中断线程 法一&#xff1a; 法二&#xff1a; 六、线程等待join() 七、线程休眠sleep() 一、Thread(String name) 定义&#xff1a;这个东西…

免杀对抗—C++混淆算法shellcode上线回调编译执行

前言 上次讲了python混淆免杀&#xff0c;今天讲一下C混淆免杀。其实都大差不差的&#xff0c;也都是通过各种算法对shellcod进行混淆免杀&#xff0c;只不过是语言从python换成c了而已。 实验环境 测试环境依旧是360、火绒、WD还有VT。 shellcode上线 下面是最基本几个sh…

数据恢复篇:如何恢复几年前删除的照片

您是否曾经遇到过几年前删除了一张图片并觉得需要恢复旧照片的情况&#xff1f;虽然&#xff0c;没有确定的方法可以丢失或删除的照片。但是&#xff0c;借助奇客数据恢复等恢复工具&#xff0c;可以恢复多年前永久删除的照片、视频和音频文件。 注意 – 如果旧数据被覆盖&…

差速轮纯跟踪算法

fig.1 差速轮纯跟踪原理图 纯跟踪是基于几何关系的跟踪控制算法&#xff0c;不管是阿克曼模型&#xff0c;还是差速轮模型&#xff0c;都是控制机器驱动轮&#xff08;通常是后轮&#xff09;中心经过目标点 T。 基于机器驱动轮中心&#xff0c;车头朝向为 X 轴正方向&#xf…

ESP32 Bluedroid 篇(1)—— ibeacon 广播

前言 前面我们已经了解了 ESP32 的 BLE 整体架构&#xff0c;现在我们开始实际学习一下Bluedroid 从机篇的广播和扫描。本文将会以 ble_ibeacon demo 为例子进行讲解&#xff0c;需要注意的一点是。ibeacon 分为两个部分&#xff0c;一个是作为广播者&#xff0c;一个是作为观…

小徐影院:Spring Boot影院管理新体验

第三章 系统分析 整个系统的功能模块主要是对各个项目元素组合、分解和更换做出对应的单元&#xff0c;最后在根据各个系统模块来做出一个简单的原则&#xff0c;系统的整体设计是根据用户的需求来进行设计的。为了更好的服务于用户要从小徐影城管理系统的设计与实现方面上做出…

24年下重庆事业单位考试报名超详细流程

&#x1f388;提交报考申请 考生通过重庆市人力资源和社会保障局官网&#xff08;rlsbj.cq.gov.cn&#xff09;“热点服务”中“人事考试网上报名”栏进行报名。报名时间为2024年8月12日9:00—8月17日9:00。 &#x1f388;网上缴费 资格初审合格后&#xff0c;考生应在2024年8…

【Python】1.初始Python--打开Python的大门

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

数据结构:排序(内部排序+各种排序算法的性质总结)

数据结构的排序是计算机科学中的一个基本且重要的操作&#xff0c;它指的是将一组数据元素&#xff08;或记录&#xff09;按照一定规则&#xff08;通常是关键字的大小&#xff09;进行排列的过程。排序后的数据元素在物理或逻辑上呈现出某种顺序性&#xff0c;从而便于后续的…