📕作者简介: 过去日记,致力于Java、GoLang,Rust等多种编程语言,热爱技术,喜欢游戏的博主。
📗本文收录于Ainx系列,大家有兴趣的可以看一看
📘相关专栏Rust初阶教程、go语言基础系列、spring教程等,大家有兴趣的可以看一看
📙Java并发编程系列,设计模式系列、go web开发框架 系列正在发展中,喜欢Java,GoLang,Rust,的朋友们可以关注一下哦!
📙 本文大部分都是借鉴刘丹冰大佬的zinx框架和文章,更推荐大家去读大佬的原文,本文只是个人学习的记录
文章目录
- Ainx-V0.2-简单的连接封装与业务绑定
- aiface创建iconnection.go
- anet 创建iconnection.go
- 重新更更正⼀一下Server.go中 处理理conn的连接业务
Ainx-V0.2-简单的连接封装与业务绑定
V0.1版本我们已经实现了了⼀一个基础的Server框架,现在我们需要对客户端链接和不不同的客户端链接所处 理理的不不同业务再做⼀一层接⼝口封装,当然我们先是把架构搭建起来。
现在在 ainterface 下创建⼀一个属于链接的接⼝⽂文件 iconnection.go ,当然他的实现⽂文件我们放在 anet 下的 connection.go 中。
aiface创建iconnection.go
ainx/ainterface/iconnection.go
package ainterface
import "net"
type IConnection interface {
// 启动连接,让当前连接开始工作
Start()
// 停止链接,结束当前连接状态
Stop()
//从当前连接获取原始的socket TCPConn GetTCPConnection() *net.TCPConn //获取当前连接ID
GetConnID() uint32 //获取远程客户端地址信息 RemoteAddr() net.Addr
}
// 定义⼀一个统⼀一处理理链接业务的接⼝口
type HandFunc func(*net.TCPConn, []byte, int) error
该接⼝的⼀些基础方法,代码注释已经介绍的很清楚,这里先简单说明⼀个HandFunc这个函数类型, 这个是所有conn链接在处理业务的函数接⼝,第⼀参数是socket原⽣链接,第二个参数是客户端请求的数据,第三个参数是客户端请求的数据长度。这样,如果我们想要指定⼀一个conn的处理业务,只要定义一个HandFunc类型的函数,然后和该链接绑定就可以了了。
anet 创建iconnection.go
ainx/anet/connection.go
package anet
import (
"ainx/ainterface"
"fmt"
"net"
)
type Connection struct {
//当前链接的socket TCP套接字
Conn *net.TCPConn
// 当前链接的ID也可以称作SessionID,ID全局唯一
ConnID uint32
// 当前链接的关闭状态
isClosed bool
// 处理该链接方法的API
handleAPI ainterface.HandFunc
// 告知该链接已经退出/停止的channel
ExitBuffChan chan bool
}
// 创建链接的方法
func NewConnection(conn *net.TCPConn, connID uint32, callback_api ainterface.HandFunc) *Connection {
c := &Connection{
Conn: conn,
ConnID: connID,
isClosed: false,
handleAPI: callback_api,
ExitBuffChan: make(chan bool, 1),
}
return c
}
// 处理conn读数据的Goroutine
func (c *Connection) StartReader() {
fmt.Println("Reader Goroutine is running")
defer fmt.Println(c.RemoteAddr().String(), "conn reader exit!")
defer c.Stop()
for {
buf := make([]byte, 512)
cnt, err := c.Conn.Read(buf)
// 读取数据失败,退出连接
if err != nil {
fmt.Println("recv buf err ", err)
c.ExitBuffChan <- true
continue
}
// 调用当前业务链(这里执行的是当前conn的绑定的handle方法)
if err := c.handleAPI(c.Conn, buf, cnt); err != nil {
fmt.Println("connID ", c.ConnID, "handle is err")
c.ExitBuffChan <- true
return
}
}
}
// 启动连接,让当前链接工作
func (c *Connection) Start() {
// 开启处理该链接读取到客户端数据之后的请求业务
go c.StartReader()
for {
select {
case <-c.ExitBuffChan:
// 得到退出消息,不再阻塞
return
}
}
}
// 停止链接,结束当前链接状态M
func (c *Connection) Stop() {
//1.如果当前链接关闭
if c.isClosed == true {
return
}
c.isClosed = true
//TODO Connection Stop() 如果用户注册了该链接的关闭回调业务,那么在此刻应该显示调用
// 关闭socket链接
err := c.Conn.Close()
if err != nil {
return
}
//通知从缓冲队列读数据的业务,该链接已经关闭
c.ExitBuffChan <- true
//关闭该链接全部管道
close(c.ExitBuffChan)
}
// 从当前链接获取原始的socket TCPConn
func (c *Connection) GetTCPConnection() *net.TCPConn {
return c.Conn
}
// 获取当前链接ID
func (c *Connection) GetConnID() uint32 {
return c.ConnID
}
// 获取远程客户端地址信息
func (c *Connection) RemoteAddr() net.Addr {
return c.Conn.RemoteAddr()
}
重新更更正⼀一下Server.go中 处理理conn的连接业务
ainx/anet/server.go
package anet
import (
"ainx/ainterface"
"errors"
"fmt"
"net"
"time"
)
type Server struct {
// 设置服务器名称
Name string
// 设置网络协议版本
IPVersion string
// 设置服务器绑定IP
IP string
// 设置端口号
Port string
}
// ============== 定义当前客户端链接的handle api ===========
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
}
// ============== 实现 ainterface.IServer 里的全部接口方法 ========
// 开启网络服务
func (s *Server) Start() {
fmt.Printf("[START] Server listenner at IP: %s, Port %s, is starting\n", s.IP, s.Port)
// 开启一个go去做服务端的Listener业务
// todo 未来目标是提供更多协议,可以利用if或者switch对IPVersion进行判断而选择采取哪种协议,下面整个方法要重写
go func() {
//1 获取一个TCP的Addr
addr, err := net.ResolveTCPAddr(s.IPVersion, s.IP+":"+s.Port)
if err != nil {
fmt.Println("resolve tcp addr err: ", err)
return
}
// 2 监听服务器地址
listener, err := net.ListenTCP(s.IPVersion, addr)
if err != nil {
fmt.Println("listen", s.IPVersion, "err", err)
return
}
// 已经成功监听
fmt.Println("start Ainx server ", s.Name, " success, now listenning...")
//TODO server.go 应该有一个自动生成ID的方法
var cid uint32
cid = 0
//3 启动server网络连接业务
for {
//3.1 阻塞等待客户端建立连接请求
conn, err := listener.AcceptTCP()
if err != nil {
fmt.Println("Accept err ", err)
continue
}
//3.2 TODO Server.Start() 设置服务器最大连接控制,如果超过最大连接,那么则关闭此新的连接
//3.3 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的
dealConn := NewConnection(conn, cid, CallBackToClient)
cid++
//3.4 启动当前链接的处理业务
go dealConn.Start()
}
}()
}
func (s *Server) Stop() {
fmt.Println("[STOP] Zinx server , name ", s.Name)
//TODO Server.Stop() 将其他需要清理的连接信息或者其他信息 也要一并停止或者清理
}
func (s *Server) Serve() {
s.Start()
//TODO Server.Serve() 是否在启动服务的时候 还要处理其他的事情呢 可以在这里添加
//阻塞,否则主Go退出, listenner的go将会退出
for {
time.Sleep(10 * time.Second)
}
}
/*
创建一个服务器句柄
*/
func NewServer(name string) ainterface.IServer {
s := &Server{
Name: name,
IPVersion: "tcp4",
IP: "0.0.0.0",
Port: "8080",
}
return s
}
CallBackToClient 是我们给当前客户端conn对象绑定的handle⽅方法,当然⽬目前是server端强制绑定 的回显业务,我们之后会丰富框架,让这个⽤用户可以让⽤用户⾃自定义指定handle。
在 start() ⽅方法中,我们主要做了了如下的修改:
//3.3 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的
dealConn := NewConntion(conn, cid, CallBackToClient)
cid +
//3.4 启动当前链接的处理业务
go dealConn.Start()
实际上,⽬目前Zinx框架的对外接⼝口并未改变,所以V0.1的测试依然有效。我们依然可以启动V0.1的测试类经行测试,测试结果如下
=== RUN TestServer
[START] Server listenner at IP: 0.0.0.0, Port 8080, is starting
Client Test ... start
start Ainx server first success, now listenning...
Reader Goroutine is running
[Conn Handle] CallBackToClient ...
Server call back : hello word,cnt =10
[Conn Handle] CallBackToClient ...
Server call back : hello word,cnt =10
[Conn Handle] CallBackToClient ...
Server call back : hello word,cnt =10
[Conn Handle] CallBackToClient ...
Server call back : hello word,cnt =10
[Conn Handle] CallBackToClient ...
Server call back : hello word,cnt =10
[Conn Handle] CallBackToClient ...
Server call back : hello word,cnt =10