【zinx】Golang轻量级TCP服务器框架(二)—— 基础client链接封装以及业务绑定

原作者视频地址:zinx-Golang轻量级TCP服务器框架
本人为自学整理的文档,梳理思考开发框架的基本思路,方法,以及视频中不理解的地方。
若想学习,强烈建议直接观看原作视频即可。
可在下方留言交流。

1.思路

我们打算将conn这个链接进行封装管理,有两个原因:首先,该包并不打算将链接直接暴露给用户;其次,我们要对conn实现读写分离操作;最后,方便之后的多客户端链接管理。

2.iconnection接口的设计

思路框图:
22

type IConnection interface {
	//启动链接 ,让当前链接开始工作
	Start()
	//停止链接, 结束当前链接
	Stop()
	//获取当前链接的套接字
	GetTCPConnection() *net.TCPConn
	//获取当前链接的链接ID
	GetConnID() uint32
	//获取远程客户端的TCP状态 IP Port
	GetRemoteAddr() net.Addr
	//发送数据,将数据发送给client
	Send(data []byte) error
}
//定义一个绑定处理链接业务的方法
type HandleFunc func(*net.TCPConn, []byte, int) error

这里可以根据自己的需要在添加相应的方法

3.connection接口的实现

这里我们需要在对接口进行实现。
代码如下:

//属性
type connection struct {
	//当前链接的socket TCP套接字
	conn *net.TCPConn
	//当前链接ID
	connID uint32
	//当前链接是否关闭
	isClosed bool
	//当前链接所绑定的API
	handleFunc ziface.HandleFunc
	//通知当前链接已经退出/停止的channel
	exitChan chan bool
}
//方法
func NewConnection(conn *net.TCPConn, connID uint32, callbackHandle ziface.HandleFunc) *connection{}
	//启动链接 ,让当前链接开始工作
func (c *connection) Start(){}
	//停止链接, 结束当前链接
func (c *connection) Stop(){}
	//获取当前链接的套接字
func (c *connection) GetTCPConnection() *net.TCPConn{}
	//获取当前链接的链接ID
func (c *connection) GetConnID() uint32{}
	//获取远程客户端的TCP状态 IP Port
func (c *connection) GetRemoteAddr() net.Addr{}
	//发送数据,将数据发送给client
func (c *connection) Send(data []byte) error{}

这里我们可以看到又有一个NewConnection函数,类似上一篇中的newserver时候的方法,为什么这样做上一篇已经说明了,这里不再赘述。
这里我们主要关注前两个方法。后面的几个都是直接返回conn的相关信息,直接return即可。send暂时现忽略。

NewConnection:它的第三个参数是一个ziface.HandleFunc类型的。而且,我们的结构体里面也有一个这样类型的函数。它的定义信息如下:

type HandleFunc func(*net.TCPConn, []byte, int) error

也就是说,它表示该种类型的函数。我们只需要给这个函数传入该种类型函数,并将其赋值给相应的结构体成员即可。目的就是为了可以将某个自定义函数绑定到该链接。当然,这个功能只是为了暂时展示,后续会更改此项功能。

Start:该函数作为链接启动函数,其实也就类似server的start方法的功能。因为我们要设计一个读写分离的结构,所以在start中还要有以下结构:

func (c *connection) Start() {
	//启动当前链接的读数据的业务
	go c.StartReader()
	//TODO:启动当前链接的写数据的业务
}

也就是将conn的读方法和写方法,作为两个单独的协程去运行。但是此处,仅仅实现了读协程(直接在StartReader实现了写),后续会将写协程分离出来。StartReader()的伪代码如下:

func (c *connection) StartReader() {

	for {
		buf := make([]byte, 512)
		cnt , err := c.conn.Read(buf)  //读协程阻塞读


		//当用当前链接绑定的handel
		err = c.handleFunc(c.conn, buf, cnt)   //这里执行的就是为其绑定的那个函数

	}
}

4.server中如何修改的呢

废话不多说,我们直接上对比代码:

//3.阻塞的等待客户端连接,处理客户端业务
*1		var cid uint32
*2		cid = 0
		
		for {
			conn, err := listenner.AcceptTCP()
			if err != nil {
				fmt.Println("[ERROR] Accept client conn is error :", err)
				continue
			}

*3			dealConn := NewConnection(conn, cid, callbackHandle)
*4			cid++
*5			dealConn.Start()

			已经与client建立连接,执行一些业务:做一个简单的字节回显任务
			//go func(){
			//	for {
			//		buf := make([]byte, 512)
			//		n, err := conn.Read(buf)
			//		if err != nil {
			//			fmt.Println("[ERROR] client conn read is error :", err)
			//			continue
			//		}
			//		fmt.Printf("server receive : %s, length is %d\n", buf, n)
			//		//回显示
			//		if _, err := conn.Write(buf[:n]); err != nil {
			//			fmt.Println("[ERROR] return client conn write is error :", err)
			//			continue
			//		}
			//	}
			//
			//}()
		}

看过第一篇的同学,应该能看出来,这是server.Start中的一段代码。前面带*的那几行,都是后来加入的,注释掉的代码是之前的处理逻辑。

标记的星1、2、4的cid就是为了统计conn的技术。
星3就是为了封装conn链接以及绑定业务函数。
星5表示启动该链接,start中又包含读协程,在读协程中实现的阻塞读以及callbackHandle(作用:回显)。其实就是实现了我们屏蔽掉的地方。

5.本篇问题

主要是学习这个思路,暂时没问题。

6.方法代码实现

6.1zinx包

iconnection.go

package ziface

import "net"

//定义链接模块的抽象层

