从0到1开发go-tcp框架【1-搭建server、封装连接与业务绑定、实现基础Router、抽取全局配置文件】

news2024/9/29 19:18:17

从0到1开发go-tcp框架【1-搭建server、封装连接与业务绑定、实现基础Router】

本期主要完成对Server的搭建、封装连接与业务绑定、实现基础Router(处理业务的部分)、抽取框架的全局配置文件

  • 从配置文件中读取数据(服务器监听端口、监听IP等),通过自定义Router完成具体业务操作

第一版最终项目结构:
在这里插入图片描述

1 搭建基础server[V1.0]

1.1 编写server端

  • 编写iserver.go,用于定义server的接口
  • 编写server.go,定义server结构体,并实现接口

①/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端功能

①创建Server.go和Client.go

  1. 编写myDemo/zinxV1.0/Server.go
package main

import "myTest/zinx/znet"

func main() {
	s := znet.NewServer("[Zinx v1.0]")
	s.Serve()
}
  1. 编写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 封装连接conn、业务绑定[V2.0]

V0.1版本我们已经实现了了⼀一个基础的Server框架,现在我们需要对客户端链接和不不同的客户端链接所处 理理的不不同业务再做⼀一层接⼝口封装,当然我们先是把架构搭建起来。
现在在 ziface 下创建⼀一个属于链接的接⼝口⽂文件 iconnection.go ,当然他的实现⽂文件我们放在 znet 下的 connection.go 中。

需要的方法:

  1. 启动连接
  2. 停止连接
  3. 得到连接的conn对象
  4. 得到连接的id
  5. 得到客户端连接的地址和端口
  6. 发送数据的方法
  7. 连接所绑定的处理业务的函数

2.1 封装Conn

  • 定义iconnection接口
  • 创建connection结构体并实现iconnection
  1. 创建/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
  1. 创建/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的处理业务逻辑部分更换为封装后的Conn来调用
在这里插入图片描述
全部代码:
/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 测试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 实现基础Router[V3.0]

3.1 Request请求封装

将连接和数据绑定在一起

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 {
}

//这里做了空实现,直接让后续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 框架集成router模块

  • 取消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
}

测试框架集成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部分

  • server:
    在这里插入图片描述
  • connection:在这里插入图片描述

4.3 测试

编写myDemo/ZinxV4.0

  • 并且编写对应的.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
}

最后效果:
在这里插入图片描述

参考:https://www.yuque.com/aceld/npyr8s/bgftov

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/808120.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《TCP IP网络编程》第十二章

第 12 章 I/O 复用 12.1 基于 I/O 复用的服务器端 多进程服务端的缺点和解决方法: 为了构建并发服务器,只要有客户端连接请求就会创建新进程。这的确是实际操作中采用的一种方案,但并非十全十美,因为创建进程要付出很大的代价。…

CK_03靶机详解

CK_03靶机详解 靶场下载地址:https://download.vulnhub.com/ck/MyFileServer_3.zip 这个靶机开放的端口特别多,所以给我们的误导也很多,我直接按照正确的思路来。 因为开着445所以就枚举了一下靶机上共享的东西,发现两个share的…

MTK联发科安卓核心板MT8385(Genio 500)规格参数资料_性能介绍

简介 MT8385安卓核心板 是一个高度集成且功能强大的物联网平台,具有以下主要特性: l 四核 Arm Cortex-A73 处理器 l 四核Arm Cortex-A53处理器 l Arm Mali™-G72 MP3 3D 图形加速器 (GPU),带有 Vulkan 1.0、OpenGL ES 3.2 和 OpenCL™ 2.x …

【SpringCloud Alibaba】(六)使用 Sentinel 实现服务限流与容错

今天,我们就使用 Sentinel 实现接口的限流,并使用 Feign 整合 Sentinel 实现服务容错的功能,让我们体验下微服务使用了服务容错功能的效果。 因为内容仅仅围绕着 SpringCloud Alibaba技术栈展开,所以,这里我们使用的服…

.sql文件导入MySQL

命令行导入 source E:\data.sql图形化界面导入 选择.sql文件路径开始。 推荐使用命令行导入!!!

matplotlib——3. 绘制分布(scatter+hist)

文章目录 1. matplotlib实现1.1 效果1.2 代码 2. seaborn实现2.1 效果2.2 代码 左图是matplotlib的结果,右图是seaborn的结果 1. matplotlib实现 1.1 效果 效果:(二维正态分布的散点图每个轴的直方图) 1.2 代码 import nump…

关于Anaconda环境配置的一些问题

