Zinx-Golang轻量级TCP服务器框架(1)

在这里插入图片描述

Server

抽象层

// 定义一个服务器接口
type IServer interface {
	//启动服务器
	Start()
	//停止服务器
	Stop()
	//运行服务器
	Server()
	//路由功能:给当前服务注册一个路由方法,供客户端的连接处理使用
	AddRouter(msgID uint32, router IRouter)
	//获取当前server的连接管理器
	GetConnMgr() IConnManager
	//注册OnConnStart 钩子函数的方法
	SetOnConnStart(func(connection IConnection))
	//注册OnConnStop 钩子函数的方法
	SetOnConnStop(func(connection IConnection))
	//调用OnConnStart 钩子函数的方法
	CallOnConnStart(connection IConnection)
	//调用OnConnStop 钩子函数的方法
	CallOnConnStop(connection IConnection)
}

实现层

// IServer的接口实现,定义一个Server的服务器模块
type Server struct {
	//服务器的名称
	Name string
	//服务器绑定的ip版本
	IPVersion string
	//服务器监听的IP
	IP string
	//服务器监听的接口
	Port int
	//当前server的消息管理模块,用来绑定MsgID和对应的处理业务API关系
	MsgHandler ziface.IMsgHandle
	//该server的连接管理器
	ConnMgr ziface.IConnManager
	//该Server创建链接后自动调用Hook函数--OnConnStart
	OnConnStart func(conn ziface.IConnection)
	//该Server销毁链接前自动调用Hook函数--OnConnStop
	OnConnStop func(conn ziface.IConnection)
}

//func CallBackToClient(conn *net.TCPConn, data []byte, cnt int) error {
//	//回显业务
//	fmt.Println("[Conn Handle] CallBackToClient...")
//	if _, err := conn.Write(data[:cnt]); err != nil {
//		fmt.Println("write back buf err", err)
//		return errors.New("CallBackToClient error")
//	}
//	return nil
//}

func (s *Server) Start() {
	fmt.Printf("[Zinx] Server Name : %s, listenner at IP : %s, Port:%d is starting\n",
		utils.GlobalObject.Name, utils.GlobalObject.Host, utils.GlobalObject.TcpPort)
	fmt.Printf("[Zinx] Version %s, MaxConn %d, MaxPackageSize %d\n",
		utils.GlobalObject.Version, utils.GlobalObject.MaxConn, utils.GlobalObject.MaxPackageSize)
	//1 获取一个TCP的Addr
	go func() {
		//开启工作池
		s.MsgHandler.StartWorkerPool()

		addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
		if err != nil {
			fmt.Println("resolve tcp addr error : ", err)
			return
		}
		//2 监听服务器的地址
		listenner, err := net.ListenTCP(s.IPVersion, addr)
		if err != nil {
			fmt.Println("listen : ", s.IPVersion, "err : ", err)
			return
		}
		fmt.Println("start Zinx server success,", s.Name, " Listenning")

		var cid uint32
		cid = 0
		//3 阻塞的等待客户端连接,处理客户端连接业务
		for {
			//如果有客户端链接过来,阻塞会返回
			conn, err := listenner.AcceptTCP()
			if err != nil {
				fmt.Println("Accept err : ", err)
				continue
			}
			//判断最大连接个数
			if s.ConnMgr.Len() >= utils.GlobalObject.MaxConn {
				//TODO 给客户端响应一个超出最大连接的错误包
				fmt.Println("Too Many Conn = ", utils.GlobalObject.MaxConn)
				conn.Close()
				continue
			}
			//conn和业务方法绑定
			dealConn := NewConnection(s, conn, cid, s.MsgHandler)
			cid++

			//启动当前链接业务处理
			go dealConn.Start()
		}
	}()
}

func (s *Server) Stop() {
	//TODO 将一些服务器的资源、状态或者一些已经开辟的连接信息 停止或者回收
	fmt.Println("[STOP] Zinx server name ", s.Name)
	s.ConnMgr.ClearConn()
}

func (s *Server) Server() {
	//启动server服务功能
	s.Start()

	//TODO 做一些启动服务器之后的额外业务

	//阻塞状态
	select {}
}

func (s *Server) AddRouter(msgID uint32, router ziface.IRouter) {
	s.MsgHandler.AddRouter(msgID, router)
	fmt.Println("add router success")
}

func (s *Server) GetConnMgr() ziface.IConnManager {
	return s.ConnMgr
}

// 初始化Server模块的方法
func NewServer(name string) ziface.IServer {
	s := &Server{
		Name:       utils.GlobalObject.Name,
		IPVersion:  "tcp4",
		IP:         utils.GlobalObject.Host,
		Port:       utils.GlobalObject.TcpPort,
		MsgHandler: NewMsgHandle(),
		ConnMgr:    NewConnManager(),
	}
	return s
}

// 注册OnConnStart 钩子函数的方法
func (s *Server) SetOnConnStart(hookFunc func(connection ziface.IConnection)) {
	s.OnConnStart = hookFunc
}

