【zinx】Golang轻量级TCP服务器框架(一)—— 基础server

10 篇文章 6 订阅

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

1.思路

首先,我们要开发的是一个tcp的框架,也就是说,我们要构建一个tcp的包,去供开发人员进行使用。也就是在开发人员的角度来讲,要有如下的伪代码:

s := zinx.server("tcp_server")  //创建server实例
s.server()    //开启服务器

既然如此,我们需要设计一个server的接口,以及它该有的属性和方法。

2.server接口的设计

server
方法
属性
启动 停止 运行
名称\ip版本\监听ip\监听端口号

这里是一个最最最基础的设计,后续会增加功能

接口代码如下:

type IServer interface {
	Start()
	Server()
	Stop()
}

熟悉go的都知道接口如何使用,这里不再赘述,为什么没有属性?后续会讲解,为什么要使用接口?

3.server接口的实现

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

type server struct {
	//服务器的名称
	name string
	//ip的版本
	iPVersion string
	//ip地址
	ip string
	//ip监听端口
	port int
}

func (s *server) Start() {

}

func (s *server) Server() {

}

func (s *server) Stop() {
}

这里我们的server结构体实现了IServer接口的三个方法,所以也就实现了这个接口。然后再server结构体中,添加属性字段。这样我们的*server就能赋值给IServer了。

4.问题所在

既然server是一个接口实现,那么我们在对其进行实例化的时候,必须需要一个方法,去生成这个结构实例。也就是:

//该函数与server结构体同一文件
func NewServer(name string) ziface.IServer{
	s := &server{
		name:      name,
		ipVersion: "tcp4",
		ip:        "0.0.0.0",
		port:      8999,
	}
	return s
}

大家可以看到,server结构体为小写字母开头(原视频中为大写),还有就是为什么返回值为IServer接口类型?

  1. 该包并不愿意将这个结构体的属性暴露给用户,而只是想将方法暴露给用户,这样的话我们就需要接口IServer来作为桥梁了。
  2. 因为server接口实现为小写字母开头,固使用该包的用户,不能直接通过下面这种方法实例化,只能通过上述的函数进行实例化。
s := server{...}   //因为server是小写字母开头

更为详细的解释:在 Go 语言中,我为什么使用接口?

5.方法代码实现(看注释)

最基础的server模块先这样,后续会逐渐增加功能

11

5.1 zinx包

IServer.go

package ziface

//基础server接口

type IServer interface {
	Start()
	Server()
	Stop()
}

server.go

package znet

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

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


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!!!")
		//3.阻塞的等待客户端连接,处理客户端业务
		for {
			conn, err := listenner.Accept()   //程序会阻塞在这里,等待新的client连接进来
			if err != nil {
				fmt.Println("[ERROR] Accept client conn is error :", err)
				continue
			}

			//测试:已经与client建立连接,执行一些业务:做一个简单的字节回显任务
			go func(){
				for {
					buf := make([]byte, 512)
					n, err := conn.Read(buf)   //阻塞,等待client发送数据过来
					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() {
   //TODO:后续加入资源释放的功能
}

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

5.2 用户调用测试程序

server_demo.go

package main

import "zinx/znet"

func main() {
	//1.创建服务端
	s := znet.NewServer("[zinxv1.0]")
	//2.启动server
	s.Server()
}

client_demo.go

package main

import (
	"fmt"
	"net"
	"time"
)

func main () {
	fmt.Println("Clinet start...")
	//1.连接远程服务器,得到一个conn
	conn, err := net.Dial("tcp", "127.0.0.1:8999")
	if err != nil{
		fmt.Println("client star is err :", err)
		return
	}
	//2.连接调用write
	for {
		_, err := conn.Write([]byte("Hello Zinxv0.1"))
		if err != nil{
			fmt.Println("client write is err :", err)
			return
		}

		buf := make([]byte, 512)
		n, err := conn.Read(buf)
		if err != nil{
			fmt.Println("client read is err :", err)
			return
		}

		fmt.Printf("server callback : length is %d, conntend is %s\n",n, buf)

		time.Sleep(1 * time.Second)
	}
}

6.总结

本小节,最重要的就是func NewServer(name string) ziface.IServer 的返回值为什么是这个接口,理解清楚,这也是设计模式的一种方式。可以类比c++中的构造函数的意思。

7.下篇预告

从以上代码可以看出,server获取的client—conn链接,并没有进行封装,仅仅只是一个单单的net.TCPConn类型的值,我们下一篇,要对其进行封装,提供一系列的方法,并用go协程进行做一个简单的读写分离的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

秋山刀名鱼丶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值