文章目录 一、关于package文件安装位置二、关于尝试下载Python包时出现的CondaSSLError三、配置环境的整个流程四、如何在Jupyter中打开任意位置的文件夹五、如何在Jupyter对应的环境中安装包 一、关于package文件安装位置 package 文件安装在envs目录底下的Lib中,可…

Llama2跟进:GPU硬件要求、微调注意事项等【202307】

在过去几天里关注Llama 2 的所有新闻已经超出了全职工作的范围。 信息网络确实充满了拍摄、实验和更新。 至少还要再过一周,但已经有一些关键点了。 推荐:用 NSDT设计器 快速搭建可编程3D场景。 在这篇文章中,我将澄清我对原始帖子中有关 Lla…

MySQL 相关知识

MySQL 相关知识 1、三大范式2、DML 语句和 DDL 语句区别3、主键和外键的区别4、drop、delete、truncate 区别5、基础架构6、MyISAM 和 InnoDB 有什么区别?7、推荐自增id作为主键问题8、为什么 MySQL 的自增主键不连续9、redo log 是做什么的?10、redo log 的刷盘时…

WIZnet W5500-EVB-Pico 静态IP配置教程(二)

W5500是一款高性价比的 以太网芯片,其全球独一无二的全硬件TCP、IP协议栈专利技术,解决了嵌入式以太网的接入问题,简单易用,安全稳定,是物联网设备的首选解决方案。WIZnet提供完善的配套资料以及实时周到的技术支持服务…

Windows Server 2012 能使用的playwright版本

由于在harkua_bot里面使用到了playwright,我的服务器又是Windows Server 2012 R2,最新版playwright不支持Windows Server 2012 R2,支持Windows Server 2016以上,所以有了这个需求 https://cdn.npmmirror.com/binaries/playwright…

手写线程池 - C++版 - 笔记总结

1.线程池原理 创建一个线程,实现很方便。 缺点:若并发的线程数量很多,并且每个线程都是执行一个时间较短的任务就结束了。 由于频繁的创建线程和销毁线程需要时间,这样的频繁创建线程会大大降低 系统的效率。 2.思考 …

【Python从入门到进阶】30、JSONPath的介绍和使用

接上篇《29、xpath抓取站长素材图片》 上一篇我们讲解了如何利用xpath来抓取站长素材网站的图片区首页的所有图片,本篇我们来介绍JSONPath的基础和具体使用。 一、JSONPath的基本概念 1、什么是JSONPath? JSONPath是一种用于在JSON(JavaSc…

致敬图灵!HashData拥抱数据智能新时代!

图1:2023ACM中国图灵大会现场 生于1912年的艾伦图灵被称为“计算机科学之父”、“人工智能之父”。1966年,国际计算机协会(ACM)为了纪念这位卓越的科学家,设立了以其名字命名的ACM图灵奖,以表彰在计算机领…

RH850 1372/1374 程序跑飞异常分析

文章目录 前言现象描述原因分析解决方案总结 前言 最近项目用瑞萨RH850系列的1372/1374开发,官方的MCAL做的不咋地就算了,FAE支持也很少。给的demo问题也很多。本文记录一下开发过程中的问题。 现象描述 MCAL配置完ADC1后,运行ADC1的采样程…

SQL注入原理分析

前言 order by的作用及含义 order by 用于判断显示位,order by 原有的作用是对字段进行一个排序,在sql注入中用order by 来判断排序,order by 1就是对一个字段进行排序,如果一共四个字段,你order by 5 数据库不知道怎么…

51:电机(ULN2003D)

1:介绍 我们51单片机使用的是直流电机 直流电机是一种将电能转换为机械能的装置。一般的直流电机有两个电极,当电极正接时,电机正转,当电极反接时,电机反转 直流电机主要由永磁体(定子)、线圈(转…

【Java|基础篇】File类和IO流

文章目录 1.File类2.流的概念3.InputStream4.OutputStream5.Reader6.Writer7.使用Scanner读文件8.使用PrintWriter写文件9.close()方法10.flush()方法10.总结 1.File类 File类是Java中用于表示文件或目录的类。它提供了一些方法来操作文件和目录的属性和内容,可以进…

对js中的window深入理解

window和 document的区别 window对象是浏览器中的全局对象,代表的是整个浏览器窗口;document只是window对象中的一部分,表示当前窗口或框架中加载的HTML文档,主要用于访问和操作文档的内容,包括DOM元素、样式、事件等&…

华为OD机试真题 Java 实现【AI面板识别】【2023 B卷 100分】,附详细解题思路

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明4、控制台输出 华为OD机试 2023B卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA)真题&#xff08…