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

打开GoLand创建项目

编写Go程序:main.go

cs 复制代码
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

cs 复制代码
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

cs 复制代码
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

cs 复制代码
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

cs 复制代码
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)在脚本:

相关推荐
Rust研习社22 分钟前
Ubuntu 全面拥抱 Rust 后,我意识到 Rust 社区要变了
linux·服务器·开发语言·后端·ubuntu·rust
hahaha 1hhh1 小时前
用SSH 建立了一个本地端口转发隧道,用于安全地访问远程服务器上的服务,后台运行。autodl
服务器·安全·ssh
游乐码2 小时前
Unity坦克案例疑难记录(一)
unity·单例模式
小肝一下3 小时前
3.linux——进程控制
linux·运维·服务器·进程控制
北山有鸟3 小时前
linux设备全解析
linux·运维·服务器
fpcc3 小时前
Linux命令——lsof分析说明
linux·服务器
小贺儿开发3 小时前
Unity3D 编辑器对象锁定工具
unity·编辑器·编程·工具·对象·互动·拓展
cui_ruicheng4 小时前
Linux网络编程(二):网络数据传输基本流程
linux·服务器·网络
jiayong234 小时前
Memory 写入、检索与纠错机制:让 Agent 记住,也让它忘对
java·服务器·网络·hermes
雨田大大4 小时前
Windows11下IDEA运行后端时,端口被占用的解决方法
linux·运维·服务器