• 并发服务器框架——zinx

并发服务器框架——zinx

2025-04-26 09:51:28 3 阅读

zinx框架

Zinx 是一个用 Go 语言编写的高性能、轻量级的 TCP 服务器框架,它被设计为简单、快速且易于使用。Zinx 提供了一系列的功能,包括但不限于连接管理、数据编解码、业务处理、负载均衡等,适用于构建各种 TCP 网络服务,如游戏服务器、即时通讯服务器等。

下面实现zinx的多个功能包括:路由、全局配置、消息封装、读写分离、消息队列、链接管理等。


utils包下GlobalObj.go

package utils

import (
	"datarace/zinx/ziface"
	"encoding/json"
	"io/ioutil"
)

/*
存储一切有关Zinx框架的全局参数,供其他模块使用
一些参数也可以通过 用户根据 zinx.json来配置
*/
type GlobalObj struct {
	TcpServer        ziface.IServer //当前Zinx的全局Server对象
	Host             string         //当前服务器主机IP
	TcpPort          int            //当前服务器主机监听端口号
	Name             string         //当前服务器名称
	Version          string         //当前Zinx版本号
	MaxPacketSize    uint32         //都需数据包的最大值
	MaxConn          int            //当前服务器主机允许的最大链接个数
	WorkerPoolSize   uint32         //业务工作Worker池的数量
	MaxWorkerTaskLen uint32         //业务工作Worker对应负责的任务队列最大任务存储数量
	ConfFilePath     string
	MaxMsgChanLen    int
}

/*
定义一个全局的对象
*/
var GlobalObject *GlobalObj

// 读取用户的配置文件
func (g *GlobalObj) Reload() {
	data, err := ioutil.ReadFile("zinx.json")
	if err != nil {
		panic(err)
	}
	//将json数据解析到struct中
	//fmt.Printf("json :%s
", data)
	err = json.Unmarshal(data, &GlobalObject)
	if err != nil {
		panic(err)
	}
}
func init() {
	//初始化GlobalObject变量,设置一些默认值
	GlobalObject = &GlobalObj{
		Name:             "ZinxServerApp",
		Version:          "V0.4",
		TcpPort:          7777,
		Host:             "0.0.0.0",
		MaxConn:          12000,
		MaxPacketSize:    4096,
		ConfFilePath:     "conf/zinx.json",
		WorkerPoolSize:   10,
		MaxWorkerTaskLen: 1024,
	}
	//从配置文件中加载一些用户配置的参数
	GlobalObject.Reload()
}

ziface包

package ziface

type IConnManager interface {
	Add(conn IConnection)                   //添加链接
	Remove(conn IConnection)                //删除连接
	Get(connID uint32) (IConnection, error) //利用ConnID获取链接
	Len() int                               //获取当前连接
	ClearConn()                             //删除并停止所有链接
}

package ziface

import "net"

type IConnection interface {
	Start()
	Stop()
	GetConnID() uint32
	GetTCPConnection() *net.TCPConn
	RemoteAddr() net.Addr
	SendMsg(msgId uint32, data []byte) error
	//直接将Message数据发送给远程的TCP客户端(有缓冲)
	SendBuffMsg(msgId uint32, data []byte) error //添加带缓冲发送消息接口
	//设置链接属性
	SetProperty(key string, value interface{})
	//获取链接属性
	GetProperty(key string) (interface{}, error)
	//移除链接属性
	RemoveProperty(key string)
}
type HandFunc func(*net.TCPConn, []byte, int) error

package ziface

type IDataPack interface {
	GetHeadLen() int32
	Pack(msg IMessage) ([]byte, error)
	Unpack([]byte) (IMessage, error)
}
package ziface

type IMessage interface {
	GetDataLen() uint32
	GetMsgId() uint32
	GetData() []byte
	SetMsgId(uint32)
	SetData([]byte)
	SetDataLen(uint32)
}

package ziface

type IMsgHandle interface {
	DoMsgHandler(request IRequest)          //马上以非阻塞方式处理消息
	AddRouter(msgId uint32, router IRouter) //为消息添加具体的处理逻辑
	StartWorkerPool()                       //启动worker工作池
	SendMsgToTaskQueue(request IRequest)    //将消息交给TaskQueue,由worker进行处理
}

package ziface

type IRequest interface {
	GetConnection() IConnection
	GetData() []byte
	GetMsgID() uint32
}

package ziface

type IRouter interface {
	PreHandle(req IRequest)
	Handle(req IRequest)
	PostHandle(req IRequest)
}

package ziface

type IServer interface {
	Start()
	Stop()
	Serve()
	AddRouter(msgId uint32, router IRouter)
	//得到链接管理
	GetConnMgr() IConnManager
	//设置该Server的连接创建时Hook函数
	SetOnConnStart(func(IConnection))
	//设置该Server的连接断开时的Hook函数
	SetOnConnStop(func(IConnection))
	//调用连接OnConnStart Hook函数
	CallOnConnStart(conn IConnection)
	//调用连接OnConnStop Hook函数
	CallOnConnStop(conn IConnection)
}

znet包
connection

package znet

import (
	"datarace/zinx/utils"
	"datarace/zinx/ziface"
	"errors"
	"fmt"
	"io"
	"net"
	"sync"
)

type Connection struct {
	//当前Conn属于哪个Server
	TcpServer ziface.IServer //当前conn属于哪个server,在conn初始化的时候添加即可
	//当前连接的socket TCP套接字
	Conn *net.TCPConn
	//当前连接的ID 也可以称作为SessionID,ID全局唯一
	ConnID uint32
	//当前连接的关闭状态
	isClosed bool
	//消息管理MsgId和对应处理方法的消息管理模块
	MsgHandler ziface.IMsgHandle
	//告知该链接已经退出/停止的channel
	ExitBuffChan chan bool
	//无缓冲管道,用于读、写两个goroutine之间的消息通信
	msgChan chan []byte
	//有关冲管道,用于读、写两个goroutine之间的消息通信
	msgBuffChan chan []byte
	//链接属性
	property map[string]interface{}
	//保护链接属性修改的锁
	propertyLock sync.RWMutex
}

// 创建连接的方法
func NewConntion(server ziface.IServer, conn *net.TCPConn, connID uint32, msgHandler ziface.IMsgHandle) *Connection {
	//初始化Conn属性
	c := &Connection{
		TcpServer:    server, //将隶属的server传递进来
		Conn:         conn,
		ConnID:       connID,
		isClosed:     false,
		MsgHandler:   msgHandler,
		ExitBuffChan: make(chan bool, 1),
		msgChan:      make(chan []byte),
		msgBuffChan:  make(chan []byte, utils.GlobalObject.MaxMsgChanLen), //不要忘记初始化
		property:     make(map[string]interface{}),                        //对链接属性map初始化
	}
	//将新创建的Conn添加到链接管理中
	c.TcpServer.GetConnMgr().Add(c) //将当前新创建的连接添加到ConnManager中
	return c
}

// 设置链接属性
func (c *Connection) SetProperty(key string, value interface{}) {
	c.propertyLock.Lock()
	defer c.propertyLock.Unlock()
	c.property[key] = value
}

// 获取链接属性
func (c *Connection) GetProperty(key string) (interface{}, error) {
	c.propertyLock.RLock()
	defer c.propertyLock.RUnlock()
	if value, ok := c.property[key]; ok {
		return value, nil
	} else {
		return nil, errors.New("no property found")
	}
}

