1.思路原作者视频地址:zinx-Golang轻量级TCP服务器框架
本人为自学整理的文档,梳理思考开发框架的基本思路,方法,以及视频中不理解的地方。
若想学习,强烈建议直接观看原作视频即可。
可在下方留言交流。
首先,我们要开发的是一个tcp的框架,也就是说,我们要构建一个tcp的包,去供开发人员进行使用。也就是在开发人员的角度来讲,要有如下的伪代码:
s := zinx.server("tcp_server") //创建server实例
s.server() //开启服务器
既然如此,我们需要设计一个server的接口,以及它该有的属性和方法。
2.server接口的设计这里是一个最最最基础的设计,后续会增加功能
接口代码如下:
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() {
}
4.问题所在这里我们的server结构体实现了IServer接口的三个方法,所以也就实现了这个接口。然后再server结构体中,添加属性字段。这样我们的*server就能赋值给IServer了。
既然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接口类型?
该包并不愿意将这个结构体的属性暴露给用户,而只是想将方法暴露给用户,这样的话我们就需要接口IServer来作为桥梁了。因为server接口实现为小写字母开头,固使用该包的用户,不能直接通过下面这种方法实例化,只能通过上述的函数进行实例化。s := server{...} //因为server是小写字母开头
更为详细的解释:在 Go 语言中,我为什么使用接口?
5.方法代码实现(看注释)最基础的server模块先这样,后续会逐渐增加功能
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.总结
7.下篇预告本小节,最重要的就是func NewServer(name string) ziface.IServer 的返回值为什么是这个接口,理解清楚,这也是设计模式的一种方式。可以类比c++中的构造函数的意思。
从以上代码可以看出,server获取的client—conn链接,并没有进行封装,仅仅只是一个单单的net.TCPConn类型的值,我们下一篇,要对其进行封装,提供一系列的方法,并用go协程进行做一个简单的读写分离的实现。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)