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