从0到1开发go-tcp框架【1-搭建server、封装连接与业务绑定、实现基础Router、抽取全局配置文件】
从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 zifacetype IServer interface {Start()Stop()Serve()
}
②/zinx/znet/server.go
package znetimport ("fmt""net"
)type Server struct {Name stringIPVersion stringIP stringPort 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
- 编写myDemo/zinxV1.0/Server.go
package mainimport "myTest/zinx/znet"func main() {s := znet.NewServer("[Zinx v1.0]")s.Serve()
}
- 编写myDemo/zinxV1.0/Client.go
package mainimport ("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 中。
需要的方法:
- 启动连接
- 停止连接
- 得到连接的conn对象
- 得到连接的id
- 得到客户端连接的地址和端口
- 发送数据的方法
- 连接所绑定的处理业务的函数
2.1 封装Conn
- 定义iconnection接口
- 创建connection结构体并实现iconnection
- 创建/zinx/ziface/iconnection.go:
package zifaceimport "net"type IConnection interface {//启动连接Start()//停止连接Stop()//获取当前连接的Conn对象GetTCPConnection() *net.TCPConn//获取当前连接模块的idGetConnectionID() uint32//获取远程客户端的TCP状态 IP:PortRemoteAddr() net.Addr//发送数据Send()
}//定义一个处理连接业务的方法
type HandleFunc func(*net.TCPConn, []byte, int) error
- 创建/zinx/znet/connection.go
package znetimport ("fmt""myTest/zinx/ziface""net"
)type Connection struct {Conn *net.TCPConnConnID uint32isClosed boolhandleAPI 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}//调用当前所绑定的处理业务的方法HandleAPIif 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 = truec.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 znetimport ("fmt""github.com/kataras/iris/v12/x/errors""net"
)type Server struct {Name stringIPVersion stringIP stringPort 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 = 0dealConn := NewConnection(conn, cid, CallBackToClient)cid++//开启goroutine处理启动当前conngo 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 mainimport ("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 mainimport "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 zifaceimport "net"type IRequest interface {GetConnection() *net.TCPConnGetData() []byte
}
zinx/znet/request.go:
package znetimport "net"type Request struct {conn *net.TCPConndata []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 zifacetype IRouter interface {//处理请求之前的方法PreHandle(request IRequest)Handler(request IRequest)//处理请求之后的方法PostHandler(request IRequest)
}
zinx/znet/router.go
package znetimport "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 znetimport ("fmt""myTest/zinx/ziface""net"
)type Connection struct {Conn *net.TCPConnConnID uint32isClosed bool//告知当前的连接已经退出ExitChan chan boolRouter 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 = truec.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 znetimport ("fmt""myTest/zinx/ziface""net"
)type Server struct {Name stringIPVersion stringIP stringPort intRouter 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 = 0dealConn := NewConnection(conn, cid, s.Router)cid++//开启goroutine处理启动当前conngo 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 mainimport ("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 mainimport ("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 utilimport ("encoding/json""io/ioutil""myTest/zinx/ziface"
)type GlobalObj struct {TCPServer ziface.IServer //当前全局Zinx的server对象Host string //当前服务器主机监听的ipTcpPort 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
相关文章:
从0到1开发go-tcp框架【1-搭建server、封装连接与业务绑定、实现基础Router、抽取全局配置文件】
从0到1开发go-tcp框架【1-搭建server、封装连接与业务绑定、实现基础Router】 本期主要完成对Server的搭建、封装连接与业务绑定、实现基础Router(处理业务的部分)、抽取框架的全局配置文件 从配置文件中读取数据(服务器监听端口、监听IP等&a…...
建设银行秋招指南,备考技巧和考试内容详解
建设银行秋招简介 银行作为非常吃香的岗位,每年都有不少同学通过投递简历,进入笔试,再到面试成功,成功到银行就职,也有相当一部分同学因为信息差,符合条件却没有报名。无法进入银行工作。 建设银行的秋招…...
Cilium 系列-7-Cilium 的 NodePort 实现从 SNAT 改为 DSR
系列文章 Cilium 系列文章 前言 将 Kubernetes 的 CNI 从其他组件切换为 Cilium, 已经可以有效地提升网络的性能。但是通过对 Cilium 不同模式的切换/功能的启用,可以进一步提升 Cilium 的网络性能。具体调优项包括不限于: 启用本地路由 (Native Rou…...
React的hooks---useReducer
useReducer 作为 useState 的代替方案,在某些场景下使用更加适合,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。 使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为父组件可以向自…...
自然语言处理从入门到应用——LangChain:模型(Models)-[文本嵌入模型Ⅱ]
分类目录:《自然语言处理从入门到应用》总目录 本文将介绍如何在LangChain中使用Embedding类。Embedding类是一种与嵌入交互的类。有很多嵌入提供商,如:OpenAI、Cohere、Hugging Face等,这个类旨在为所有这些提供一个标准接口。 …...
Olap BI工具对比
背景 目前公司主要使用数据存储有MySQL、ES、Hive、HBase、TiDB等 MySQL用于存储应用的基本支撑数据,数据量少;ES和Hbase用于存储和查询调用记录,数据量多;Hive和TiDB用于DC上使用,数据量多。主要使用的数据分析平台…...
【iOS】Cocoapods的安装以及使用
文章目录 前言一、Cocoapods的作用二、安装Cocoapods三、使用Cocoapods总结 前言 最近笔者在仿写天气预报App时用到了api调用数据,一般的基本数据类型我们用Xcode中自带的框架就可以转换得到。但是在和风天气api中的图标的格式为svg格式。 似乎iOS13之后Xcode中可…...
OpenCvSharp (C# OpenCV) 二维码畸变矫正--基于透视变换(附源码)
导读 本文主要介绍如何使用OpenCvSharp中的透视变换来实现二维码的畸变矫正。 由于CSDN文章中贴二维码会导致显示失败,大家可以直接点下面链接查看图片: C# OpenCV实现二维码畸变矫正--基于透视变换 (详细步骤 + 代码) 实现步骤 讲解实现步骤之前先看下效果(左边是原图,右边…...
下级平台级联视频汇聚融合平台EasyCVR,层级显示不正确的原因排查
视频汇聚平台安防监控EasyCVR可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有GB28181、RTSP/Onvif、RTMP等,以及厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等,能对外分发RTSP、RTMP、FLV、HLS、WebRTC等…...
Android程序CPU使用大的异常分析
程序出现CPU使用过高的问题,如果能够重现,就比较好办了,可以top命令查看各线程的cpu使用,定位到线程。 以下是问国内某AI的答案 在Android应用中,如果某个应用消耗了大量的CPU资源,可以采取以下方法进行分…...
[数学建模] 0、关于数学建模的一点看法付费专栏食用说明
文章目录 1、前言2、数学建模学习索引2.1、建模知识点 3、实战建模论文索引3.1、国赛真题索引3.1.1、[数学建模] [2001年国赛模拟] 1. 血管的三维重建3.1.2、[数学建模] [2011年B国赛模拟] 2. 交巡警服务平台的设置与调度3.1.3、[数学建模][2012年A国赛模拟] 3. 葡萄酒的评价 3…...
2.oracle数据库自增主键
不同于mysql,oracle主键自增不能在建表时直接设置,其实也很简单 1.建表 CREATE TABLE test(id NUMBER NOT NULL,key1 VARCHAR2(40) NULL,key2 VARCHAR2(40) NULL);2.设置主键 alter table test add constraint test_pk primary key (id);3.新建序列tes…...
算法通关村第二关——链表加法的问题解析
题目类型 链表反转、栈 题目描述 * 题目: * 给你两个非空链表来表示两个非负整数,数字最高位位于链表的开始位置。 * 它们的每个节点都只存储一个数字。将这两个数相加会返回一个新的链表。 * 你可以假设除了数字0外,这两个数字都不会以0开头…...
mapboxGL中楼层与室内地图的结合展示
概述 质量不够,数量来凑,没错,本文就是来凑数的。前面的几篇文章实现了楼栋与楼层单体化的展示、室内地图的展示,本文结合前面的几篇文章,做一个综合的展示效果。 实现效果 实现 1. 数据处理 要实现上图所示的效果…...
使用Anaconda3创建pytorch虚拟环境
一、Conda配置Pytorch环境 1.conda安装Pytorch环境 打开Anaconda Prompt,输入命令行: conda create -n pytorch python3.6 输入y,再回车。 稍等,便完成了Pytorch的环境安装。我们可以利用以下命令激活pytorch环境。 conda…...
QT 常用数据结构整理
目录 QString篇 QString篇 //初始化bool bOk false;QString str "sd";QString strTemp(str);str QString("%1,%2").arg("11").arg("-gg");qDebug()<<str;str.sprintf("%s %d","ni",1);qDebug()<<…...
Fiddler使用教程|渗透测试工具使用方法Fiddler
提示:如有问题可联系我,24小时在线 文章目录 前言一、Fiddler界面介绍二、菜单栏1.菜单Fiddler工具栏介绍Fiddler命令行工具详解 前言 网络渗透测试工具: Fiddler是目前最常用的http抓包工具之一。 Fiddler是功能非常强大,是web…...
网站密码忘记了怎么办?chrome浏览器,谷歌浏览器。
有时候忘记了网站的密码,又不想“忘记密码”去一番折腾。如果你正好用的是 chrome 浏览器。 那么根本就没必要折腾,直接就能看到网站密码。 操作如下 1.在浏览器右上角点击三个小点: 2.点这三个点: 3.选择“显示密码”&#x…...
23款奔驰GLS450加装原厂香氛负离子系统,清香宜人,久闻不腻
奔驰原厂香氛合理性可通过车内空气调节组件营造芳香四溢的怡人氛围。通过更换手套箱内香氛喷雾发生器所用的香水瓶,可轻松选择其他香氛。香氛的浓度和持续时间可调。淡雅的香氛缓缓喷出,并且在关闭后能够立刻散去。车内气味不会永久改变,香氛…...
流数据湖平台Apache Paimon(一)概述
文章目录 第1章 概述1.1 简介1.2 核心特性1.3 基本概念1.3.1 Snapshot1.3.2 Partition1.3.3 Bucket1.3.4 Consistency Guarantees一致性保证 1.4 文件布局1.4.1 Snapshot Files1.4.2 Manifest Files1.4.3 Data Files1.4.4 LSM Trees 第1章 概述 1.1 简介 Flink 社区希望能够将…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...






