[Zinx]
<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>
<3.Design and Implementation of the Zinx Framework's Routing Module>
<4.Zinx Global Configuration>
<5.Zinx Message Encapsulation Module Design and Implementation>
<6.Design and Implementation of Zinx Multi-Router Mode>
<7. Building Zinx's Read-Write Separation Model>
<8.Zinx Message Queue and Task Worker Pool Design and Implementation>
<9. Zinx Connection Management and Property Setting>
[Zinx Application - MMO Game Case Study]
<10. Application Case Study using the Zinx Framework>
<11. MMO Online Game AOI Algorithm>
<12.Data Transmission Protocol: Protocol Buffers>
<13. MMO Game Server Application Protocol>
<14. Building the Project and User Login>
<15. World Chat System Implementation>
<16. Online Location Information Synchronization>
<17. Moving position and non-crossing grid AOI broadcasting>
<18. Player Logout>
<19. Movement and AOI Broadcast Across Grids>
source code
https://github.com/aceld/zinx/blob/master/examples/zinx_release/zinx-v0.2.tar.gz
V0.1 version has implemented a basic Server framework, and now we need to further encapsulate the client connections and bind different business logic to different client connections with another layer of interface encapsulation. First, we need to build the architecture.
2.1 Zinx-V0.2 Code Implementation
Next, we will use a few steps to implement Zinx-V0.2 version.
2.1.1 Create iconnection.go in ziface
Create an interface file called iconnection.go under the ziface directory as an interface file, and the corresponding implementation file will be placed in connection.go under znet.
The interface file is implemented as follows:
//zinx/ziface/iconnection.go
package ziface
import "net"
// Define the connection interface
type IConnection interface {
// Start the connection, making the current connection work
Start()
// Stop the connection, ending the current connection state
Stop()
// Get the raw socket TCPConn from the current connection
GetTCPConnection() *net.TCPConn
// Get the current connection ID
GetConnID() uint32
// Get the remote client's address information
RemoteAddr() net.Addr
}
// Define an interface for handling connection business uniformly
type HandFunc func(*net.TCPConn, []byte, int) error
Some of the basic methods of this interface are as follows:
(1) Start(), start the connection, and let the current connection start reading and writing related work.
(2) Stop(), stop the connection, end the current connection state, and recycle related resources and close relevant logic.
(3) GetTCPConnection(), get the socket TCPConn type connection data structure.
(4) GetConnID(), get the current connection ID. Each connection will be assigned a connection ID. The purpose is to distinguish different connections or manage multiple connections uniformly, and count the number of connections.
(5) RemoteAddr(), get the remote client connection. For each connection that has been established, there will be address information for the remote end of the socket.
It is important to note the HandFunc function type:
type HandFunc func(*net.TCPConn, []byte, int) error
This is the function interface for handling business for all conn connections. The first parameter is the raw socket connection, the second parameter is the data requested by the client, and the third parameter is the length of the data requested by the client. In this way, if you want to specify a conn's business processing, just define a function of type HandFunc and bind it to the connection.
2.1.2 Create connection.go
in the /znet/ directory to implement Connection that implements the IConnection interface. The specific code is as follows:
//zinx/znet/connection.go
package znet
import (
"fmt"
"net"
"zinx/ziface"
)
type Connection struct {
// Current connection's socket TCP socket
Conn *net.TCPConn
// Current connection's ID, also known as SessionID, ID is
globally unique
ConnID uint32
// Current connection's close status
isClosed bool
// The handle function of this connection's api
handleAPI ziface.HandFunc
// Channel to inform that the connection has exited/stopped
ExitBuffChan chan bool
}
The Connection struct includes the following attributes:
(1) Conn, the TCP socket of the current connection, encapsulated standard library TCPConn structure.
(2) ConnID, the ID of the current connection, which requires globally unique.
(3) isClosed, the close status of the current connection.
(4) handleAPI, the handling method API bound to the current connection, which is the callback business method registered by the developer.
(5) ExitBuffChan, a channel used to notify that the connection has exited/stopped for communication synchronization purposes.
Next, a construction method for Connection is provided, named NewConnection, and the implementation code is as follows:
//zinx/znet/connection.go
func NewConntion(conn *net.TCPConn, connID uint32, callback_api ziface.HandFunc) *Connection{
c := &Connection{
Conn: conn,
ConnID: connID,
isClosed: false,
handleAPI: callback_api,
ExitBuffChan: make(chan bool, 1),
}
return c
}
Next, we provide a functional method for Connection, StartReader(). The main logic of this function is an infinite loop that waits for server messages in a blocking manner. When data arrives, it is read into local memory. Once the data is complete, it is passed to the handleAPI registered by the developer to handle business. Any exceptions or errors will break out of this loop and end the current method. The specific implementation code is as follows:
//zinx/znet/connection.go
/* Goroutine to handle reading data from the connection */
func (c *Connection) StartReader() {
fmt.Println("Reader Goroutine is running")
defer fmt.Println(c.RemoteAddr().String(), " conn reader exit!")
defer c.Stop()
for {
// Read the largest amount of data to buf
buf := make([]byte, 512)
cnt, err := c.Conn.Read(buf)
if err != nil {
fmt.Println("recv buf err ", err)
c.ExitBuffChan <- true
continue
}
// Call the handle function registered by the developer for this connection to handle the business logic
if err := c.handleAPI(c.Conn, buf, cnt); err !=nil {
fmt.Println("connID ", c.ConnID, " handle is error")
c.ExitBuffChan <- true
return
}
}
}
If an exception occurs in the middle, the reader goroutine will send a message through the ExitBuffChan, writing a bool value to notify other goroutines that the current connection has exited, so as to handle some other cleanup tasks. The Start() method implemented by Connection is as follows:
//zinx/znet/connection.go
func (c *Connection) Start() {
go c.StartReader()
for {
select {
case <- c.ExitBuffChan:
return
}
}
}
Start a Goroutine to execute the business logic of reading data, and the main logic is permanently blocked until ExitBuffChan has a message to read, indicating that the connection has exited, and additional recovery actions can be performed before exiting. The implementation of the Stop() method for Connection is as follows:
//zinx/znet/connection.go
// Stop the connection and end the current connection state.
func (c *Connection) Stop() {
//1. If the current connection is already closed
if c.isClosed == true {
return
}
c.isClosed = true
// TODO Connection Stop() If the user has registered the close callback business of the connection, it should be called explicitly at this moment.
// Close the socket connection
c.Conn.Close()
// Notify the business that reads data from the buffer queue that the connection has been closed
c.ExitBuffChan <- true
// Close all channels for this connection
close(c.ExitBuffChan)
}
The Stop() method is used to actively close the connection. The implementation of GetTCPConnection(), GetConnID(), and RemoteAddr() methods for Connection is as follows:
//zinx/znet/connection.go
// Get the original socket TCPConn from the current connection
func (c *Connection) GetTCPConnection() *net.TCPConn {
return c.Conn
}
// Get the current connection ID
func (c *Connection) GetConnID() uint32{
return c.ConnID
}
// Get the remote client address information
func (c *Connection) RemoteAddr() net.Addr {
return c.Conn.RemoteAddr()
}
2.1.3 Correct the connection business logic handling for conn in server.go.
In server.go, the defined Connection object is integrated, and a connection handling callback method CallBackToClient() is defined. The current business is the echo business, which echoes the data transmitted by the peer back to the peer. The modified code for server.go is as follows:
//zinx/znet/server.go
package znet
import (
"errors"
"fmt"
"net"
"time"
"zinx/ziface"
)
// iServer implementation, defines a Server service class
type Server struct {
// the name of the server
Name string
// tcp4 or other
IPVersion string
// the IP address that the server is bound to
IP string
// the port that the server is bound to
Port int
}
//============== Define the handle API for the current client connection ===========
func CallBackToClient(conn *net.TCPConn, data []byte, cnt int) error {
// echo business
fmt.Println("[Conn Handle] CallBackToClient ... ")
if _, err := conn.Write(data[:cnt]); err !=nil {
fmt.Println("write back buf err ", err)
return errors.New("CallBackToClient error")
}
return nil
}
//============== Implement all interface methods in ziface.IServer ========
// start the network service
func (s *Server) Start() {
fmt.Printf("[START] Server listenner at IP: %s, Port %d, is starting\n", s.IP, s.Port)
// start a go routine to do the server listener business
go func() {
// 1. get a TCP address
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
}
// 2. listen to the server address
listenner, err:= net.ListenTCP(s.IPVersion, addr)
if err != nil {
fmt.Println("listen", s.IPVersion, "err", err)
return
}
// the listener has started successfully
fmt.Println("start Zinx server ", s.Name, " succ, now listenning...")
// TODO server.go should have a method to generate ID automatically
var cid uint32
cid = 0
// 3. start the server network connection business
for {
// 3.1. block and wait for the client to establish a connection request
conn, err := listenner.AcceptTCP()
if err != nil {
fmt.Println("Accept err ", err)
continue
}
// 3.2. TODO Server.Start() set the maximum connection control of the server. If the maximum connection is exceeded, then close this new connection
// 3.3. handle the new connection request business, where handler and conn are bound
dealConn := NewConntion(conn, cid, CallBackToClient)
cid ++
// 3.4. start the handling business of the current connection
go dealConn.Start()
}
}()
}
// ... ...
CallBackToClient() is a handle method bound to the conn object, and currently, the server-side is forcibly bound to the echo business. Later, the framework will be enriched so that users can customize the handle.
In the Start() method, the following modifications are mainly made:
//…
//3.3 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的
dealConn := NewConntion(conn, cid, CallBackToClient)
cid ++
//3.4 启动当前链接的处理业务
go dealConn.Start()
//…
Okay, now the Connection connection and handleAPI have been bound. Let's test the usage of Zinx-V0.2 framework.
2.2 Using Zinx-V0.2 to complete the application
Actually, the external interface of the Zinx framework has not changed, so the V0.1 test is still valid and the code remains the same:
//Server.go
package main
import (
"zinx/znet"
)
//Test function for Server module
func main() {
//1 create a server handle s
s := znet.NewServer("[zinx V0.1]")
//2 start the service
s.Serve()
}
Start Server.go and execute the following command:
go run Server.go
The client code is the same as before, as follows:
//Client.go
package main
import (
"fmt"
"net"
"time"
)
func main() {
fmt.Println("Client Test ... start")
//Wait for 3 seconds before making a test request to give the server a chance to start up
time.Sleep(3 * time.Second)
conn,err := net.Dial("tcp", "127.0.0.1:7777")
if err != nil {
fmt.Println("client start err, exit!")
return
}
for {
_, err := conn.Write([]byte("hahaha"))
if err !=nil {
fmt.Println("write error 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)
time.Sleep(1*time.Second)
}
}
Start Client.go to test, the command is as follows:
go run Client.go
The result is the same as before, and the callback method of Connection has been called, as follows:
[START] Server listenner at IP: 0.0.0.0, Port 7777, is starting
Client Test ... start
listen tcp4 err listen tcp4 0.0.0.0:7777: bind: address already in use
server call back : hello ZINX, cnt = 6
server call back : hello ZINX, cnt = 6
server call back : hello ZINX, cnt = 6
server call back : hello ZINX, cnt = 6
2.4 Summary
Now we have constructed the prototype of Zinx, defined the abstract layer IServer and IConnection, and implemented the corresponding implementation layer classes Server and Connection. We have completed the basic business method binding, but we are still far from a true framework. Next, we will continue to improve the Zinx framework.
[Zinx]
<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>
<3.Design and Implementation of the Zinx Framework's Routing Module>
<4.Zinx Global Configuration>
<5.Zinx Message Encapsulation Module Design and Implementation>
<6.Design and Implementation of Zinx Multi-Router Mode>
<7. Building Zinx's Read-Write Separation Model>
<8.Zinx Message Queue and Task Worker Pool Design and Implementation>
<9. Zinx Connection Management and Property Setting>
[Zinx Application - MMO Game Case Study]
<10. Application Case Study using the Zinx Framework>
<11. MMO Online Game AOI Algorithm>
<12.Data Transmission Protocol: Protocol Buffers>
<13. MMO Game Server Application Protocol>
<14. Building the Project and User Login>
<15. World Chat System Implementation>
<16. Online Location Information Synchronization>
<17. Moving position and non-crossing grid AOI broadcasting>
<18. Player Logout>
<19. Movement and AOI Broadcast Across Grids>
Author:
discord: https://discord.gg/xQ8Xxfyfcz
zinx: https://github.com/aceld/zinx
github: https://github.com/aceld
aceld's home: https://yuque.com/aceld
Top comments (0)