// 注册OnConnStop 钩子函数的方法
func (s *Server) SetOnConnStop(hookFunc func(connection ziface.IConnection)) {
	s.OnConnStop = hookFunc
}

// 调用OnConnStart 钩子函数的方法
func (s *Server) CallOnConnStart(conn ziface.IConnection) {
	if s.OnConnStart != nil {
		fmt.Println("----> Call OnConnStart()...")
		s.OnConnStart(conn)
	}
}

// 调用OnConnStop 钩子函数的方法
func (s *Server) CallOnConnStop(conn ziface.IConnection) {
	if s.OnConnStop != nil {
		fmt.Println("----> Call OnConnStop()...")
		s.OnConnStop(conn)
	}
}

  • Start():开启一个阻塞的go程负责绑定、监听、端口,同时开启工作池,当服务器监听到一个新的connection,则会判断是否超过最大连接,将conn与业务方法绑定,再开启一个go程处理业务,这样充分的利用了go程的高并发性。
  • Server():由于start是异步方法,因此server中需要阻塞。在这之间也可以做扩展工作。

Connection

抽象层

// 定义链接的抽象层
type IConnection interface {
	//启动链接 让当前链接准备开始工作
	Start()
	//停止链接 结束当前链接的工作
	Stop()
	//获取当前链接的绑定socket conn
	GetTCPConnection() *net.TCPConn
	//获取当前连接模块的链接ID
	GetConnID() uint32
	//获取远程客户端的TCP状态 IP port
	RemoteAddr() net.Addr
	//发送数据,将数据发送给远程的客户端
	SendMsg(msgId uint32, data []byte) error
	//设置链接属性
	SetProperty(key string, value interface{})
	//获取链接属性
	GetProperty(key string) (interface{}, error)
	//移除链接属性
	RemoveProperty(key string)
}

// 定义一个处理链接业务的方法
type HandleFunc func(*net.TCPConn, []byte, int) error

实现层

type Connection struct {
	//当前Conn属于哪个Server
	TcpServer ziface.IServer
	//当前链接的socket TCP套接字
	Conn *net.TCPConn
	//链接的ID
	ConnID uint32
	//当前的链接状态
	isClosed bool
	//当前的链接所绑定的处理业务方法API
	handleAPI ziface.HandleFunc
	//告知当前链接已经退出 的channel
	ExitChan chan bool
	//无缓冲管道,用于读、写goroutine之间消息通信
	msgChan chan []byte
	//当前server的消息管理模块,用来绑定MsgID和对应的处理业务API关系
	MsgHandler ziface.IMsgHandle
	//链接属性集合
	property map[string]interface{}
	//保护链接属性的锁
	propertyLock sync.RWMutex
}

// 初始化链接模块的方法
func NewConnection(server ziface.IServer, conn *net.TCPConn, connID uint32, MsgHandler ziface.IMsgHandle) *Connection {
	c := &Connection{
		TcpServer: server,
		Conn:      conn,
		ConnID:    connID,
		isClosed:  false,
		//handleAPI: callback_api,
		MsgHandler: MsgHandler,
		ExitChan:   make(chan bool, 1),
		msgChan:    make(chan []byte),
		property:   make(map[string]interface{}),
	}

	c.TcpServer.GetConnMgr().Add(c)

	return c
}

// 链接的读业务方法
func (c *Connection) StartReader() {
	fmt.Println("[Reader Goroutine is running...]")
	defer fmt.Println("connID = ", c.ConnID, "Reader is exit,remote addr is ", c.RemoteAddr().String())
	defer c.Stop()

	for {
		//buf := make([]byte, utils.GlobalObject.MaxPackageSize)
		//_, err := c.Conn.Read(buf)
		//if err != nil {
		//	fmt.Println("recv buf err,", err)
		//	continue
		//}
		//创建一个拆包对象
		dp := NewDataPack()
		//读取客户端的Msg Head 二进制流 8个字节
		headData := make([]byte, dp.GetHeadLen())
		if _, err := io.ReadFull(c.GetTCPConnection(), headData); err != nil {
			fmt.Println("read msg head error,", err)
			break
		}
		//拆包,得到msgID、msgDatalen放在msg消息中
		msg, err := dp.Unpack(headData)
		if err != nil {
			fmt.Println("unpack error", err)
			break
		}
		//根据dataLen 读取data
		var data []byte
		if msg.GetMsgLen() > 0 {
			data = make([]byte, msg.GetMsgLen())
			if _, err := io.ReadFull(c.GetTCPConnection(), data); err != nil {
				fmt.Println("read msg body error,", err)
				break
			}
		}
		msg.SetMsg(data)
		//得到当前conn数据的Request请求数据
		req := Request{
			conn: c,
			msg:  msg,
		}

		if utils.GlobalObject.WorkerPoolSize > 0 {
			c.MsgHandler.SendMsgToTaskQueue(&req)
		} else {
			go c.MsgHandler.DoMsgHandler(&req)
		}

	}
}

