Zinx - V0.1 构建最基础的Server
Zinx的框架结构:
整体思路:
客户端发送请求到服务器端,服务端会有一个Goroutine专门处理listenner和监听这个过程,然后有客户端连接过来之后会启动一个客户端处理Goroutine,这个Goroutine会分别开启一个客户端读写Goroutine,Reader是用来从客户端把数据读到已经开辟好的工作池中,工作池处理业务会针对用户自已经注册好的自定义好的业务来取一些业务规则来去处理数据,当数据处理完之后会将数据再交给Write Handler回写给客户端
Zinx 的最基本的两个模块ziface和zent:
- ziface主要是存放一些Zinx框架的全部模块的抽象层接口类
Zinx 框架的最基本的服务类接口iserver,定义在该模块中
- znet模块是Zinx框架中网络相关功能的实现,所有网络相关模块定义在这里
基础的Server模块拥有的三个方法和相应属性
代码实现
在iserver.go中定义一个服务器接口,提供三个方法
package ziface
//定义一个服务器接口
type IServer interface {
//启动服务器
Start()
//停止服务器
Stop()
//运行服务器
Serve()
}
然后再server.go中将该接口实例化,定义一个Server结构体对象,继承服务器接口即实现接口中的方法
实现接口中的方法前我们需要提供一个初始化Server对象的方法NewServer(),该方法返回的是一个抽象层iserver,在该方法中创建一个server对象并将其返回
//初始化Server模块的方法
func NewServer(name string) ziface.IServer {
s := &Server{
Name: name,
IPVersion: "tcp4",
IP: "0.0.0.0",
Port: 8999,
}
return s
}
首先实现Serve方法,在其中一定会调用Start方法将其服务器启动,所以先要实现Start方法,开发一个单体服务器有以下三个步骤
- 获取一个TCP的Addr,即创建一个套接字
- 监听服务器的地址
- 监听成功后阻塞的等待客户端链接,处理客户端链接业务(读写)
其中第三步中我们使用for循环不断遍历等待listenner.AcceptTCP()的返回,如果没有客户端连接就会一直阻塞,如果有连接过来就会返回一个conn句柄和客户端进行基本的读写操作
已经有客户端建立连接后,我们就可以起一个协程去承载一些业务,在这里做一个最基本的最大512字节长度的显业务,在这个协程中不断地从客户端获取数据,所以也要用一个for循环
// 1 获取一个TCP的Addr
addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
if err != nil {
fmt.Println("resolve tcp addt error : ", err)
return
}
//2 监听服务器的地址
listenner, err := net.ListenTCP(s.IPVersion, addr)
if err != nil {
fmt.Println("listen ", s.IPVersion, " err ", err)
return
}
fmt.Println("start Zinx server succ, ", s.Name, " succ, Listenning...")
//3 阻塞的等待客户端链接,处理客户端链接业务(读写)
for {
//如果有客户端链接过来,阻塞会返回
conn, err := listenner.AcceptTCP()
if err != nil {
fmt.Println("Accept err", err)
continue
}
//已经与客户端建立间接,做一些业务, 做一个最基本的最大512字节长度的回显业务
go func() {
for {
buf := make([]byte, 512)
cnt, err := conn.Read(buf)
if err != nil {
fmt.Println("recv buf err ", err)
continue
}
fmt.Printf("recv client buf %s, cnt %d\n", buf, cnt)
//回显功能
if _, err := conn.Write(buf[:cnt]); err != nil {
fmt.Println("write back buf err ", err)
continue
}
}
}()
}
但如果这样写,这个等待客户端连接listenner.AcceptTCP()的过程会阻塞,所以我们可以将整个Start业务用go func()来承载
然后再回到Serve()方法中,因为Start()本身是异步的,如果不在Serve()方法中进行阻塞,主线程在调用Start()后就会结束,服务器就没了,所以我们需要select阻塞
//运行服务器
func (s *Server) Serve() {
//启动server的服务功能
s.Start()
//TODO 做一些启动服务器之后的额外业务
//阻塞状态
select {}
}
server.go完整代码
package znet
import (
"fmt"
"net"
"zinx/ziface"
)
//iServer的接口实现,定义一个Server的服务器模块
type Server struct {
//服务器的名称
Name string
//服务器绑定的ip版本
IPVersion string
//服务器监听的IP
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() {
// 1 获取一个TCP的Addr
addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
if err != nil {
fmt.Println("resolve tcp addt error : ", err)
return
}
//2 监听服务器的地址
listenner, err := net.ListenTCP(s.IPVersion, addr)
if err != nil {
fmt.Println("listen ", s.IPVersion, " err ", err)
return
}
fmt.Println("start Zinx server succ, ", s.Name, " succ, Listenning...")
//3 阻塞的等待客户端链接,处理客户端链接业务(读写)
for {
//如果有客户端链接过来,阻塞会返回
conn, err := listenner.AcceptTCP()
if err != nil {
fmt.Println("Accept err", err)
continue
}
//已经与客户端建立间接,做一些业务, 做一个最基本的最大512字节长度的回显业务
go func() {
for {
buf := make([]byte, 512)
cnt, err := conn.Read(buf)
if err != nil {
fmt.Println("recv buf err ", err)
continue
}
fmt.Printf("recv client buf %s, cnt %d\n", buf, cnt)
//回显功能
if _, err := conn.Write(buf[:cnt]); err != nil {
fmt.Println("write back buf err ", err)
continue
}
}
}()
}
}()
}
//停止服务器
func (s *Server) Stop() {
//TODO 将一些服务器的资源、状态或者一些已经开辟的链接信息 进行停止或者回收
}
//运行服务器
func (s *Server) Serve() {
//启动server的服务功能
s.Start()
//TODO 做一些启动服务器之后的额外业务
//阻塞状态
select {}
}
//初始化Server模块的方法
func NewServer(name string) ziface.IServer {
s := &Server{
Name: name,
IPVersion: "tcp4",
IP: "0.0.0.0",
Port: 8999,
}
return s
}
服务端测试
为了我们这个框架能够边写边使用,我们创建一个myDemo模块来测试Zinx框架,其中创建一个Server.go文件,该文件是基于Zinx框架开发的服务器端应用程序,在main函数中创建一个server句柄,使用Zinx的api,然后启动server
package main
import "zinx/znet"
//基于Zinx框架来开发的 服务器端应用程序
func main() {
//1 创建一个server句柄,使用Zinx的api
s := znet.NewServer("[zinx V0.1]")
//2 启动server
s.Serve()
}
客户端测试
- 直接链接远程服务器,得到一个conn链接
- 链接调用Write 写数据(for循环不断写),(服务器有将字符串返回的功能)
需要注意的是,我们要在客户端的for循环中将cpu阻塞,否则会将cpu性能跑满
package main
import (
"fmt"
"net"
"time"
)
//模拟客户端
func main() {
fmt.Println("client start...")
time.Sleep(1 * time.Second)
//1 直接链接远程服务器,得到一个conn链接
conn, err := net.Dial("tcp", "127.0.0.1:8999")
if err != nil {
fmt.Println("client start err, exit!")
return
}
for {
//2 链接调用Write 写数据
_, err := conn.Write([]byte("Hello Zinx V0.1.."))
if err != nil {
fmt.Println("write conn err", err)
return
}
buf := make([]byte, 512)
cnt, err := conn.Read(buf)
if err != nil {
fmt.Println("read buf error")
return
}
fmt.Printf(" server call back: %s, cnt = %d\n", buf, cnt)
//cpu阻塞
time.Sleep(1 * time.Second)
}
}