// 移除链接属性
func (c *Connection) RemoveProperty(key string) {
	c.propertyLock.Lock()
	defer c.propertyLock.Unlock()
	delete(c.property, key)
}
func (c *Connection) startReader() {
	fmt.Println("[Reader Goroutine is running]")
	defer fmt.Println(c.RemoteAddr().String(), "[conn Reader exit!]")
	defer c.Stop()
	for {
		// 创建拆包解包的对象
		dp := NewDataPack()
		//读取客户端的Msg head
		headData := make([]byte, dp.GetHeadLen())
		if _, err := io.ReadFull(c.GetTCPConnection(), headData); err != nil {
			fmt.Println("read msg head error ", err)
			break
		}
		//拆包,得到msgid 和 datalen 放在msg中
		msg, err := dp.Unpack(headData)
		if err != nil {
			fmt.Println("unpack error ", err)
			break
		}
		//根据 dataLen 读取 data,放在msg.Data中
		var data []byte
		if msg.GetDataLen() > 0 {
			data = make([]byte, msg.GetDataLen())
			if _, err := io.ReadFull(c.GetTCPConnection(), data); err != nil {
				fmt.Println("read msg data error ", err)
				continue
			}
		}
		msg.SetData(data)
		//得到当前客户端请求的Request数据
		req := Request{
			conn: c,
			msg:  msg,
		}
		//从绑定好的消息和对应的处理方法中执行对应的Handle方法
		if utils.GlobalObject.WorkerPoolSize > 0 {
			//已经启动工作池机制,将消息交给Worker处理
			c.MsgHandler.SendMsgToTaskQueue(&req)
		} else {
			//从绑定好的消息和对应的处理方法中执行对应的Handle方法
			go c.MsgHandler.DoMsgHandler(&req)
		}
	}
}

// 启动连接,让当前连接开始工作
func (c *Connection) Start() {
	//1 开启用户从客户端读取数据流程的Goroutine
	go c.startReader()
	//2 开启用于写回客户端数据流程的Goroutine
	go c.StartWriter()
	//按照用户传递进来的创建连接时需要处理的业务,执行钩子方法
	c.TcpServer.CallOnConnStart(c)
}

//func (c *Connection) Start() {
//	go c.startReader()
//	for {
//		select {
//		case <-c.ExitBuffChan:
//			return
//		}
//	}
//}

// 停止连接,结束当前连接状态M
func (c *Connection) Stop() {
	fmt.Println("Conn Stop()...ConnID = ", c.ConnID)
	//如果当前链接已经关闭
	if c.isClosed == true {
		return
	}
	c.isClosed = true
	//==================
	//如果用户注册了该链接的关闭回调业务,那么在此刻应该显示调用
	c.TcpServer.CallOnConnStop(c)
	//==================
	// 关闭socket链接
	c.Conn.Close()
	//关闭Writer
	c.ExitBuffChan <- true
	//将链接从连接管理器中删除
	c.TcpServer.GetConnMgr().Remove(c)
	//关闭该链接全部管道
	close(c.ExitBuffChan)
	close(c.msgBuffChan)
}

/*
写消息Goroutine, 用户将数据发送给客户端
*/
func (c *Connection) StartWriter() {
	fmt.Println("[Writer Goroutine is running]")
	defer fmt.Println(c.RemoteAddr().String(), "[conn Writer exit!]")
	for {
		select {
		case data := <-c.msgChan:
			//有数据要写给客户端
			if _, err := c.Conn.Write(data); err != nil {
				fmt.Println("Send Data error:, ", err, " Conn Writer exit")
				return
			}
			//针对有缓冲channel需要些的数据处理
		case data, ok := <-c.msgBuffChan:
			if ok {
				//有数据要写给客户端
				if _, err := c.Conn.Write(data); err != nil {
					fmt.Println("Send Buff Data error:, ", err, " Conn Writer exit")
					return
				}
			} else {
				break
				fmt.Println("msgBuffChan is Closed")
			}
		case <-c.ExitBuffChan:
			return
		}
	}
}

// 直接将Message数据发送数据给远程的TCP客户端
func (c *Connection) SendMsg(msgId uint32, data []byte) error {
	if c.isClosed == true {
		return errors.New("Connection closed when send msg")
	}
	//将data封包,并且发送
	dp := NewDataPack()
	msg, err := dp.Pack(NewMsgPackage(msgId, data))
	if err != nil {
		fmt.Println("Pack error msg id = ", msgId)
		return errors.New("Pack error msg ")
	}
	//写回客户端
	c.msgChan <- msg //将之前直接回写给conn.Write的方法 改为 发送给Channel 供Writer读取
	return nil
}

// 从当前连接获取原始的socket TCPConn
func (c *Connection) GetTCPConnection() *net.TCPConn {
	return c.Conn
}

// 获取当前连接ID
func (c *Connection) GetConnID() uint32 {
	return c.ConnID
}

// 获取远程客户端地址信息
func (c *Connection) RemoteAddr() net.Addr {
	return c.Conn.RemoteAddr()
}

// // 直接将Message数据发送数据给远程的TCP客户端
//
//	func (c *Connection) SendMsg(msgId uint32, data []byte) error {
//		if c.isClosed == true {
//			return errors.New("Connection closed when send msg")
//		}
//		//将data封包,并且发送
//		dp := NewDataPack()
//		msg, err := dp.Pack(NewMsgPackage(msgId, data))
//		if err != nil {
//			fmt.Println("Pack error msg id = ", msgId)
//			return errors.New("Pack error msg ")
//		}
//		//写回客户端
//		if _, err := c.Conn.Write(msg); err != nil {
//			fmt.Println("Write msg id ", msgId, " error ")
//			c.ExitBuffChan <- true
//			return errors.New("conn Write error")
//		}
//		return nil
//	}
func (c *Connection) SendBuffMsg(msgId uint32, data []byte) error {
	if c.isClosed == true {
		return errors.New("Connection closed when send buff msg")
	}
	//将data封包,并且发送
	dp := NewDataPack()
	msg, err := dp.Pack(NewMsgPackage(msgId, data))
	if err != nil {
		fmt.Println("Pack error msg id = ", msgId)
		return errors.New("Pack error msg ")
	}
	//写回客户端
	c.msgBuffChan <- msg
	return nil
}

ConnManager

package znet

import (
	"datarace/zinx/ziface"
	"errors"
	"fmt"
	"sync"
)

type ConnManager struct {
	connections map[uint32]ziface.IConnection
	connLock    sync.RWMutex
}

/*
创建一个链接管理
*/
func NewConnManager() *ConnManager {
	return &ConnManager{
		connections: make(map[uint32]ziface.IConnection),
	}
}

// 添加链接
func (connMgr *ConnManager) Add(conn ziface.IConnection) {
	//保护共享资源Map 加写锁
	connMgr.connLock.Lock()
	defer connMgr.connLock.Unlock()
	//将conn连接添加到ConnMananger中
	connMgr.connections[conn.GetConnID()] = conn
	fmt.Println("connection add to ConnManager successfully: conn num = ", connMgr.Len())
}

// 删除连接
func (connMgr *ConnManager) Remove(conn ziface.IConnection) {
	//保护共享资源Map 加写锁
	connMgr.connLock.Lock()
	defer connMgr.connLock.Unlock()
	//删除连接信息
	delete(connMgr.connections, conn.GetConnID())
	fmt.Println("connection Remove ConnID=", conn.GetConnID(), " successfully: conn num = ", connMgr.Len())
}

// 利用ConnID获取链接
func (connMgr *ConnManager) Get(connID uint32) (ziface.IConnection, error) {
	//保护共享资源Map 加读锁
	connMgr.connLock.RLock()
	defer connMgr.connLock.RUnlock()
	if conn, ok := connMgr.connections[connID]; ok {
		return conn, nil
	} else {
		return nil, errors.New("connection not found")
	}
}

// 获取当前连接
func (connMgr *ConnManager) Len() int {
	return len(connMgr.connections)
}

