從0到1開發(fā)go-tcp框架【1-搭建server、封裝連接與業(yè)務(wù)綁定、實現(xiàn)基礎(chǔ)Router】
本期主要完成對Server的搭建、封裝連接與業(yè)務(wù)綁定、實現(xiàn)基礎(chǔ)Router(處理業(yè)務(wù)的部分)、抽取框架的全局配置文件
- 從配置文件中讀取數(shù)據(jù)(服務(wù)器監(jiān)聽端口、監(jiān)聽IP等),通過自定義Router完成具體業(yè)務(wù)操作
第一版最終項目結(jié)構(gòu):
1 搭建基礎(chǔ)server[V1.0]
1.1 編寫server端
- 編寫iserver.go,用于定義server的接口
- 編寫server.go,定義server結(jié)構(gòu)體,并實現(xiàn)接口
①/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() {
//啟動服務(wù)監(jiān)聽端口
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...")
//阻塞連接,處理業(yè)務(wù)
for {
conn, err := listener.AcceptTCP()
if err != nil {
fmt.Println("Accept err ", err)
continue
}
//處理業(yè)務(wù):回顯消息
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)
//回顯讀取到的字節(jié)數(shù)
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()
//阻塞,一直讀取客戶端所發(fā)送過來的消息
select {}
}
1.2 測試server端功能
①創(chuàng)建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 創(chuàng)建服務(wù)器連接
conn, err := net.Dial("tcp", "127.0.0.1:8090")
if err != nil {
fmt.Println("client start err ", err)
return
}
for {
//2 調(diào)用連接向服務(wù)器發(fā)數(shù)據(jù)
_, err := conn.Write([]byte("Hello Zinx v0.1"))
if err != nil {
fmt.Println("write conn err ", err)
return
}
// 3 讀取服務(wù)器返回的數(shù)據(jù)
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循環(huán)導(dǎo)致其他程序無法獲取cpu時間片
time.Sleep(time.Second * 1)
}
}
②測試結(jié)果
可以看到每隔1秒服務(wù)器就從客戶端接受到數(shù)據(jù)并回顯
2 封裝連接conn、業(yè)務(wù)綁定[V2.0]
V0.1版本我們已經(jīng)實現(xiàn)了了?一個基礎(chǔ)的Server框架,現(xiàn)在我們需要對客戶端鏈接和不不同的客戶端鏈接所處 理理的不不同業(yè)務(wù)再做?一層接?口封裝,當(dāng)然我們先是把架構(gòu)搭建起來。
現(xiàn)在在 ziface 下創(chuàng)建?一個屬于鏈接的接?口?文件 iconnection.go ,當(dāng)然他的實現(xiàn)?文件我們放在 znet 下的 connection.go 中。
需要的方法:
- 啟動連接
- 停止連接
- 得到連接的conn對象
- 得到連接的id
- 得到客戶端連接的地址和端口
- 發(fā)送數(shù)據(jù)的方法
- 連接所綁定的處理業(yè)務(wù)的函數(shù)
2.1 封裝Conn
- 定義iconnection接口
- 創(chuàng)建connection結(jié)構(gòu)體并實現(xiàn)iconnection
- 創(chuàng)建/zinx/ziface/iconnection.go:
package ziface
import "net"
type IConnection interface {
//啟動連接
Start()
//停止連接
Stop()
//獲取當(dāng)前連接的Conn對象
GetTCPConnection() *net.TCPConn
//獲取當(dāng)前連接模塊的id
GetConnectionID() uint32
//獲取遠程客戶端的TCP狀態(tài) IP:Port
RemoteAddr() net.Addr
//發(fā)送數(shù)據(jù)
Send()
}
//定義一個處理連接業(yè)務(wù)的方法
type HandleFunc func(*net.TCPConn, []byte, int) error
- 創(chuàng)建/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
//告知當(dāng)前的連接已經(jīng)退出
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()
//讀取數(shù)據(jù)
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
}
//調(diào)用當(dāng)前所綁定的處理業(yè)務(wù)的方法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)
}
//獲取當(dāng)前連接的Conn對象
func (c *Connection) GetTCPConnection() *net.TCPConn {
return c.Conn
}
//獲取當(dāng)前連接模塊的id
func (c *Connection) GetConnectionID() uint32 {
return c.ConnID
}
//獲取遠程客戶端的TCP狀態(tài) IP:Port
func (c *Connection) RemoteAddr() net.Addr {
return c.Conn.RemoteAddr()
}
//發(fā)送數(shù)據(jù)
func (c *Connection) Send() {
}
2.2 修改server.go(通過封裝的conn實現(xiàn)處理業(yè)務(wù))
將修改server.go,添加CallBackToClient方法,用于實現(xiàn)具體業(yè)務(wù)
將ZinxV1.0版本中的server.go的處理業(yè)務(wù)邏輯部分更換為封裝后的Conn來調(diào)用
全部代碼:
/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
}
//定義當(dāng)前客戶端連接所綁定的handleAPI(暫時寫死處理業(yè)務(wù)邏輯:數(shù)據(jù)回顯)
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() {
//啟動服務(wù)監(jiān)聽端口
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...")
//阻塞連接,處理業(yè)務(wù)
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處理啟動當(dāng)前conn
go dealConn.Start()
處理業(yè)務(wù):回顯消息
//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)
// //回顯讀取到的字節(jié)數(shù)
// 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()
//阻塞,一直讀取客戶端所發(fā)送過來的消息
select {}
}
2.3 測試ZinxV2.0功能
①修改Server.go和Client.go的日志打印
創(chuàng)建/myDemo/ZinxV2.0/Client.go和/myDemo/ZinxV2.0/Server.go,這部分測試代碼和V1.0沒有區(qū)別,將打印日志換成Zinx2.0即可
- Client.go
package main
import (
"fmt"
"net"
"time"
)
/*
模擬客戶端
*/
func main() {
fmt.Println("client start...")
time.Sleep(time.Second * 1)
//1 創(chuàng)建服務(wù)器連接
conn, err := net.Dial("tcp", "127.0.0.1:8090")
if err != nil {
fmt.Println("client start err ", err)
return
}
for {
//2 調(diào)用連接向服務(wù)器發(fā)數(shù)據(jù)
_, err := conn.Write([]byte("Hello Zinx v0.2"))
if err != nil {
fmt.Println("write conn err ", err)
return
}
// 3 讀取服務(wù)器返回的數(shù)據(jù)
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循環(huán)導(dǎo)致其他程序無法獲取cpu時間片
time.Sleep(time.Second * 1)
}
}
- Server.go
package main
import "myTest/zinx/znet"
func main() {
s := znet.NewServer("[Zinx v2.0]")
s.Serve()
}
②測試結(jié)果
3 實現(xiàn)基礎(chǔ)Router[V3.0]
3.1 Request請求封裝
將連接和數(shù)據(jù)綁定在一起
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模塊
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 {
}
//這里做了空實現(xiàn),直接讓后續(xù)Router繼承BaseRouter,然后根據(jù)需要重寫對應(yīng)方法即可
func (br *BaseRouter) PreHandle(request ziface.IRequest) {}
func (br *BaseRouter) Handler(request ziface.IRequest) {}
func (br *BaseRouter) PostHandler(request ziface.IRequest) {}
3.3 框架集成router模塊
- 取消znet/server.go中的HandlerFunc模塊,改為Router。server.go中添加Router屬性
![]()
- 將znet/connection.go中的callback_api ziface.HandleFunc參數(shù)改為Router
![]()
zinx/znet/connection.go
package znet
import (
"fmt"
"myTest/zinx/ziface"
"net"
)
type Connection struct {
Conn *net.TCPConn
ConnID uint32
isClosed bool
//告知當(dāng)前的連接已經(jīng)退出
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()
//讀取數(shù)據(jù)
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)
}
//獲取當(dāng)前連接的Conn對象
func (c *Connection) GetTCPConnection() *net.TCPConn {
return c.Conn
}
//獲取當(dāng)前連接模塊的id
func (c *Connection) GetConnectionID() uint32 {
return c.ConnID
}
//獲取遠程客戶端的TCP狀態(tài) IP:Port
func (c *Connection) RemoteAddr() net.Addr {
return c.Conn.RemoteAddr()
}
//發(fā)送數(shù)據(jù)
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() {
//啟動服務(wù)監(jiān)聽端口
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...")
//阻塞連接,處理業(yè)務(wù)
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處理啟動當(dāng)前conn
go dealConn.Start()
}
}()
}
func (s *Server) Stop() {
}
func (s *Server) Serve() {
s.Start()
//阻塞,一直讀取客戶端所發(fā)送過來的消息
select {}
}
func (s *Server) AddRouter(router ziface.IRouter) {
s.Router = 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 創(chuàng)建服務(wù)器連接
conn, err := net.Dial("tcp", "127.0.0.1:8090")
if err != nil {
fmt.Println("client start err ", err)
return
}
for {
//2 調(diào)用連接向服務(wù)器發(fā)數(shù)據(jù)
_, err := conn.Write([]byte("Hello Zinx v0.3"))
if err != nil {
fmt.Println("write conn err ", err)
return
}
// 3 讀取服務(wù)器返回的數(shù)據(jù)
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循環(huán)導(dǎo)致其他程序無法獲取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()
}
最終效果:
按照模板方法設(shè)計模式,完成了調(diào)用
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 //當(dāng)前全局Zinx的server對象
Host string //當(dāng)前服務(wù)器主機監(jiān)聽的ip
TcpPort int //當(dāng)前服務(wù)器主機監(jiān)聽的端口號
Name string //當(dāng)前服務(wù)器的名稱
Version string //當(dāng)前Zinx的版本號
MaxConn int //當(dāng)前服務(wù)器所允許的最大連接數(shù)
MaxPackageSize uint32 //當(dāng)前Zinx框架數(shù)據(jù)包的最大值
}
var GlobalObject *GlobalObj
//從配置文件中重新加載GlobalObject的信息
func (g *GlobalObj) Reload() {
data, err := ioutil.ReadFile("conf/zinx.json")
if err != nil {
panic(err)
}
//將json文件數(shù)據(jù)解析到struct中
err = json.Unmarshal(data, &GlobalObject)
if err != nil {
panic(err)
}
}
//在其他文件導(dǎo)入該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中去加載用戶自定義的參數(shù)
GlobalObject.Reload()
}
4.2 替換之前server.go中的硬編碼
包括/zinx/znet/server.go和/zinx/znet/connection.go部分
- server:
![]()
- connection:
![]()
4.3 測試
編寫myDemo/ZinxV4.0
- 并且編寫對應(yīng)的.json配置文件(Client.go與Server.go都與V3.0一樣)
zinx.json
{
"Name": "Zinx Server Application",
"Version": "V0.4",
"Host": "0.0.0.0",
"TcpPort": 8091,
"MaxConn": 30,
"MaxPackageSize": 1024
}
最后效果:文章來源:http://www.zghlxwxcb.cn/news/detail-739568.html
參考:https://www.yuque.com/aceld/npyr8s/bgftov文章來源地址http://www.zghlxwxcb.cn/news/detail-739568.html
到了這里,關(guān)于從0到1開發(fā)go-tcp框架【1-搭建server、封裝連接與業(yè)務(wù)綁定、實現(xiàn)基礎(chǔ)Router、抽取全局配置文件】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!