go-tcp フレームワークを 0 から 1 まで開発 [1-サーバーの構築、接続とビジネスのバインディングをパッケージ化、基本的なルーターを実装]
この問題では主に、サーバーの構築、接続とビジネス バインディングのカプセル化、基本的なルーター (ビジネスを処理する部分) の実装、およびフレームワークのグローバル構成ファイルの抽出が完了します。
- 設定ファイル (サーバーのリスニング ポート、リスニング IP など) からデータを読み取り、カスタム ルーターを通じて特定のビジネス操作を完了します。
最初のバージョンの最終プロジェクト構造:
1 基本サーバーの構築【V1.0】
1.1 サーバー側の書き込み
- iserver.go を記述してサーバーインターフェースを定義します
- server.go を作成し、サーバー構造を定義し、インターフェイスを実装します。
①/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 サーバー側機能のテスト
① Server.goとClient.goを作成する
- myDemo/zinxV1.0/Server.go を書き込みます
package main
import "myTest/zinx/znet"
func main() {
s := znet.NewServer("[Zinx v1.0]")
s.Serve()
}
- 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)
}
}
②検査結果
サーバーがクライアントからデータを受信し、それを 1 秒ごとにエコーしていることがわかります。
2 パッケージ接続接続、ビジネスバインディング [V2.0]
バージョン V0.1 では、基本的なサーバー フレームワークを実装しました。今度は、クライアント リンクと、さまざまなクライアント リンクによって処理されるさまざまなビジネス用に、インターフェイスのカプセル化の別の層を作成する必要があります。もちろん、最初に構造を構築します。
次に、ziface の下にインターフェイス ファイル iconnection.go を作成します。もちろん、その実装ファイルは znet の下の connection.go に置きます。
必要な方法:
- 接続を開始する
- 接続を停止する
- 接続された conn オブジェクトを取得する
- 接続のIDを取得します
- クライアント接続のアドレスとポートを取得します。
- データの送信方法
- つながりに縛られた業務処理機能
2.1 パッケージ接続
- iconnection インターフェイスを定義する
- 接続構造を作成し、iconnection を実装する
- /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
- /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 server.goの変更(カプセル化されたconnによる業務処理の実現)
特定のビジネスを実現するためにserver.goを修正し、CallBackToClientメソッドを追加します
ZinxV1.0 バージョンの server.go の処理ビジネス ロジック部分を、すべてのコード
/zinx/znet/server.goを呼び出すカプセル化された Conn に置き換えます。
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 ZinxV2.0 機能のテスト
①Server.goとClient.goのログ出力を修正
/myDemo/ZinxV2.0/Client.go と /myDemo/ZinxV2.0/Server.go を作成します。テスト コードのこの部分は V1.0 と同じです。印刷ログを 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()
}
②検査結果
3 基本ルーターの実現[V3.0]
3.1 リクエストのカプセル化
接続とデータをバインドする
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 ルーターモジュール
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 フレームワーク統合ルーターモジュール
- znet/server.go の HandlerFunc モジュールをキャンセルし、Router に変更します。server.go に Router プロパティを追加
- znet/connection.go の callback_api ziface.HandleFunc パラメータを 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
}
テストフレームワーク統合ルーター効果
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()
}
最終的な効果:
テンプレートメソッドの設計パターンに従って、呼び出しが完了します
4 グローバル設定ファイルを解凍する【V4.0】
4.1 /zinx/util/globalobj.go を書き込む
主にzinx設定ファイルの情報を読み取るために使用されます。
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 以前のserver.goのハードコードを置き換える
/zinx/znet/server.go セクションと /zinx/znet/connection.go セクションを含めます
- サーバ:
- 繋がり:
4.3 テスト
myDemo/ZinxV4.0 を書く
- そして、対応する .json 設定ファイルを記述します (Client.go と Server.go は V3.0 と同じです)。
ジンクス.json
{
"Name": "Zinx Server Application",
"Version": "V0.4",
"Host": "0.0.0.0",
"TcpPort": 8091,
"MaxConn": 30,
"MaxPackageSize": 1024
}
最終的な効果:
参考:https://www.yuque.com/aceld/npyr8s/bgftov