// 清除并停止所有连接
func (connMgr *ConnManager) ClearConn() {
	//保护共享资源Map 加写锁
	connMgr.connLock.Lock()
	defer connMgr.connLock.Unlock()
	//停止并删除全部的连接信息
	for connID, conn := range connMgr.connections {
		//停止
		conn.Stop()
		//删除
		delete(connMgr.connections, connID)
	}
	fmt.Println("Clear All Connections successfully: conn num = ", connMgr.Len())
}

datapack

package znet

import (
	"bytes"
	"datarace/zinx/utils"
	"datarace/zinx/ziface"
	"encoding/binary"
	"errors"
)

type DataPack struct{}

func NewDataPack() *DataPack {
	return &DataPack{}
}
func (dp *DataPack) GetHeadLen() uint32 {
	//Id uint32(4字节) +  DataLen uint32(4字节)
	return 8
}

// 封包方法(压缩数据)
func (dp *DataPack) Pack(msg ziface.IMessage) ([]byte, error) {
	//创建一个存放bytes字节的缓冲
	dataBuff := bytes.NewBuffer([]byte{})
	//写dataLen
	if err := binary.Write(dataBuff, binary.LittleEndian, msg.GetDataLen()); err != nil {
		return nil, err
	}
	//写msgID
	if err := binary.Write(dataBuff, binary.LittleEndian, msg.GetMsgId()); err != nil {
		return nil, err
	}
	//写data数据
	if err := binary.Write(dataBuff, binary.LittleEndian, msg.GetData()); err != nil {
		return nil, err
	}
	return dataBuff.Bytes(), nil
}
func (dp *DataPack) Unpack(binaryData []byte) (ziface.IMessage, error) {
	//创建一个从输入二进制数据的ioReader
	dataBuff := bytes.NewReader(binaryData)
	//只解压head的信息,得到dataLen和msgID
	msg := &Message{}
	//读dataLen
	if err := binary.Read(dataBuff, binary.LittleEndian, &msg.DataLen); err != nil {
		return nil, err
	}
	//读msgID
	if err := binary.Read(dataBuff, binary.LittleEndian, &msg.Id); err != nil {
		return nil, err
	}
	//判断dataLen的长度是否超出我们允许的最大包长度
	if utils.GlobalObject.MaxPacketSize > 0 && msg.DataLen > utils.GlobalObject.MaxPacketSize {
		return nil, errors.New("Too large msg data recieved")
	}
	//这里只需要把head的数据拆包出来就可以了,然后再通过head的长度,再从conn读取一次数据
	return msg, nil
}

message

package znet

type Message struct {
	Id      uint32
	DataLen uint32
	Data    []byte
}

func NewMsgPackage(id uint32, data []byte) *Message {
	return &Message{
		Id:      id,
		DataLen: uint32(len(data)),
		Data:    data,
	}
}

// 获取消息数据段长度
func (msg *Message) GetDataLen() uint32 {
	return msg.DataLen
}

// 获取消息ID
func (msg *Message) GetMsgId() uint32 {
	return msg.Id
}

// 获取消息内容
func (msg *Message) GetData() []byte {
	return msg.Data
}

// 设置消息数据段长度
func (msg *Message) SetDataLen(len uint32) {
	msg.DataLen = len
}

// 设计消息ID
func (msg *Message) SetMsgId(msgId uint32) {
	msg.Id = msgId
}

// 设计消息内容
func (msg *Message) SetData(data []byte) {
	msg.Data = data
}

msgHandler

package znet

import (
	"datarace/zinx/utils"
	"datarace/zinx/ziface"
	"fmt"
	"strconv"
)

type MsgHandle struct {
	Apis           map[uint32]ziface.IRouter
	WorkerPoolSize uint32
	TaskQueue      []chan ziface.IRequest
}

func NewMsgHandle() *MsgHandle {
	return &MsgHandle{
		Apis:           make(map[uint32]ziface.IRouter),
		WorkerPoolSize: utils.GlobalObject.WorkerPoolSize,
		TaskQueue:      make([]chan ziface.IRequest, utils.GlobalObject.WorkerPoolSize),
	}
}

// 马上以非阻塞方式处理消息
func (mh *MsgHandle) DoMsgHandler(request ziface.IRequest) {
	handler, ok := mh.Apis[request.GetMsgID()]
	if !ok {
		fmt.Println("api msgId = ", request.GetMsgID(), " is not FOUND!")
		return
	}
	//执行对应处理方法
	handler.PreHandle(request)
	handler.Handle(request)
	handler.PostHandle(request)
}

// 为消息添加具体的处理逻辑
func (mh *MsgHandle) AddRouter(msgId uint32, router ziface.IRouter) {
	//1 判断当前msg绑定的API处理方法是否已经存在
	if _, ok := mh.Apis[msgId]; ok {
		panic("repeated api , msgId = " + strconv.Itoa(int(msgId)))
	}
	//2 添加msg与api的绑定关系
	mh.Apis[msgId] = router
	fmt.Println("Add api msgId = ", msgId)
}

// 启动一个Worker工作流程
func (mh *MsgHandle) StartOneWorker(workerID int, taskQueue chan ziface.IRequest) {
	fmt.Println("Worker ID = ", workerID, " is started.")
	//不断的等待队列中的消息
	for {
		select {
		//有消息则取出队列的Request,并执行绑定的业务方法
		case request := <-taskQueue:
			mh.DoMsgHandler(request)
		}
	}
}

// 启动worker工作池
func (mh *MsgHandle) StartWorkerPool() {
	//遍历需要启动worker的数量,依此启动
	for i := 0; i < int(mh.WorkerPoolSize); i++ {
		//一个worker被启动
		//给当前worker对应的任务队列开辟空间
		mh.TaskQueue[i] = make(chan ziface.IRequest, utils.GlobalObject.MaxWorkerTaskLen)
		//启动当前Worker,阻塞的等待对应的任务队列是否有消息传递进来
		go mh.StartOneWorker(i, mh.TaskQueue[i])
	}
}

// 将消息交给TaskQueue,由worker进行处理
func (mh *MsgHandle) SendMsgToTaskQueue(request ziface.IRequest) {
	//根据ConnID来分配当前的连接应该由哪个worker负责处理
	//轮询的平均分配法则
	//得到需要处理此条连接的workerID
	workerID := request.GetConnection().GetConnID() % mh.WorkerPoolSize
	fmt.Println("Add ConnID=", request.GetConnection().GetConnID(), " request msgID=", request.GetMsgID(), "to workerID=", workerID)
	//将请求消息发送给任务队列
	mh.TaskQueue[workerID] <- request
}

request

package znet

import (
	"datarace/zinx/ziface"
)

type Request struct {
	conn ziface.IConnection
	msg  ziface.IMessage
}

func (r *Request) GetConnection() ziface.IConnection {
	return r.conn
}

// 获取请求消息的数据
func (r *Request) GetData() []byte {
	return r.msg.GetData()
}

// 获取请求的消息的ID
func (r *Request) GetMsgID() uint32 {
	return r.msg.GetMsgId()
}

router

package znet

import "datarace/zinx/ziface"

type BaseRouter struct{}

func (br *BaseRouter) PreHandle(request ziface.IRequest)  {}
func (br *BaseRouter) Handle(request ziface.IRequest)     {}
func (br *BaseRouter) PostHandle(request ziface.IRequest) {}

server

package znet

import (
	"datarace/zinx/utils"
	"datarace/zinx/ziface"
	"fmt"
	"net"
	"time"
)

