Develop go-tcp framework from 0 to 1 [1-build server, package connection and business binding, implement basic Router]
This issue mainly completes the construction of the Server, encapsulation of connection and business binding, implementation of the basic Router (the part that handles the business), and the extraction of the global configuration file of the framework
- Read data from the configuration file (server listening port, listening IP, etc.), and complete specific business operations through custom Router
First version final project structure:
1 Build the basic server[V1.0]
1.1 Write server side
- Write iserver.go to define the server interface
- Write server.go, define the server structure, and implement the interface
①/zinx/ziface/iserver.go:
package ziface
type IServer interface {
Start()
Stop()
Serve()
}
②/zinx/znet/server.go
package znet
import (
"fmt"
"net"
)
type Server struct {
Name string
IPVersion string
IP string
Port int
}
func NewServer(name string) *Server {
s := &Server{
Name: name,
IPVersion: "tcp4",
IP: "0.0.0.0",
Port: 8090,
}
return s
}
func (s *Server) Start() {
//启动服务监听端口
fmt.Printf("[start] Server listener 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.Printf("resolve tcp addr error %v\n", err)
return
}
listener, err := net.ListenTCP(s.IPVersion, addr)
if err != nil {
fmt.Println("listen ", s.IPVersion, " err ", err)
return
}
fmt.Println("[start] Zinx server success ", s.Name, "Listening...")
//阻塞连接,处理业务
for {
conn, err := listener.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("read buf err ", err)
continue
}
fmt.Printf("receive client buf %s, cnt %d \n", buf, cnt)
//回显读取到的字节数
if _, err := conn.Write(buf[:cnt]); err != nil {
fmt.Println("write buf err ", err)
continue
}
}
}()
}
}()
}
func (s *Server) Stop() {
}
func (s *Server) Serve() {
s.Start()
//阻塞,一直读取客户端所发送过来的消息
select {
}
}
1.2 Test server-side functions
① Create Server.go and Client.go
- Write myDemo/zinxV1.0/Server.go
package main
import "myTest/zinx/znet"
func main() {
s := znet.NewServer("[Zinx v1.0]")
s.Serve()
}
- Write myDemo/zinxV1.0/Client.go
package main
import (
"fmt"
"net"
"time"
)
/*
模拟客户端
*/
func main() {
fmt.Println("client start...")
time.Sleep(time.Second * 1)
//1 创建服务器连接
conn, err := net.Dial("tcp", "127.0.0.1:8090")
if err != nil {
fmt.Println("client start err ", err)
return
}
for {
//2 调用连接向服务器发数据
_, err := conn.Write([]byte("Hello Zinx v0.1"))
if err != nil {
fmt.Println("write conn err ", err)
return
}
// 3 读取服务器返回的数据
buf := make([]byte, 512)
cnt, err := conn.Read(buf)
if err != nil {
fmt.Println("client read buf err ", err)
return
}
fmt.Printf("server call back:%s, cnt=%d\n", buf, cnt)
//cpu阻塞,让出cpu时间片,避免无限for循环导致其他程序无法获取cpu时间片
time.Sleep(time.Second * 1)
}
}
②Test results
It can be seen that the server receives data from the client and echoes it every 1 second
2 Package connection conn, business binding [V2.0]
In version V0.1, we have implemented a basic server framework. Now we need to make another layer of interface encapsulation for client links and different businesses handled by different client links. Of course, we first Build the structure.
Now create an interface file iconnection.go under ziface. Of course, we put its implementation file in connection.go under znet.
Required method:
- start connection
- stop connection
- Get the connected conn object
- get the id of the connection
- Get the address and port of the client connection
- method of sending data
- The function of processing business bound by the connection
2.1 Package Conn
- Define the iconnection interface
- Create a connection structure and implement iconnection
- Create /zinx/ziface/iconnection.go:
package ziface
import "net"
type IConnection interface {
//启动连接
Start()
//停止连接
Stop()
//获取当前连接的Conn对象
GetTCPConnection() *net.TCPConn
//获取当前连接模块的id
GetConnectionID() uint32
//获取远程客户端的TCP状态 IP:Port
RemoteAddr() net.Addr
//发送数据
Send()
}
//定义一个处理连接业务的方法
type HandleFunc func(*net.TCPConn, []byte, int) error
- Create /zinx/znet/connection.go
package znet
import (
"fmt"
"myTest/zinx/ziface"
"net"
)
type Connection struct {
Conn *net.TCPConn
ConnID uint32
isClosed bool
handleAPI ziface.HandleFunc
//告知当前的连接已经退出
ExitChan chan bool
}
func NewConnection(conn *net.TCPConn, connID uint32, callback_api ziface.HandleFunc) *Connection {
c := &Connection{
Conn: conn,
ConnID: connID,
handleAPI: callback_api,
isClosed: false,
ExitChan: make(chan bool, 1),
}
return c
}
func (c *Connection) StartReader() {
fmt.Println("reader goroutine is running...")
defer fmt.Println("connID=", c.ConnID, "Reader is exit, remote addr is ", c.RemoteAddr().String())
defer c.Stop()
//读取数据
for {
buf := make([]byte, 512)
cnt, err := c.Conn.Read(buf)
if err != nil {
fmt.Printf("connID %d receive buf err %s\n", c.ConnID, err)
continue
}
//调用当前所绑定的处理业务的方法HandleAPI
if err := c.handleAPI(c.Conn, buf, cnt); err != nil {
fmt.Println("ConnID", c.ConnID, " handle is err ", err)
break
}
}
}
//启动连接
func (c *Connection) Start() {
fmt.Printf("ConnID %d is Start...", c.ConnID)
go c.StartReader()
}
//停止连接
func (c *Connection) Stop() {
fmt.Println("Connection Stop()...ConnectionID = ", c.ConnID)
if c.isClosed {
return
}
c.isClosed = true
c.Conn.Close()
close(c.ExitChan)
}
//获取当前连接的Conn对象
func (c *Connection) GetTCPConnection() *net.TCPConn {
return c.Conn
}
//获取当前连接模块的id
func (c *Connection) GetConnectionID() uint32 {
return c.ConnID
}
//获取远程客户端的TCP状态 IP:Port
func (c *Connection) RemoteAddr() net.Addr {
return c.Conn.RemoteAddr()
}
//发送数据
func (c *Connection) Send() {
}
2.2 Modify server.go (realize business processing through the encapsulated conn)
Will modify server.go and add CallBackToClient method to realize specific business
Replace the processing business logic part of server.go in ZinxV1.0 version with the encapsulated Conn to call
all codes:
/zinx/znet/server.go:
package znet
import (
"fmt"
"github.com/kataras/iris/v12/x/errors"
"net"
)
type Server struct {
Name string
IPVersion string
IP string
Port int
}
func NewServer(name string) *Server {
s := &Server{
Name: name,
IPVersion: "tcp4",
IP: "0.0.0.0",
Port: 8090,
}
return s
}
//定义当前客户端连接所绑定的handleAPI(暂时写死处理业务逻辑:数据回显)
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 buf err ", err)
return errors.New("CallBackToClient error")
}
return nil
}
func (s *Server) Start() {
//启动服务监听端口
fmt.Printf("[start] Server listener 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.Printf("resolve tcp addr error %v\n", err)
return
}
listener, err := net.ListenTCP(s.IPVersion, addr)
if err != nil {
fmt.Println("listen ", s.IPVersion, " err ", err)
return
}
fmt.Println("[start] Zinx server success ", s.Name, "Listening...")
//阻塞连接,处理业务
for {
conn, err := listener.AcceptTCP()
if err != nil {
fmt.Println("Accept err ", err)
continue
}
var cid uint32 = 0
dealConn := NewConnection(conn, cid, CallBackToClient)
cid++
//开启goroutine处理启动当前conn
go dealConn.Start()
处理业务:回显消息
//go func() {
// for {
// buf := make([]byte, 512)
// cnt, err := conn.Read(buf)
// if err != nil {
// fmt.Println("read buf err ", err)
// continue
// }
// fmt.Printf("receive client buf %s, cnt %d \n", buf, cnt)
// //回显读取到的字节数
// if _, err := conn.Write(buf[:cnt]); err != nil {
// fmt.Println("write buf err ", err)
// continue
// }
// }
//
//}()
}
}()
}
func (s *Server) Stop() {
}
func (s *Server) Serve() {
s.Start()
//阻塞,一直读取客户端所发送过来的消息
select {
}
}
2.3 Test ZinxV2.0 function
① Modify the log printing of Server.go and Client.go
Create /myDemo/ZinxV2.0/Client.go and /myDemo/ZinxV2.0/Server.go, this part of the test code is the same as V1.0, just replace the print log with Zinx2.0
- Client.go
package main
import (
"fmt"
"net"
"time"
)
/*
模拟客户端
*/
func main() {
fmt.Println("client start...")
time.Sleep(time.Second * 1)
//1 创建服务器连接
conn, err := net.Dial("tcp", "127.0.0.1:8090")
if err != nil {
fmt.Println("client start err ", err)
return
}
for {
//2 调用连接向服务器发数据
_, err := conn.Write([]byte("Hello Zinx v0.2"))
if err != nil {
fmt.Println("write conn err ", err)
return
}
// 3 读取服务器返回的数据
buf := make([]byte, 512)
cnt, err := conn.Read(buf)
if err != nil {
fmt.Println("client read buf err ", err)
return
}
fmt.Printf("server call back:%s, cnt=%d\n", buf, cnt)
//cpu阻塞,让出cpu时间片,避免无限for循环导致其他程序无法获取cpu时间片
time.Sleep(time.Second * 1)
}
}
- Server.go
package main
import "myTest/zinx/znet"
func main() {
s := znet.NewServer("[Zinx v2.0]")
s.Serve()
}
②Test results
3 Realize the basic Router[V3.0]
3.1 Request encapsulation
Bind connections and data together
zinx/ziface/irequest.go:
package ziface
import "net"
type IRequest interface {
GetConnection() *net.TCPConn
GetData() []byte
}
zinx/znet/request.go:
package znet
import "net"
type Request struct {
conn *net.TCPConn
data []byte
}
func (r *Request) GetConnection() *net.TCPConn {
return r.conn
}
func (r *Request) GetData() []byte {
return r.data
}
3.2 Router module
zinx/ziface/irouter.go
package ziface
type IRouter interface {
//处理请求之前的方法
PreHandle(request IRequest)
Handler(request IRequest)
//处理请求之后的方法
PostHandler(request IRequest)
}
zinx/znet/router.go
package znet
import "myTest/zinx/ziface"
type BaseRouter struct {
}
//这里做了空实现,直接让后续Router继承BaseRouter,然后根据需要重写对应方法即可
func (br *BaseRouter) PreHandle(request ziface.IRequest) {
}
func (br *BaseRouter) Handler(request ziface.IRequest) {
}
func (br *BaseRouter) PostHandler(request ziface.IRequest) {
}
3.3 Framework integrated router module
- Cancel the HandlerFunc module in znet/server.go and change it to Router. Add Router property in server.go
- Change the callback_api ziface.HandleFunc parameter in znet/connection.go to Router
zinx/znet/connection.go
package znet
import (
"fmt"
"myTest/zinx/ziface"
"net"
)
type Connection struct {
Conn *net.TCPConn
ConnID uint32
isClosed bool
//告知当前的连接已经退出
ExitChan chan bool
Router ziface.IRouter
}
func NewConnection(conn *net.TCPConn, connID uint32, router ziface.IRouter) *Connection {
c := &Connection{
Conn: conn,
ConnID: connID,
Router: router,
isClosed: false,
ExitChan: make(chan bool, 1),
}
return c
}
func (c *Connection) StartReader() {
fmt.Println("reader goroutine is running...")
defer fmt.Println("connID=", c.ConnID, "Reader is exit, remote addr is ", c.RemoteAddr().String())
defer c.Stop()
//读取数据
for {
buf := make([]byte, 512)
_, err := c.Conn.Read(buf)
if err != nil {
fmt.Printf("connID %d receive buf err %s\n", c.ConnID, err)
continue
}
//封装请求,改为router处理
r := Request{
conn: c.Conn,
data: buf,
}
go func(request ziface.IRequest) {
c.Router.PreHandle(request)
c.Router.Handler(request)
c.Router.PostHandler(request)
}(&r)
}
}
//启动连接
func (c *Connection) Start() {
fmt.Printf("ConnID %d is Start...", c.ConnID)
go c.StartReader()
}
//停止连接
func (c *Connection) Stop() {
fmt.Println("Connection Stop()...ConnectionID = ", c.ConnID)
if c.isClosed {
return
}
c.isClosed = true
c.Conn.Close()
close(c.ExitChan)
}
//获取当前连接的Conn对象
func (c *Connection) GetTCPConnection() *net.TCPConn {
return c.Conn
}
//获取当前连接模块的id
func (c *Connection) GetConnectionID() uint32 {
return c.ConnID
}
//获取远程客户端的TCP状态 IP:Port
func (c *Connection) RemoteAddr() net.Addr {
return c.Conn.RemoteAddr()
}
//发送数据
func (c *Connection) Send() {
}
zinx/znet/server.go
package znet
import (
"fmt"
"myTest/zinx/ziface"
"net"
)
type Server struct {
Name string
IPVersion string
IP string
Port int
Router ziface.IRouter
}
func NewServer(name string) *Server {
s := &Server{
Name: name,
IPVersion: "tcp4",
IP: "0.0.0.0",
Port: 8090,
Router: nil,
}
return s
}
func (s *Server) Start() {
//启动服务监听端口
fmt.Printf("[start] Server listener 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.Printf("resolve tcp addr error %v\n", err)
return
}
listener, err := net.ListenTCP(s.IPVersion, addr)
if err != nil {
fmt.Println("listen ", s.IPVersion, " err ", err)
return
}
fmt.Println("[start] Zinx server success ", s.Name, "Listening...")
//阻塞连接,处理业务
for {
conn, err := listener.AcceptTCP()
if err != nil {
fmt.Println("Accept err ", err)
continue
}
var cid uint32 = 0
dealConn := NewConnection(conn, cid, s.Router)
cid++
//开启goroutine处理启动当前conn
go dealConn.Start()
}
}()
}
func (s *Server) Stop() {
}
func (s *Server) Serve() {
s.Start()
//阻塞,一直读取客户端所发送过来的消息
select {
}
}
func (s *Server) AddRouter(router ziface.IRouter) {
s.Router = router
}
Test framework integrated router effect
myDemo/ZinxV3.0/client.go
package main
import (
"fmt"
"net"
"time"
)
/*
模拟客户端
*/
func main() {
fmt.Println("client start...")
time.Sleep(time.Second * 1)
//1 创建服务器连接
conn, err := net.Dial("tcp", "127.0.0.1:8090")
if err != nil {
fmt.Println("client start err ", err)
return
}
for {
//2 调用连接向服务器发数据
_, err := conn.Write([]byte("Hello Zinx v0.3"))
if err != nil {
fmt.Println("write conn err ", err)
return
}
// 3 读取服务器返回的数据
buf := make([]byte, 512)
cnt, err := conn.Read(buf)
if err != nil {
fmt.Println("client read buf err ", err)
return
}
fmt.Printf("server call back:%s, cnt=%d\n", buf, cnt)
//cpu阻塞,让出cpu时间片,避免无限for循环导致其他程序无法获取cpu时间片
time.Sleep(time.Second * 1)
}
}
myDemo/ZinxV3.0/server.go
package main
import (
"fmt"
"myTest/zinx/ziface"
"myTest/zinx/znet"
)
//自定义一个Router,测试路由功能
type PingRouter struct {
znet.BaseRouter
}
func (pr *PingRouter) PreHandle(request ziface.IRequest) {
_, err := request.GetConnection().Write([]byte("pre handle success..."))
if err != nil {
fmt.Println("server call pre handle err ", err)
return
}
fmt.Println("server call pre handle...")
}
func (pr *PingRouter) Handler(request ziface.IRequest) {
_, err := request.GetConnection().Write([]byte("handle success..."))
if err != nil {
fmt.Println("server call handle err ", err)
return
}
fmt.Println("server call handler....")
}
func (pr *PingRouter) PostHandler(request ziface.IRequest) {
_, err := request.GetConnection().Write([]byte("post handle success..."))
if err != nil {
fmt.Println("server call post handle err ", err)
return
}
fmt.Println("server call post handler...")
}
func main() {
s := znet.NewServer("[Zinx v3.0]")
//添加自定义路由
router := &PingRouter{
}
s.AddRouter(router)
s.Serve()
}
final effect:
According to the template method design pattern, the call is completed
4 Extract the global configuration file [V4.0]
4.1 Write /zinx/util/globalobj.go
Mainly used to read the information of the zinx configuration file
package util
import (
"encoding/json"
"io/ioutil"
"myTest/zinx/ziface"
)
type GlobalObj struct {
TCPServer ziface.IServer //当前全局Zinx的server对象
Host string //当前服务器主机监听的ip
TcpPort int //当前服务器主机监听的端口号
Name string //当前服务器的名称
Version string //当前Zinx的版本号
MaxConn int //当前服务器所允许的最大连接数
MaxPackageSize uint32 //当前Zinx框架数据包的最大值
}
var GlobalObject *GlobalObj
//从配置文件中重新加载GlobalObject的信息
func (g *GlobalObj) Reload() {
data, err := ioutil.ReadFile("conf/zinx.json")
if err != nil {
panic(err)
}
//将json文件数据解析到struct中
err = json.Unmarshal(data, &GlobalObject)
if err != nil {
panic(err)
}
}
//在其他文件导入该util包的时候会加载init
func init() {
GlobalObject = &GlobalObj{
Name: "ZinxServerApp",
Version: "V0.4",
TcpPort: 8090,
Host: "0.0.0.0",
MaxConn: 120,
MaxPackageSize: 4096,
}
//尝试从conf/zinx.json中去加载用户自定义的参数
GlobalObject.Reload()
}
4.2 Replace the hard code in server.go before
Include /zinx/znet/server.go and /zinx/znet/connection.go sections
- server:
- connection:
4.3 Testing
Write myDemo/ZinxV4.0
- And write the corresponding .json configuration file (Client.go and Server.go are the same as V3.0)
zinx.json
{
"Name": "Zinx Server Application",
"Version": "V0.4",
"Host": "0.0.0.0",
"TcpPort": 8091,
"MaxConn": 30,
"MaxPackageSize": 1024
}
Final effect:
Reference: https://www.yuque.com/aceld/npyr8s/bgftov