从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 社区希望能够将…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...