// iServer 接口实现,定义一个Server服务类
type Server struct {
	//服务器的名称
	Name string
	//tcp4 or other
	IPVersion string
	//服务绑定的IP地址
	IP string
	//服务绑定的端口
	Port int
	//当前Server的消息管理模块,用来绑定MsgId和对应的处理方法
	msgHandler ziface.IMsgHandle
	//当前Server的链接管理器
	ConnMgr ziface.IConnManager
	//新增两个hook函数原型
	//该Server的连接创建时Hook函数
	OnConnStart func(conn ziface.IConnection)
	//该Server的连接断开时的Hook函数
	OnConnStop func(conn ziface.IConnection)
}

// 得到链接管理
func (s *Server) GetConnMgr() ziface.IConnManager {
	return s.ConnMgr
}

// ============== 实现 ziface.IServer 里的全部接口方法 ========
// 开启网络服务
func (s *Server) Start() {
	fmt.Printf("[START] Server listenner at IP: %s, Port %d, is starting
", s.IP, s.Port)
	fmt.Printf("[START] Server name: %s,listenner at IP: %s, Port %d is starting
", s.Name, s.IP, s.Port)
	fmt.Printf("[Zinx] Version: %s, MaxConn: %d,  MaxPacketSize: %d
",
		utils.GlobalObject.Version,
		utils.GlobalObject.MaxConn,
		utils.GlobalObject.MaxPacketSize)
	//开启一个go去做服务端Linster业务
	go func() {
		//1 获取一个TCP的Addr
		s.msgHandler.StartWorkerPool()
		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 监听服务器地址
		listenner, err := net.ListenTCP(s.IPVersion, addr)
		if err != nil {
			fmt.Println("listen", s.IPVersion, "err", err)
			return
		}
		//已经监听成功
		fmt.Println("start Zinx server  ", s.Name, " succ, now listenning...")
		var cid uint32
		cid = 0
		//3 启动server网络连接业务
		for {
			//3.1 阻塞等待客户端建立连接请求
			conn, err := listenner.AcceptTCP()
			if err != nil {
				fmt.Println("Accept err ", err)
				continue
			}
			//=============
			//3.2 设置服务器最大连接控制,如果超过最大连接,那么则关闭此新的连接
			if s.ConnMgr.Len() >= utils.GlobalObject.MaxConn {
				conn.Close()
				continue
			}
			//=============
			//3.3 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的
			dealConn := NewConntion(s, conn, cid, s.msgHandler)
			cid++
			//3.4 启动当前链接的处理业务
			go dealConn.Start()
			//go func() {
			//	//不断的循环从客户端获取数据
			//	for {
			//		buf := make([]byte, 512)
			//		cnt, err := conn.Read(buf)
			//		if err != nil {
			//			fmt.Println("recv buf err ", err)
			//			continue
			//		}
			//		//回显
			//		if _, err := conn.Write(buf[:cnt]); err != nil {
			//			fmt.Println("write back buf err ", err)
			//			continue
			//		}
			//	}
			//}()
		}
	}()
}
func (s *Server) Stop() {
	fmt.Println("[STOP] Zinx server , name ", s.Name)
	//将其他需要清理的连接信息或者其他信息 也要一并停止或者清理
	s.ConnMgr.ClearConn()
}
func (s *Server) Serve() {
	s.Start()
	//TODO Server.Serve() 是否在启动服务的时候 还要处理其他的事情呢 可以在这里添加
	//阻塞,否则主Go退出, listenner的go将会退出
	for {
		time.Sleep(10 * time.Second)
	}
}
func (s *Server) AddRouter(msgId uint32, router ziface.IRouter) {
	s.msgHandler.AddRouter(msgId, router)
	fmt.Println("Add Router SUCC!! msgID = ", msgId)
}

/*
创建一个服务器句柄
*/
func NewServer() *Server {
	utils.GlobalObject.Reload()
	s := &Server{
		Name:       utils.GlobalObject.Name,
		IPVersion:  "tcp4",
		IP:         utils.GlobalObject.Host,
		Port:       utils.GlobalObject.TcpPort,
		msgHandler: NewMsgHandle(),   //msgHandler 初始化
		ConnMgr:    NewConnManager(), //创建ConnManage
	}
	return s
}

// 设置该Server的连接创建时Hook函数
func (s *Server) SetOnConnStart(hookFunc func(ziface.IConnection)) {
	s.OnConnStart = hookFunc
}

// 设置该Server的连接断开时的Hook函数
func (s *Server) SetOnConnStop(hookFunc func(ziface.IConnection)) {
	s.OnConnStop = hookFunc
}

// 调用连接OnConnStart Hook函数
func (s *Server) CallOnConnStart(conn ziface.IConnection) {
	if s.OnConnStart != nil {
		fmt.Println("---> CallOnConnStart....")
		s.OnConnStart(conn)
	}
}

// 调用连接OnConnStop Hook函数
func (s *Server) CallOnConnStop(conn ziface.IConnection) {
	if s.OnConnStop != nil {
		fmt.Println("---> CallOnConnStop....")
		s.OnConnStop(conn)
	}
}

客户端

package main

import (
	"datarace/zinx/znet"
	"fmt"
	"io"
	"net"
	"time"
)

/*
模拟客户端
*/
func main() {
	fmt.Println("Client Test ... start")
	//3秒之后发起测试请求,给服务端开启服务的机会
	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 {
		//发封包message消息
		dp := znet.NewDataPack()
		msg, _ := dp.Pack(znet.NewMsgPackage(0, []byte("Zinx V0.8 Client0 Test Message")))
		_, err := conn.Write(msg)
		if err != nil {
			fmt.Println("write error err ", err)
			return
		}
		//先读出流中的head部分
		headData := make([]byte, dp.GetHeadLen())
		_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止
		if err != nil {
			fmt.Println("read head error")
			break
		}
		//将headData字节流 拆包到msg中
		msgHead, err := dp.Unpack(headData)
		if err != nil {
			fmt.Println("server unpack err:", err)
			return
		}
		if msgHead.GetDataLen() > 0 {
			//msg 是有data数据的,需要再次读取data数据
			msg := msgHead.(*znet.Message)
			msg.Data = make([]byte, msg.GetDataLen())
			//根据dataLen从io中读取字节流
			_, err := io.ReadFull(conn, msg.Data)
			if err != nil {
				fmt.Println("server unpack data err:", err)
				return
			}
			fmt.Println("==> Recv Msg: ID=", msg.Id, ", len=", msg.DataLen, ", data=", string(msg.Data))
		}
		time.Sleep(1 * time.Second)
	}
}

服务端

package main

import (
	"datarace/zinx/ziface"
	"datarace/zinx/znet"
	"fmt"
)

// ping test 自定义路由
type PingRouter struct {
	znet.BaseRouter
}

// Ping Handle
func (this *PingRouter) Handle(request ziface.IRequest) {
	fmt.Println("Call PingRouter Handle")
	//先读取客户端的数据,再回写ping...ping...ping
	fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData()))
	err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping"))
	if err != nil {
		fmt.Println(err)
	}
}

type HelloZinxRouter struct {
	znet.BaseRouter
}

// HelloZinxRouter Handle
func (this *HelloZinxRouter) Handle(request ziface.IRequest) {
	fmt.Println("Call HelloZinxRouter Handle")
	//先读取客户端的数据,再回写ping...ping...ping
	fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData()))
	err := request.GetConnection().SendBuffMsg(1, []byte("Hello Zinx Router V0.10"))
	if err != nil {
		fmt.Println(err)
	}
}

// 创建连接的时候执行
func DoConnectionBegin(conn ziface.IConnection) {
	fmt.Println("DoConnecionBegin is Called ... ")
	//=============设置两个链接属性,在连接创建之后===========
	fmt.Println("Set conn Name, Home done!")
	conn.SetProperty("Name", "Aceld")
	conn.SetProperty("Home", "https://www.jianshu.com/u/35261429b7f1")
	//===================================================
	err := conn.SendMsg(2, []byte("DoConnection BEGIN..."))
	if err != nil {
		fmt.Println(err)
	}
}