// 写消息goroutine,专门发消息给客户端模块
func (c *Connection) StartWriter() {
	fmt.Println("[Writer Goroutine is running...]")
	defer fmt.Println(c.RemoteAddr().String(), "[conn writer exit]")

	for {
		select {
		case data := <-c.msgChan:
			//写数据给客户端
			if _, err := c.Conn.Write(data); err != nil {
				fmt.Println("send data error , ", err)
				return
			}
		case <-c.ExitChan:
			//代表Reader已经退出,此时Writer也要退出
			return
		}
	}
}

// 启动链接 让当前链接准备开始工作
func (c *Connection) Start() {
	fmt.Println("Conn Start... ConID = ", c.ConnID)

	//启动从当前链接的读数据的业务
	go c.StartReader()
	//启动当前写业务
	go c.StartWriter()
	//按照开发者传递进来的 创建链接之后需要调用的处理业务,执行对应的Hook函数
	c.TcpServer.CallOnConnStart(c)
}

// 停止链接 结束当前链接的工作
func (c *Connection) Stop() {
	fmt.Println("Conn Stop... ConID = ", c.ConnID)

	if c.isClosed == true {
		return
	}
	c.isClosed = true

	//调用开发者注册的销毁链接之前需要执行的业务Hook函数
	c.TcpServer.CallOnConnStop(c)

	c.Conn.Close()

	c.ExitChan <- true

	c.TcpServer.GetConnMgr().Remove(c)

	close(c.ExitChan)
	close(c.msgChan)

}

// 获取当前链接的绑定socket conn
func (c *Connection) GetTCPConnection() *net.TCPConn {
	return c.Conn
}

// 获取当前连接模块的链接ID
func (c *Connection) GetConnID() uint32 {
	return c.ConnID
}

// 获取远程客户端的TCP状态 IP port
func (c *Connection) RemoteAddr() net.Addr {
	return c.Conn.RemoteAddr()
}

// 发送数据,将数据发送给远程的客户端
func (c *Connection) SendMsg(msgId uint32, data []byte) error {
	if c.isClosed == true {
		return errors.New("conn closed when send msg")
	}

	dp := NewDataPack()
	binaryMsg, err := dp.Pack(NewMsgPackage(msgId, data))
	if err != nil {
		fmt.Println("pack error msg id = ", msgId)
		return errors.New("pack error msg")
	}

	//将消息发送给管道
	c.msgChan <- binaryMsg

	return nil
}

// 设置链接属性
func (c *Connection) SetProperty(key string, value interface{}) {
	c.propertyLock.Lock()
	defer c.propertyLock.Unlock()

	c.property[key] = value
}

// 获取链接属性
func (c *Connection) GetProperty(key string) (interface{}, error) {
	c.propertyLock.RLock()
	defer c.propertyLock.RUnlock()

	if value, ok := c.property[key]; ok {
		return value, nil
	}
	return nil, errors.New("no property found")

}

// 移除链接属性
func (c *Connection) RemoveProperty(key string) {
	c.propertyLock.Lock()
	defer c.propertyLock.Unlock()

	delete(c.property, key)
}
  • 当启动Start()时,异步开启俩个go程,一个读,一个写。
  • 读go程,首先将数据进行制定格式拆包,对数据进行request封装,然后用绑定的业务函数处理。
  • 写go程,专门发消息给客户端模块。

Router

抽象层

type IRequest interface {
	GetConnection() IConnection

	GetData() []byte

	GetMsgId() uint32
}

type IRouter interface {
	PreHandle(request IRequest)

	Handle(rqeuest IRequest)

	PostHandle(request IRequest)
}

实现层

type Request struct {
	conn ziface.IConnection

	msg ziface.IMessage
}

func (r *Request) GetConnection() ziface.IConnection {
	return r.conn
}

func (r *Request) GetData() []byte {
	return r.msg.GetMsg()
}

func (r *Request) GetMsgId() uint32 {
	return r.msg.GetMsgId()
}


type BaseRouter struct {
}

// 这里之所以BaseRouter方法都为空
// 是因为有的Router不希望有PreHandle、PostHandle这俩个业务
// 所以Router全部继承BaseRouter的好处就是,不需要实现PreHandle,PostHandle
func (br *BaseRouter) PreHandle(request ziface.IRequest) {

}

func (br *BaseRouter) Handle(request ziface.IRequest) {

}

func (br *BaseRouter) PostHandle(request ziface.IRequest) {

}
  • 定义一个抽象接口IRouter,实现一个BaseRouter实现接口中的所有方法但方法中没有实际业务,随后用户可以自定义Router继承这个BaseRouter,基于基类,用户想实现哪个方法就实现哪个方法。
  • 现在一个Server只能绑定一个Router,后续可以给个哈希表存储多个Router。

内容来自 @刘丹冰Aceld
项目 Zinx

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值