type IConnection interface {
	//启动链接 ,让当前链接开始工作
	Start()
	//停止链接, 结束当前链接
	Stop()
	//获取当前链接的套接字
	GetTCPConnection() *net.TCPConn
	//获取当前链接的链接ID
	GetConnID() uint32
	//获取远程客户端的TCP状态 IP Port
	GetRemoteAddr() net.Addr
	//发送数据,将数据发送给client
	Send(data []byte) error
}

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

conection.go

package znet

import (
	"fmt"
	"net"
	"zinx/ziface"
)

type connection struct {
	//当前链接的socket TCP套接字
	conn *net.TCPConn
	//当前链接ID
	connID uint32
	//当前链接是否关闭
	isClosed bool
	//当前链接所绑定的API
	handleFunc ziface.HandleFunc
	//通知当前链接已经退出/停止的channel
	exitChan chan bool
}

//初始化链接模块
func NewConnection(conn *net.TCPConn, connID uint32, callbackHandle ziface.HandleFunc) *connection{
	c := &connection{
		conn:       conn,
		connID:     connID,
		isClosed:   false,
		handleFunc: callbackHandle,
		exitChan: make(chan bool, 1),
	}
	return c
}


func (c *connection) StartReader() {
	fmt.Println("connID = ", c.connID,"Reader goroutine si running...")
	defer fmt.Println("connID = ", c.connID,"Reader is exit, remote addr is ", c.GetRemoteAddr().String())
	defer c.Stop()

	for {
		buf := make([]byte, 512)
		cnt , err := c.conn.Read(buf)
		if err != nil {
			fmt.Println("connID = ", c.connID,"recv is err :",err)
			continue
		}

		//当用当前链接绑定的handel
		err = c.handleFunc(c.conn, buf, cnt)
		if err != nil {
			fmt.Println("connID = ", c.connID,"handle is err :",err)
			break   //这里的话,直接break,或者contine都是可以的,根据自己的实际业务编写
		}
	}
}


//启动链接 ,让当前链接开始工作
func (c *connection) Start() {
	fmt.Println("[CONN START] client connection is ", c.GetRemoteAddr().String())

	//启动当前链接的读数据的业务
	go c.StartReader()
	//TODO:启动当前链接的写数据的业务
}
//停止链接, 结束当前链接
func (c *connection) Stop() {
	fmt.Println("[CONN CLOSE] client connection is ", c.conn)

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

	_ = c.conn.Close()

	close(c.exitChan)
}
//获取当前链接的套接字
func (c *connection) GetTCPConnection() *net.TCPConn {
	return c.conn
}
//获取当前链接的链接ID
func (c *connection) GetConnID() uint32 {
	return c.connID
}
//获取远程客户端的TCP状态 IP Port
func (c *connection) GetRemoteAddr() net.Addr {
	return c.conn.RemoteAddr()
}
//发送数据,将数据发送给client
func (c *connection) Send(data []byte) error {
	return nil
}

server.go(有改动)

package znet

import (
	"errors"
	"fmt"
	"net"
	"zinx/ziface"
)

//iserver接口的实现,实现server的服务模块
type server struct {
	//服务器的名称
	name string
	//ip的版本
	ipVersion string
	//ip地址
	ip string
	//ip监听端口
	port int
}


func callbackHandle(conn *net.TCPConn, buf []byte, cnt int) error{
	fmt.Println("[Conn Handle] Running...")
	if _, err := conn.Write(buf); err != nil {
		fmt.Println("write back buf err :", err)
		return errors.New("CallBack error")
	}

	return nil
}


func (s *server) Start() {

	go func(){
		//1.获取一个tcp的addr
		addr , err := net.ResolveTCPAddr(s.ipVersion, fmt.Sprintf("%s:%d", s.ip, s.port))
		if err != nil {
			fmt.Println("[ERROR] Resolve tcp addr is error :", err)
			return
		}
		//2.监听服务器的地址
		listenner, err :=  net.ListenTCP(s.ipVersion, addr)
		if err != nil {
			fmt.Println("[ERROR] ListenTCP tcp addr is error :", err)
			return
		}
		fmt.Println("[ZINX] zinx start is success!!!")
		var cid uint32
		cid = 0

		//3.阻塞的等待客户端连接,处理客户端业务
		for {
			conn, err := listenner.AcceptTCP()
			if err != nil {
				fmt.Println("[ERROR] Accept client conn is error :", err)
				continue
			}


			dealConn := NewConnection(conn, cid, callbackHandle)
			cid++
			dealConn.Start()

			已经与client建立连接,执行一些业务:做一个简单的字节回显任务
			//go func(){
			//	for {
			//		buf := make([]byte, 512)
			//		n, err := conn.Read(buf)
			//		if err != nil {
			//			fmt.Println("[ERROR] client conn read is error :", err)
			//			continue
			//		}
			//		fmt.Printf("server receive : %s, length is %d\n", buf, n)
			//		//回显示
			//		if _, err := conn.Write(buf[:n]); err != nil {
			//			fmt.Println("[ERROR] return client conn write is error :", err)
			//			continue
			//		}
			//	}
			//
			//}()
		}
	}()


}

func (s *server) Server() {
	fmt.Printf("[START] Server Listenner at IP :%s, Port :%d, is starting\n",s.ip, s.port)
	s.Start()

	select {}
}

func (s *server) Stop() {

}

func NewServer(name string) ziface.IServer{
	s := &server{
		name:      name,
		ipVersion: "tcp4",
		ip:        "0.0.0.0",
		port:      8999,
	}
	return s
}

6.2用户调用测试程序

毫无变化

7.总结

这节没有不理解的地方,关键还是封装思路以及注册函数的这个想法,实现活用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋山刀名鱼丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值