// 连接断开的时候执行
func DoConnectionLost(conn ziface.IConnection) {
	//============在连接销毁之前,查询conn的Name,Home属性=====
	if name, err := conn.GetProperty("Name"); err == nil {
		fmt.Println("Conn Property Name = ", name)
	}
	if home, err := conn.GetProperty("Home"); err == nil {
		fmt.Println("Conn Property Home = ", home)
	}
	//===================================================
	fmt.Println("DoConneciotnLost is Called ... ")
}
func main() {
	//创建一个server句柄
	s := znet.NewServer()
	//注册链接hook回调函数
	s.SetOnConnStart(DoConnectionBegin)
	s.SetOnConnStop(DoConnectionLost)
	//配置路由
	s.AddRouter(0, &PingRouter{})
	s.AddRouter(1, &HelloZinxRouter{})
	//开启服务
	s.Serve()
}


本文地址:https://www.vps345.com/1730.html

搜索文章

Tags

PV计算 带宽计算 流量带宽 服务器带宽 上行带宽 上行速率 什么是上行带宽? CC攻击 攻击怎么办 流量攻击 DDOS攻击 服务器被攻击怎么办 源IP 服务器 linux 运维 游戏 云计算 javascript 前端 chrome edge 阿里云 网络 网络安全 网络协议 llama 算法 opencv 自然语言处理 神经网络 语言模型 ssh ubuntu deepseek Ollama 模型联网 API CherryStudio 进程 操作系统 进程控制 Ubuntu 科技 ai java 人工智能 个人开发 python MCP 数据库 centos oracle 关系型 安全 分布式 Flask FastAPI Waitress Gunicorn uWSGI Uvicorn RTSP xop RTP RTSPServer 推流 视频 宝塔面板访问不了 宝塔面板网站访问不了 宝塔面板怎么配置网站能访问 宝塔面板配置ip访问 宝塔面板配置域名访问教程 宝塔面板配置教程 vue.js audio vue音乐播放器 vue播放音频文件 Audio音频播放器自定义样式 播放暂停进度条音量调节快进快退 自定义audio覆盖默认样式 rust http 开发语言 fastapi mcp mcp-proxy mcp-inspector fastapi-mcp agent sse harmonyos 华为 typescript 计算机网络 编辑器 numpy HCIE 数通 node.js json html5 firefox HarmonyOS Next Windsurf vue3 HTML audio 控件组件 vue3 audio音乐播放器 Audio标签自定义样式默认 vue3播放音频文件音效音乐 自定义audio播放器样式 播放暂停调整声音大小下载文件 kubernetes 容器 学习方法 经验分享 程序人生 机器学习 asm c# cpu 内存 实时 使用 flutter Hyper-V WinRM TrustedHosts cuda cudnn anaconda ESXi ESP32 camera Arduino 电子信息 统信UOS 麒麟 bonding 链路聚合 运维开发 云原生 ollama 大模型 mac 物联网 iot tcp/ip android 鸿蒙 php Dell R750XS udp unity 华为云 嵌入式硬件 单片机 c++ 温湿度数据上传到服务器 Arduino HTTP 面试 性能优化 jdk intellij-idea 架构 YOLO efficientVIT YOLOv8替换主干网络 TOLOv8 学习 GaN HEMT 氮化镓 单粒子烧毁 辐射损伤 辐照效应 uni-app WSL2 深度学习 目标检测 计算机视觉 flask spring boot AI编程 AIGC websocket 开源 pycharm ide pytorch filezilla 无法连接服务器 连接被服务器拒绝 vsftpd 331/530 macos 鸿蒙系统 前端框架 nginx dubbo adb ssl vim golang 后端 大模型微调 windows 实时音视频 音视频 AI AI大模型 程序员 Agent 信息与通信 kafka jmeter 软件测试 kylin arm chatgpt 系统架构 智能路由器 外网访问 内网穿透 端口映射 计算机外设 docker 多线程服务器 Linux网络编程 自动化 n8n 工作流 workflow 监控 自动化运维 gateway Clion Nova ResharperC++引擎 Centos7 远程开发 sqlserver 大数据 大数据平台 tcpdump debian 金融 数据挖掘 数据结构 c语言 笔记 爬虫 网络用户购物行为分析可视化平台 大数据毕业设计 1024程序员节 tomcat conda pillow https AISphereButler yum live555 rtsp rtp vscode Trae IDE AI 原生集成开发环境 Trae AI 测试工具 web安全 WSL win11 无法解析服务器的名称或地址 小程序 微信小程序域名配置 微信小程序服务器域名 微信小程序合法域名 小程序配置业务域名 微信小程序需要域名吗 微信小程序添加域名 电脑 .net Kali Linux 黑客 渗透测试 信息收集 命名管道 客户端与服务端通信 express django web3.py LLM 向日葵 代码调试 ipdb adobe Python 网络编程 聊天服务器 套接字 TCP 客户端 Socket springsecurity6 oauth2 授权服务器 token sas github 创意 社区 DeepSeek-R1 API接口 源码剖析 rtsp实现步骤 流媒体开发 微服务 matplotlib k8s 持续部署 ecmascript KVM redis 缓存 NPS 雨云服务器 雨云 C 环境变量 进程地址空间 DeepSeek 远程控制 远程看看 远程协助 iBMC UltraISO wireshark spring apache rsyslog Cookie mcu word图片自动上传 word一键转存 复制word图片 复制word图文 复制word公式 粘贴word图文 粘贴word公式 C语言 ipython ue4 着色器 ue5 虚幻 DigitalOcean GPU服务器购买 GPU服务器哪里有 GPU服务器 nvidia gpu算力 springcloud bootstrap html YOLOv8 NPU Atlas800 A300I pro asi_bench nextjs react reactjs 软件工程 stm32 webrtc 僵尸进程 mount挂载磁盘 wrong fs type LVM挂载磁盘 Centos7.9 jenkins maven svn VMware安装Ubuntu Ubuntu安装k8s threejs 3D Linux 进程信号 qt stm32项目 腾讯云大模型知识引擎 Deepseek Qwen2.5-coder 离线部署 课程设计 agi firewalld thingsboard postgresql Dify prometheus PVE dell服务器 go 代理模式 devops springboot pip rabbitmq RoboVLM 通用机器人策略 VLA设计哲学 vlm fot robot 视觉语言动作模型 具身智能 服务器配置 生物信息学 YOLOv12 飞牛NAS 飞牛OS MacBook Pro Ubuntu Server Ubuntu 22.04.5 react.js 前端面试题 oceanbase rc.local 开机自启 systemd mybatis 服务器管理 宝塔面板 配置教程 服务器安装 网站管理 嵌入式 linux驱动开发 arm开发 UOS 统信操作系统 transformer ping++ 深度优先 图论 并集查找 换根法 树上倍增 ddos llm llama3 Chatglm 开源大模型 zotero WebDAV 同步失败 ffmpeg mongodb jupyter asp.net大文件上传 asp.net大文件上传源码 ASP.NET断点续传 asp.net上传文件夹 asp.net上传大文件 .net core断点续传 .net mvc断点续传 部署 银河麒麟服务器操作系统 系统激活 gitee 博客 windwos防火墙 defender防火墙 win防火墙白名单 防火墙白名单效果 防火墙只允许指定应用上网 防火墙允许指定上网其它禁止 工业4.0 sql KingBase 跨域 vue 漏洞 并查集 leetcode ollama下载加速 mysql 负载均衡 安全威胁分析 微信小程序 智能手机 NAS Termux Samba LDAP postman mock mock server 模拟服务器 mock服务器 Postman内置变量 Postman随机数据 intellij idea ai小智 语音助手 ai小智配网 ai小智教程 智能硬件 esp32语音助手 diy语音助手 micropython esp32 mqtt 蓝耘科技 元生代平台工作流 ComfyUI IIS服务器 IIS性能 日志监控 腾讯云 MQTT mosquitto 消息队列 fpga开发 r语言 数据可视化 数据分析 .netcore git ansible sqlite 交换机 telnet 远程登录 软件需求 kamailio sip VoIP outlook 高效远程协作 TrustViewer体验 跨设备操作便利 智能远程控制 spring cloud hibernate 安装教程 GPU环境配置 Ubuntu22 CUDA PyTorch Anaconda安装 rust腐蚀 框架搭建 豆瓣 追剧助手 迅雷 nas 微信 远程工作 unity3d list 孤岛惊魂4 产品经理 microsoft 低代码 aws googlecloud 恒源云 微信分享 Image wxopensdk vSphere vCenter 软件定义数据中心 sddc gitlab okhttp CORS Linux PID open webui 虚拟局域网 selete 高级IO LInux echarts matlab 传统数据库升级 银行 大语言模型 LLMs 政务 分布式系统 监控运维 Prometheus Grafana 服务器数据恢复 数据恢复 存储数据恢复 北亚数据恢复 oracle数据恢复 opcua opcda KEPServer安装 实时互动 能力提升 面试宝典 技术 IT信息化 oneapi 鲲鹏 宠物 毕业设计 免费学习 宠物领养 宠物平台 gpt-3 文心一言 设计模式 小番茄C盘清理 便捷易用C盘清理工具 小番茄C盘清理的优势尽显何处? 教你深度体验小番茄C盘清理 C盘变红?!不知所措? C盘瘦身后电脑会发生什么变化? 小艺 Pura X Google pay Apple pay eureka excel 其他 中间件 可信计算技术 安全架构 网络攻击模型 XCC Lenovo 显卡驱动 大模型应用 华为od 华为认证 网络工程师 繁忙 服务器繁忙 解决办法 替代网站 汇总推荐 AI推理 移动云 MS Materials android studio MacMini Mac 迷你主机 mini Apple 自定义客户端 SAS shell embedding visualstudio etcd 数据安全 RBAC 企业微信 Linux24.04 deepin 信息可视化 网页设计 SSL 域名 skynet 硬件架构 LORA NLP Java 蓝桥杯 docker run 数据卷挂载 交互模式 VR手套 数据手套 动捕手套 动捕数据手套 驱动开发 硬件工程 嵌入式实习 ruby 本地部署 api ftp web 技能大赛 av1 电视盒子 机顶盒ROM 魔百盒刷机 pyqt java-ee Kylin-Server 国产操作系统 机器人 线程 3d 数学建模 docker搭建nacos详解 docker部署nacos docker安装nacos 腾讯云搭建nacos centos7搭建nacos EasyConnect Cline springboot远程调试 java项目远程debug docker远程debug java项目远程调试 springboot远程 RustDesk自建服务器 rustdesk服务器 docker rustdesk 黑客技术 URL 输入法 服务器主板 AI芯片 keepalived ssrf 失效的访问控制 eNSP 网络规划 VLAN 企业网络 交互 WebRTC gpt 远程 命令 执行 sshpass 操作 openwrt Redis Desktop ux 多线程 linux环境变量 zabbix bash open Euler dde RTMP 应用层 GPU hadoop DevEco Studio RAID RAID技术 磁盘 存储 opensearch helm 多进程 yolov8 unix xrdp 远程桌面 远程连接 ArcTS 登录 ArcUI GridItem jar gradle arkUI SRS 流媒体 直播 string模拟实现 深拷贝 浅拷贝 经典的string类问题 三个swap 链表 游戏服务器 TrinityCore 魔兽世界 wps 安卓 开发环境 SSL证书 C# MQTTS 双向认证 emqx elk k8s资源监控 annotations自动化 自动化监控 监控service 监控jvm chrome devtools selenium chromedriver IPMITOOL BMC 硬件管理 FTP 服务器 游戏程序 环境迁移 崖山数据库 YashanDB bug VMware创建虚拟机 linux安装配置 Ubuntu 24.04.1 轻量级服务器 NFS redhat dash 正则表达式 Docker Compose docker compose docker-compose tidb GLIBC pdf 群晖 文件分享 iis VSCode 密码学 Cursor raid5数据恢复 磁盘阵列数据恢复 银河麒麟操作系统 国产化 rpc 远程过程调用 Windows环境 TRAE Ubuntu DeepSeek DeepSeek Ubuntu DeepSeek 本地部署 DeepSeek 知识库 DeepSeek 私有化知识库 本地部署 DeepSeek DeepSeek 私有化部署 直播推流 服务器部署ai模型 sqlite3 safari 系统 Anolis nginx安装 环境安装 linux插件下载 nac 802.1 portal 毕设 EtherCAT转Modbus ECT转Modbus协议 EtherCAT转485网关 ECT转Modbus串口网关 EtherCAT转485协议 ECT转Modbus网关 职场和发展 黑苹果 虚拟机 VMware 开机自启动 pygame 小游戏 五子棋 kvm 无桌面 命令行 串口服务器 rag ragflow ragflow 源码启动 elasticsearch 媒体 微信公众平台 risc-v sdkman Portainer搭建 Portainer使用 Portainer使用详解 Portainer详解 Portainer portainer 三级等保 服务器审计日志备份 pyautogui 目标跟踪 OpenVINO 推理应用 css ip命令 新增网卡 新增IP 启动网卡 联想开天P90Z装win10 ci/cd 多个客户端访问 IO多路复用 回显服务器 TCP相关API mamba Vmamba QQ bot Docker Reactor C++ ceph 软考 流式接口 mysql离线安装 ubuntu22.04 mysql8.0 压测 ECS 源码 搜索引擎 glibc rocketmq 混合开发 JDK 宕机切换 服务器宕机 OpenManus visual studio code dify 深度求索 私域 知识库 压力测试 网工 hive Hive环境搭建 hive3环境 Hive远程模式 AP配网 AK配网 小程序AP配网和AK配网教程 WIFI设备配网小程序UDP开 frp CLion DOIT 四博智联 centos-root /dev/mapper yum clean all df -h / du -sh 数据库架构 数据管理 数据治理 数据编织 数据虚拟化 图像处理 mariadb 京东云 idm 基础入门 编程 匿名管道 windows日志 python3.11 报错 自动化测试 性能测试 功能测试 flash-attention curl wget netty 设置代理 实用教程 IIS .net core Hosting Bundle .NET Framework vs2022 XFS xfs文件系统损坏 I_O error es jvm JAVA 磁盘监控 昇腾 npu linux 命令 sed 命令 相差8小时 UTC 时间 状态管理的 UDP 服务器 Arduino RTOS openEuler 自动化任务管理 gitea 迁移指南 cnn 邮件APP 免费软件 virtualenv file server http server web server 集成学习 集成测试 c ruoyi Invalid Host allowedHosts rdp 实验 yum源切换 更换国内yum源 王者荣耀 Wi-Fi DNS minicom 串口调试工具 计算机 ui ecm bpm 云电竞 云电脑 todesk deepseek r1 vr SysBench 基准测试 iftop 网络流量监控 Minecraft make命令 makefile文件 Erlang OTP gen_server 热代码交换 事务语义 docker命令大全 5G 3GPP 卫星通信 MNN Qwen 指令 备份SQL Server数据库 数据库备份 傲梅企业备份网络版 mq 音乐服务器 Navidrome 音流 系统安全 gaussdb playbook Linux awk awk函数 awk结构 awk内置变量 awk参数 awk脚本 awk详解 在线预览 xlsx xls文件 在浏览器直接打开解析xls表格 前端实现vue3打开excel 文件地址url或接口文档流二进 Dell HPE 联想 浪潮 iDRAC R720xd freebsd ios 监控k8s 监控kubernetes ocr 远程服务 思科模拟器 思科 Cisco 测试用例 文件系统 路径解析 Docker Hub docker pull 镜像源 daemon.json kind AI写作 AI作画 next.js 部署next.js 聊天室 大模型面经 大模型学习 矩阵 CPU 主板 电源 网卡 WebUI DeepSeek V3 剧本 muduo MacOS录屏软件 个人博客 X11 Xming 弹性计算 虚拟化 计算虚拟化 弹性裸金属 RAGFLOW RAG 检索增强生成 文档解析 大模型垂直应用 log4j minio grafana 微信开放平台 微信公众号配置 IPMI 医疗APP开发 app开发 硬件 设备 PCI-Express vscode 1.86 网站搭建 serv00 jetty undertow Linux无人智慧超市 LInux多线程服务器 QT项目 LInux项目 单片机项目 USB网络共享 SSH Playwright 云服务器 裸金属服务器 弹性裸金属服务器 p2p ip 无人机 银河麒麟 kylin v10 麒麟 v10 linux上传下载 hugo EMUI 回退 降级 升级 protobuf 序列化和反序列化 安装 Netty 即时通信 NIO SWAT 配置文件 服务管理 网络共享 k8s集群资源管理 云原生开发 webstorm 银河麒麟桌面操作系统 Kylin OS DeepSeek行业应用 Heroku 网站部署 vmware 卡死 kali 共享文件夹 SSH 服务 SSH Server OpenSSH Server 自动化编程 游戏机 nuxt3 强制清理 强制删除 mac废纸篓 边缘计算 ros2 moveit 机器人运动 Ubuntu 24 常用命令 Ubuntu 24 Ubuntu vi 异常处理 半虚拟化 硬件虚拟化 Hypervisor AI代码编辑器 模拟退火算法 单元测试 田俊楠 code-server xml 灵办AI 算力 显示过滤器 ICMP Wireshark安装 wsl Ark-TS语言 npm pgpool openssl 业界资讯 cmos rime DBeaver 数据仓库 kerberos 数据库系统 图形化界面 大模型入门 大模型教程 tensorflow remote-ssh trae 直流充电桩 充电桩 MQTT协议 消息服务器 代码 GCC crosstool-ng ukui 麒麟kylinos openeuler dba W5500 OLED u8g2 TCP服务器 chfs ubuntu 16.04 统信 虚拟机安装 同步 备份 建站 wsl2 VMware安装mocOS macOS系统安装 火绒安全 VPS Nuxt.js Xterminal 监控k8s集群 集群内prometheus gcc 多层架构 解耦 网络穿透 系统开发 binder 车载系统 framework 源码环境 dns 分析解读 uniapp 英语 致远OA OA服务器 服务器磁盘扩容 OD机试真题 华为OD机试真题 服务器能耗统计 数据集 figma 本地部署AI大模型 强化学习 prompt 单一职责原则 信号 上传视频文件到服务器 uniApp本地上传视频并预览 uniapp移动端h5网页 uniapp微信小程序上传视频 uniapp app端视频上传 uniapp uview组件库 powerpoint spark HistoryServer Spark YARN jobhistory Headless Linux tcp less GoogLeNet CDN OpenSSH 交叉编译 实战案例 序列化反序列化 IPv4 子网掩码 公网IP 私有IP SSH 密钥生成 SSH 公钥 私钥 生成 主从复制 zookeeper flink 人工智能生成内容 nfs 华为机试 DIFY armbian u-boot 程序员创富 epoll GIS 遥感 WebGIS 游戏引擎 cfssl 大大通 第三代半导体 碳化硅 UDP的API使用 ShenTong Windows ai工具 拓扑图 CVE-2024-7347 QT 5.12.12 QT开发环境 Ubuntu18.04 双系统 GRUB引导 Linux技巧 网络结构图 信创 信创终端 中科方德 nlp h.264 RAGFlow 本地知识库部署 DeepSeek R1 模型 HarmonyOS 7z 虚拟现实 P2P HDLC 项目部署到linux服务器 项目部署过程 uv MI300x sonoma 自动更新 wordpress 无法访问wordpess后台 打开网站页面错乱 linux宝塔面板 wordpress更换服务器 web3 xshell termius iterm2 vscode1.86 1.86版本 ssh远程连接 SSE neo4j 数据库开发 database LLM Web APP Streamlit big data cpp-httplib 软负载 AI-native Docker Desktop xcode 服务网格 istio sysctl.conf vm.nr_hugepages rclone AList webdav fnOS c/c++ 串口 视觉检测 视频编解码 读写锁 办公自动化 自动化生成 pdf教程 rnn Linux的基础指令 asp.net大文件上传下载 云服务 odoo 服务器动作 Server action 僵尸世界大战 游戏服务器搭建 arcgis seatunnel 高效日志打印 串口通信日志 服务器日志 系统状态监控日志 异常记录日志 g++ g++13 历史版本 下载 语法 etl 图形渲染 v10 软件 ldap 音乐库 飞牛 Ubuntu共享文件夹 共享目录 Linux共享文件夹 运维监控 k8s二次开发 集群管理 捆绑 链接 谷歌浏览器 youtube google gmail seleium swoole FTP服务器 WSL2 上安装 Ubuntu 架构与原理 vpn yaml Ultralytics 可视化 推荐算法 代理 Linux环境 alias unalias 别名 lio-sam SLAM DeepSeek r1 Open WebUI cd 目录切换 fd 文件描述符 regedit 开机启动 IDEA 宝塔 用户缓冲区 模拟实现 ArkTs ArkUI tailscale derp derper 中转 triton 模型分析 支付 微信支付 开放平台 大文件分片上传断点续传及进度条 如何批量上传超大文件并显示进度 axios大文件切片上传详细教 node服务器合并切片 vue3大文件上传报错提示错误 大文件秒传跨域报错cors vue-i18n 国际化多语言 vue2中英文切换详细教程 如何动态加载i18n语言包 把语言json放到服务器调用 前端调用api获取语言配置文件 miniapp 真机调试 调试 debug 断点 网络API请求调试方法 HiCar CarLife+ CarPlay QT RK3588 apt 国内源 Node-Red 编程工具 流编程 webgl SenseVoice Unity Dedicated Server Host Client 无头主机 aarch64 编译安装 HPC Deepseek-R1 私有化部署 推理模型 eclipse 考研 Mac内存不够用怎么办 NLP模型 自学笔记 小米 澎湃OS Android lua x64 SIGSEGV xmm0 cocoapods MCP server C/S 常用命令 文本命令 目录命令 私有化 版本 玩机技巧 软件分享 软件图标 域名服务 DHCP 符号链接 配置 Kali 渗透 bat 端口 查看 ss sentinel 服务器无法访问 ip地址无法访问 无法访问宝塔面板 宝塔面板打不开 langchain deep learning midjourney banner 前后端分离 移动魔百盒 USB转串口 CH340 Ubuntu22.04 开发人员主页 easyui trea idea FunASR ASR 佛山戴尔服务器维修 佛山三水服务器维修 实习 相机 权限 我的世界服务器搭建 ubuntu24.04.1 干货分享 黑客工具 密码爆破 IO模型 Spring Security 技术共享 我的世界 我的世界联机 数码 飞牛nas fnos 服务器时间 C++软件实战问题排查经验分享 0xfeeefeee 0xcdcdcdcd 动态库加载失败 程序启动失败 程序运行权限 标准用户权限与管理员权限 ISO镜像作为本地源 金仓数据库 2025 征文 数据库平替用金仓 执法记录仪 智能安全帽 smarteye 键盘 线性代数 电商平台 免费域名 域名解析 代理服务器 UOS1070e 代码托管服务 iphone cursor AD 域管理 李心怡 dns是什么 如何设置电脑dns dns应该如何设置 openvpn server openvpn配置教程 centos安装openvpn 镜像 IMX317 MIPI H265 VCU H3C Linux的权限 怎么卸载MySQL MySQL怎么卸载干净 MySQL卸载重新安装教程 MySQL5.7卸载 Linux卸载MySQL8.0 如何卸载MySQL教程 MySQL卸载与安装 小智AI服务端 xiaozhi TTS pppoe radius docker部署Python AI agent ROS 自动驾驶 上传视频至服务器代码 vue3批量上传多个视频并预览 如何实现将本地视频上传到网页 element plu视频上传 ant design vue vue3本地上传视频及预览移除 Claude uni-file-picker 拍摄从相册选择 uni.uploadFile H5上传图片 微信小程序上传图片 像素流送api 像素流送UE4 像素流送卡顿 像素流送并发支持 AnythingLLM AnythingLLM安装 信号处理 互信 稳定性 看门狗 Typore 单例模式 CrewAI edge浏览器 qemu libvirt VS Code WebVM 基础环境 策略模式 流水线 脚本式流水线 串口驱动 CH341 uart 485 DenseNet 游戏开发 安防软件 端口测试 显示管理器 lightdm gdm bcompare Beyond Compare 模拟器 教程 阻塞队列 生产者消费者模型 服务器崩坏原因 can 线程池 增强现实 沉浸式体验 应用场景 技术实现 案例分析 AR Xinference ubuntu24 vivado24 网络药理学 生信 gromacs 分子动力学模拟 MD 动力学模拟 磁盘镜像 服务器镜像 服务器实时复制 实时文件备份 DocFlow 浏览器开发 AI浏览器 ssh漏洞 ssh9.9p2 CVE-2025-23419 Jellyfin HTTP 服务器控制 ESP32 DeepSeek 备选 网站 调用 示例 云桌面 微软 AD域控 证书服务器 xss TrueLicense 毕昇JDK 嵌入式Linux IPC minecraft lsb_release /etc/issue /proc/version uname -r 查看ubuntu版本 中兴光猫 换光猫 网络桥接 自己换光猫 超融合 vasp安装 查询数据库服务IP地址 SQL Server 分布式训练 语音识别 AutoDL rustdesk EtherNet/IP串口网关 EIP转RS485 EIP转Modbus EtherNet/IP网关协议 EIP转RS485网关 EIP串口服务器 国标28181 视频监控 监控接入 语音广播 流程 SIP SDP dity make Radius qt项目 qt项目实战 qt教程 虚拟显示器 searxng PPI String Cytoscape CytoHubba Docker引擎已经停止 Docker无法使用 WSL进度一直是0 镜像加速地址 银河麒麟高级服务器 外接硬盘 Kylin perf 根服务器 clickhouse 社交电子 软件构建 换源 Debian EMQX 通信协议 反向代理 产测工具框架 IMX6ULL 管理框架 junit ros firewall openstack Xen 重启 排查 系统重启 日志 原因 composer 做raid 装系统 Java Applet URL操作 服务器建立 Socket编程 网络文件读取 laravel IM即时通讯 剪切板对通 HTML FORMAT 内网服务器 内网代理 内网通信 VM搭建win2012 win2012应急响应靶机搭建 攻击者获取服务器权限 上传wakaung病毒 应急响应并溯源 挖矿病毒处置 应急响应综合性靶场 saltstack 需求分析 规格说明书 Logstash 日志采集 影刀 #影刀RPA# deekseek 飞书 阿里云ECS autodl 聚类 AD域 程序 性能分析 ubuntu20.04 ros1 Noetic 20.04 apt 安装 云耀服务器 top Linux top top命令详解 top命令重点 top常用参数 proxy模式 react native fast wpf onlyoffice 在线office 磁盘清理 wsgiref Web 服务器网关接口 CentOS Stream CentOS 容器技术 ardunio BLE docker搭建pg docker搭建pgsql pg授权 postgresql使用 postgresql搭建 iperf3 带宽测试 db java-rocketmq 对比 工具 meld DiffMerge 项目部署 IMM KylinV10 麒麟操作系统 Vmware deployment daemonset statefulset cronjob 合成模型 扩散模型 图像生成 AI Agent 字节智能运维 软件卸载 系统清理 欧标 OCPP MDK 嵌入式开发工具 论文笔记 sublime text OpenHarmony 知识图谱 鸿蒙开发 移动开发 Linux权限 权限命令 特殊权限 sequoiaDB AI员工 端口聚合 windows11 prometheus数据采集 prometheus数据模型 prometheus特点 内网环境 Linux find grep 钉钉 PX4 MacOS 软链接 硬链接 System V共享内存 进程通信 网卡的名称修改 eth0 ens33 进程优先级 调度队列 进程切换 docker desktop image 本地化部署 防火墙 NAT转发 NAT Server Qwen2.5-VL vllm 网络建设与运维 jina chrome 浏览器下载 chrome 下载安装 谷歌浏览器下载 树莓派 VNC nosql xpath定位元素 MySql css3 kernel docker部署翻译组件 docker部署deepl docker搭建deepl java对接deepl 翻译组件使用 word win服务器架设 windows server harmonyOS面试题 rpa su sudo 加解密 Yakit yaklang UDP 流量运营 抓包工具 带外管理 状态模式 嵌入式系统开发 区块链 粘包问题 perl llama.cpp 极限编程 安装MySQL Unity插件 visual studio iventoy VmWare OpenEuler Python基础 Python教程 Python技巧 navicat ranger MySQL8.0 大模型部署 zip unzip Attention 环境配置 查看显卡进程 fuser ArtTS 网络爬虫 rtsp服务器 rtsp server android rtsp服务 安卓rtsp服务器 移动端rtsp服务 大牛直播SDK nvm whistle SEO hexo 多路转接 grub 版本升级 扩容 问题解决 kotlin ssh远程登录 虚幻引擎 virtualbox 大模型推理 健康医疗 互联网医院 gnu Sealos 论文阅读 centos 7 企业网络规划 华为eNSP 浏览器自动化 WLAN 多端开发 智慧分发 应用生态 鸿蒙OS rancher 烟花代码 烟花 元旦 性能调优 安全代理 SVN Server tortoise svn 网络搭建 神州数码 神州数码云平台 云平台 hosts 智能音箱 智能家居 ABAP 物联网开发 元服务 应用上架 ip协议 vu大文件秒传跨域报错cors HAProxy 存储维护 NetApp存储 EMC存储 沙盒 TCP协议 抗锯齿 nftables milvus 计算生物学 生物信息 基因组 HarmonyOS NEXT 原生鸿蒙 开发 MVS 海康威视相机 fstab React Next.js 开源框架 风扇控制软件 dock 加速 搜狗输入法 中文输入法 数字证书 签署证书 热榜 yolov5 话题通信 服务通信 智能电视 js WebServer 搭建个人相关服务器 服务器正确解析请求体 国产数据库 瀚高数据库 数据迁移 下载安装 MAC SecureCRT UEFI Legacy MBR GPT U盘安装操作系统 达梦 DM8 IO 视频平台 录像 视频转发 视频流 接口优化 MAVROS 四旋翼无人机 MobaXterm 解决方案 通信工程 毕业 conda配置 conda镜像源 vnc Reactor反应堆 yum换源 开机黑屏 电视剧收视率分析与可视化平台 mcp服务器 client close 离线部署dify macOS mm-wiki搭建 linux搭建mm-wiki mm-wiki搭建与使用 mm-wiki使用 mm-wiki详解 copilot 西门子PLC 通讯 机柜 1U 2U ubuntu 18.04