Zinx - V0.2 链接封装与业务绑定
- 之前的v0.1版本,已经实现了一个基础的Server框架,现在需要对客户端链接和不同的客户端链接锁处理的不同业务再做一层接口封装
- 在ziface下创建一个属于链接的接口文件iconnection.go,znet下创建文件connection.go实现链接接口
连接模块实现思路
代码实现
新增iconnection链接接口:定义链接的方法和处理连接绑定业务的方法
package ziface
import "net"
//定义链接模块的抽象层
type IConneciton interface {
//启动链接 让当前的链接准备开始工作
Start()
//停止链接 结束当前链接的工作
Stop()
//获取当前链接的绑定socket conn
GetTCPConnection() *net.TCPConn
//获取当前链接模块的链接ID
GetConnID() uint32
//获取远程客户端的 TCP状态 IP port
RemoteAddr() net.Addr
//发送数据, 将数据发送给远程的客户端
Send(data []byte) error
}
//定义一个处理链接业务的方法
type HandleFunc func(*net.TCPConn, []byte, int) error
- 关于HandFunc的函数说明:type HandleFunc func(*net.TCPConn, []byte, int) error
这个HandFunc是一个函数类型,是所有conn链接在处理业务的函数接口
第一个参数:socket原生链接
第二个参数:客户端请求的数据
第三个参数:客户端请求的数据长度
这样如果我们想要指定一个conn的处理业务,只需要定义一个HandFunc类型的函数,就可以和该链接绑定了
新增connection链接实现:实现链接
然后开始实现连接的方法和属性,提供一个初始化连接模块的方法NewConnection(),需要注意的是当框架启动时调用Start方法尝试开启两个Goroutine,分别是从客户端读的Goroutine和从客户端写的Goroutine,我们这里先把读写业务写在一个Goroutine中,后面再分开
package znet
import (
"fmt"
"net"
"zinx/ziface"
)
//链接模块
type Connection struct {
//当前链接的socket TCP套接字
Conn *net.TCPConn
//链接的ID
ConnID uint32
//当前的链接状态
isClosed bool
//当前链接所绑定的处理业务方法API
handleAPI ziface.HandleFunc
//告知当前链接已经退出的/停止 channel
ExitChan chan bool
}
//初始化链接模块的方法
func NewConnection(conn *net.TCPConn, connID uint32, callback_api ziface.HandleFunc) *Connection {
c := &Connection{
Conn: conn,
ConnID: connID,
handleAPI: callback_api,
isClosed: false,
ExitChan: make(chan bool, 1),
}
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中, 最大512字节
buf := make([]byte, 512)
cnt, err := c.Conn.Read(buf)
if err != nil {
fmt.Println("recv buf err", err)
continue
}
//调用当前链接所绑定的HandleAPI
if err := c.handleAPI(c.Conn, buf, cnt); err != nil {
fmt.Println("ConnID ", c.ConnID, " handle is error", err)
break
}
}
}
//启动链接 让当前的链接准备开始工作
func (c *Connection) Start() {
fmt.Println("Conn Start() ... ConnID = ", c.ConnID)
//启动从当前链接的读数据的业务
go c.StartReader()
//TODO 启动从当前链接写数据的业务
}
//停止链接 结束当前链接的工作
func (c *Connection) Stop() {
fmt.Println("Conn Stop().. ConnID = ", c.ConnID)
//如果当前链接已经关闭
if c.isClosed == true {
return
}
c.isClosed = true
//关闭socket链接
c.Conn.Close()
//回收资源
close(c.ExitChan)
}
//获取当前链接的绑定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) Send(data []byte) error {
return nil
}
接下来就要将connection和server进行绑定,我们可以将server.go文件中Start()方法的第三步的conn句柄和封装好的connection模块进行绑定,我们可以将“已经与客户端建立连接,做一些业务,做一个最基本的最大的512字节长度的回显业务”这部分内存交给connection模块进行处理,在server.go提供一个HandlerFunc类型的callback_api函数CallBackToClient()方法,这个方法用来定义当前客户端链接的所绑定handle api(目前这个handle是写死的,以后优化应该由用户自定义handle方法)
//3 阻塞的等待客户端链接,处理客户端链接业务(读写)
for {
//如果有客户端链接过来,阻塞会返回
conn, err := listenner.AcceptTCP()
if err != nil {
fmt.Println("Accept err", err)
continue
}
// 将处理新连接的业务方法 和 conn 进行绑定 得到我们的链接模块
dealConn := NewConnection(conn, cid, CallBackToClient)
cid++
// 启动当前的链接业务处理
go dealConn.Start()
}
// 定义当前客户端链接的所绑定handle api(目前这个handle是写死的,以后优化应该由用户自定义handle方法)
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
}
整体思路
我们在启动业务的时候调用server.go中的Start()方法,监听服务器地址,当客户端连接建立成功后会得到conn句柄,通过conn句柄和CallBackToClient()方法绑定在一起得到一个dealconn,然后开启一个Goroutine调用connection模块的Start()方法,开启StartReader()协程,这个协程首先会从客户端读512字节的数据,然后调用handleAPI处理数据,即在客户端定义好的CallBackToClient()方法