说明
本文档按照实验楼–Go 并发服务器框架 Zinx 入门的文档同步学习记录(大部分内容相同)
https://www.lanqiao.cn/courses/1639
主要有以下原因:
1、模仿大神写教程的风格
2、验证每一个步骤,而不是简简单单的复制教程中的代码。简单重现
将完成 Zinx 框架的 server 模块。也就是实现如下图所示的功能模块:

知识点
初始化
为了更好的看到 Zinx 框架,首先我们需要构建 Zinx 的最基本的两个模块 ziface
和znet
。
ziface
主要是存放一些 Zinx 框架的全部模块的抽象层接口类,Zinx 框架的最基本的是服务类接口 iserver
,定义在 ziface 模块中。
znet
模块是 zinx 框架中网络相关功能的实现,所有网络相关模块都会定义在znet
模块中。
我们通过在命令行中运行如下命令,进行初始化操作。
1
| wget https://labfile.oss.aliyuncs.com/courses/1639/init.sh && /bin/bash init.sh
|
现在在我们的 src 目录下的文件路径如下:
1 2 3 4 5 6 7 8 9 10
| . ├── init.sh └── src └── zinx ├── ziface │ └── iserver.go └── znet └── server.go
4 directories, 3 files
|
在 ziface 下创建服务模块抽象层 iserver.go
作为接口,我们对外只提供方法,所以我们可以抽象为:
启动服务器方法。
停止服务器方法。
开启业务服务方法。
1 2 3 4 5 6 7 8 9 10
| package ziface
type IServer interface{ Start() Stop() Serve() }
|
在 znet 下实现服务模块 server.go
我们这里使用 Server 结构体来实现上面接口中所定义的方法。首先,作为一个服务器,必须要有的三个属性就是:
- 服务器名。
- 服务器 IP。
- 服务器监听的端口。
但是现在的 IP 地址不只有 IPv4 ,IPv6 也在推行当中了。所以我们还需要一个 IPversion 的属性来表示 IP 地址的版本。
所以,这个结构体定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| type Server struct { Name string IPVersion string IP string Port int }
func NewServer (name string) ziface.IServer { s:= &Server { Name :name, IPVersion:"tcp4", IP:"0.0.0.0", Port:7777, } return s }
|
启动服务器的实现
启动一个服务器分为三步:
获取 TCP 的地址。
监听服务器地址。
启动 server 网络连接业务。
实现过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| func (s *Server) Start() { fmt.Printf("[START] Server listenner at IP: %s, Port %d, is starting\n", s.IP, s.Port) go func() { addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port)) if err != nil { fmt.Println("resolve tcp addr err: ", err) return } listenner, err:= net.ListenTCP(s.IPVersion, addr) if err != nil { fmt.Println("listen", s.IPVersion, "err", err) return } fmt.Println("start Zinx server ", s.Name, " succ, now listenning...") for { conn, err := listenner.AcceptTCP() if err != nil { fmt.Println("Accept err ", err) continue } go func () { for { buf := make([]byte, 512) cnt, err := conn.Read(buf) if err != nil { fmt.Println("recv buf err ", err) continue } if _, err := conn.Write(buf[:cnt]); err !=nil { fmt.Println("write back buf err ", err) continue } } }() } }() }
|
剩余两个方法的实现
这里,我们的 Stop 方法和 Serve 方法,只做出一个打印的功能。因为这些功能需要与其他功能相结合使用。
1 2 3 4 5 6 7 8 9 10 11 12
| func (s *Server) Stop() { fmt.Println("[STOP] Zinx server , name " , s.Name) } func (s *Server) Serve() { s.Start() for { time.Sleep(10*time.Second) } }
|
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| package znet import ( "fmt" "net" "time" "zinx/ziface" )
type Server struct { Name string IPVersion string IP string Port int }
func (s *Server) Start() { fmt.Printf("[START] Server listenner at IP: %s, Port %d, is starting\n", s.IP, s.Port) go func() { addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port)) if err != nil { fmt.Println("resolve tcp addr err: ", err) return } listenner, err:= net.ListenTCP(s.IPVersion, addr) if err != nil { fmt.Println("listen", s.IPVersion, "err", err) return } fmt.Println("start Zinx server ", s.Name, " succ, now listenning...") for { conn, err := listenner.AcceptTCP() if err != nil { fmt.Println("Accept err ", err) continue } go func () { for { buf := make([]byte, 512) cnt, err := conn.Read(buf) if err != nil { fmt.Println("recv buf err ", err) continue } if _, err := conn.Write(buf[:cnt]); err !=nil { fmt.Println("write back buf err ", err) continue } } }() } }() } func (s *Server) Stop() { fmt.Println("[STOP] Zinx server , name " , s.Name) } func (s *Server) Serve() { s.Start() for { time.Sleep(10*time.Second) } }
func NewServer (name string) ziface.IServer { s:= &Server { Name :name, IPVersion:"tcp4", IP:"0.0.0.0", Port:7777, } return s }
|
入坑笔记
因为引用了本地的库
1 2 3 4 5 6
| import ( "fmt" "net" "time" "zinx/ziface" )
|
所以
先看下gopath
使用前将本地的库临时加进去
1
| export GOPATH=$GOPATH:/Users/jinkangli/project/go/my/zinx
|
其他知识点
Go语言中Goroutine与线程的区别
https://www.cnblogs.com/xi-jie/p/11447905.html
1、什么是Goroutine?
Goroutine是建立在线程之上的轻量级的抽象。它允许我们以非常低的代价在同一个地址空间中并行地执行多个函数或者方法。相比于线程,它的创建和销毁的代价要小很多,并且它的调度是独立于线程的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main import ( "fmt" "time" ) func learning() { fmt.Println("My first goroutine") } func main() { go learning() time.Sleep(1 * time.Second) fmt.Println("main function") }
|
这段代码的输出是:
My first goroutine
main function
如果将Sleep去掉,将会输出的是:
main function
这是因为,和线程一样,golang的主函数(其实也是跑在一个goroutine中)并不会等待其他goroutine结束。如果主goroutine结束了,所有其他goroutine都将结束。