以太坊网络架构解析

2023-11-11

                  以太坊网络架构解析

版权

0x7F@知道创宇404区块链安全研究团队

https://www.cnblogs.com/southx/p/9334639.html

 

0x00 前言

区块链的火热程度一直以直线上升,其中以区块链 2.0 —— 以太坊为代表,不断的为传统行业带来革新,同时也推动区块链技术发展。

区块链是一种分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式,这是一个典型的去中心化应用,建立在 p2p 网络之上;本文以学习和分析以太坊运作原理为目的,将以太坊网络架构作为一个切入点,逐步深入分析,最终对以太坊网络架构有个大致的了解。

通过学习以太坊网络架构,可以更容易的对网络部分的源码进行审计,便于后续的协议分析,来发现未知的安全隐患;除此之外,目前基于 p2p 网络的成熟的应用非常少,借助分析以太坊网络架构的机会,可以学习一套成熟的 p2p 网络运行架构。

本文侧重于数据链路的建立和交互,不涉及网络模块中的节点发现、区块同步、广播等功能模块。

0x01 目录

  1. Geth 启动
  2. 网络架构
  3. 共享密钥
  4. RLPXFrameRW 帧
  5. RLP 编码
  6. LES 协议
  7. 总结

其中第 3、4、5 三个小节是第 2 节「网络架构」的子内容,作为详细的补充。

0x02 Geth 启动

在介绍以太坊网络架构之前,首先简单分析下 Geth 的整体启动流程,便于后续的理解和分析。

以太坊源码目录

  1.  

    tree -d -L 1

  2.  

    .

  3.  

    ├── accounts 账号相关

  4.  

    ├── bmt 实现二叉merkle树

  5.  

    ├── build 编译生成的程序

  6.  

    ├── cmd geth程序主体

  7.  

    ├── common 工具函数库

  8.  

    ├── consensus 共识算法

  9.  

    ├── console 交互式命令

  10.  

    ├── containers docker 支持相关

  11.  

    ├── contracts 合约相关

  12.  

    ├── core 以太坊核心部分

  13.  

    ├── crypto 加密函数库

  14.  

    ├── dashboard 统计

  15.  

    ├── eth 以太坊协议

  16.  

    ├── ethclient 以太坊RPC客户端

  17.  

    ├── ethdb 底层存储

  18.  

    ├── ethstats 统计报告

  19.  

    ├── event 事件处理

  20.  

    ├── internal RPC调用

  21.  

    ├── les 轻量级子协议

  22.  

    ├── light 轻客户端部分功能

  23.  

    ├── log 日志模块

  24.  

    ├── metrics 服务监控相关

  25.  

    ├── miner 挖矿相关

  26.  

    ├── mobile geth的移动端API

  27.  

    ├── node 接口节点

  28.  

    ├── p2p p2p网络协议

  29.  

    ├── params 一些预设参数值

  30.  

    ├── rlp RLP系列化格式

  31.  

    ├── rpc RPC接口

  32.  

    ├── signer 签名相关

  33.  

    ├── swarm 分布式存储

  34.  

    ├── tests 以太坊JSON测试

  35.  

    ├── trie Merkle Patricia实现

  36.  

    ├── vendor 一些扩展库

  37.  

    └── whisper 分布式消息

  38.  

     

  39.  

    35 directories

初始化工作

Geth 的 main() 函数非常的简洁,通过 app.Run() 来启动程序:

  1.  

    [./cmd/geth/main.go]

  2.  

    func main() {

  3.  

    if err := app.Run(os.Args); err != nil {

  4.  

    fmt.Fprintln(os.Stderr, err)

  5.  

    os.Exit(1)

  6.  

    }

  7.  

    }

其简洁是得力于 Geth 使用了 gopkg.in/urfave/cli.v1 扩展包,该扩展包用于管理程序的启动,以及命令行解析,其中 app 是该扩展包的一个实例。

在 Go 语言中,在有 init() 函数的情况下,会默认先调用 init() 函数,然后再调用 main() 函数;Geth 几乎在 ./cmd/geth/main.go#init() 中完成了所有的初始化操作:设置程序的子命令集,设置程序入口函数等,下面看下 init() 函数片段:

  1.  

    [./cmd/geth/main.go]

  2.  

    func init() {

  3.  

    // Initialize the CLI app and start Geth

  4.  

    app.Action = geth

  5.  

    app.HideVersion = true // we have a command to print the version

  6.  

    app.Copyright = "Copyright 2013-2018 The go-ethereum Authors"

  7.  

    app.Commands = []cli.Command{

  8.  

    // See chaincmd.go:

  9.  

    initCommand,

  10.  

    importCommand,

  11.  

    exportCommand,

  12.  

    importPreimagesCommand,

  13.  

    ...

  14.  

    }

  15.  

    ...

  16.  

    }

在以上代码中,预设了 app实例的值,其中 app.Action = geth作为 app.Run()调用的默认函数,而 app.Commands保存了子命令实例,通过匹配命令行参数可以调用不同的函数(而不调用 app.Action),使用 Geth 不同的功能,如:开启带控制台的 Geth、使用 Geth 创造创世块等。

节点启动流程

无论是通过 geth() 函数还是其他的命令行参数启动节点,节点的启动流程大致都是相同的,这里以 geth() 为例:

  1.  

    [./cmd/geth/main.go]

  2.  

    func geth(ctx *cli.Context) error {

  3.  

    node := makeFullNode(ctx)

  4.  

    startNode(ctx, node)

  5.  

    node.Wait()

  6.  

    return nil

  7.  

    }

其中makeFullNode()函数将返回一个节点实例,然后通过startNode()启动。在 Geth 中,每一个功能模块都被视为一个服务,每一个服务的正常运行驱动着 Geth 的各项功能;makeFullNode()通过解析命令行参数,注册指定的服务。以下是 makeFullNode()代码片段:
  1.  

    [./cmd/geth/config.go]

  2.  

    func makeFullNode(ctx *cli.Context) *node.Node {

  3.  

    stack, cfg := makeConfigNode(ctx)

  4.  

     

  5.  

    utils.RegisterEthService(stack, &cfg.Eth)

  6.  

     

  7.  

    if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {

  8.  

    utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)

  9.  

    }

  10.  

     

  11.  

    ...

  12.  

     

  13.  

    // Add the Ethereum Stats daemon if requested.

  14.  

    if cfg.Ethstats.URL != "" {

  15.  

    utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)

  16.  

    }

  17.  

    return stack

  18.  

    }

然后通过 startNode() 启动各项服务并运行节点。以下是 Geth 启动流程图:

每个服务正常运行,相互协作,构成了 Geth:

0x03 网络架构

通过 main() 函数的调用,最终启动了 p2p 网络,这一小节对网络架构做详细的分析。

三层架构
以太坊是去中心化的数字货币系统,天然适用 p2p 通信架构,并且在其上还支持了多种协议。在以太坊中,p2p 作为通信链路,用于负载上层协议的传输,可以将其分为三层结构:

  1. 最上层是以太坊中各个协议的具体实现,如 eth 协议、les 协议。
  2. 第二层是以太坊中的 p2p 通信链路层,主要负责启动监听、处理新加入连接或维护连接,为上层协议提供了信道。
  3. 最下面的一层,是由 Go 语言所提供的网络 IO 层,也就是对 TCP/IP 中的网络层及以下的封装。

p2p 通信链路层
从最下层开始逐步分析,第三层是由 Go 语言所封装的网络 IO 层,这里就跳过了,直接分析 p2p 通信链路层。p2p 通信链路层主要做了三项工作:

  1. 由上层协议的数据交付给 p2p 层后,首先通过 RLP 编码。
  2. RLP 编码后的数据将由共享密钥进行加密,保证通信过程中数据的安全。
  3. 最后,将数据流转换为 RLPXFrameRW 帧,便于数据的加密传输和解析。
    (以上三点由下文做分析)

p2p 源码分析
p2p 同样作为 Geth 中的一项服务,通过「0x03 Geth 启动」中 startNode() 启动,p2p 通过其 Start() 函数启动。以下是 Start() 函数代码片段:

  1.  

    [./p2p/server.go]

  2.  

    func (srv *Server) Start() (err error) {

  3.  

    ...

  4.  

    if !srv.NoDiscovery {

  5.  

    ...

  6.  

    }

  7.  

    if srv.DiscoveryV5 {

  8.  

    ...

  9.  

    }

  10.  

    ...

  11.  

    // listen/dial

  12.  

    if srv.ListenAddr != "" {

  13.  

    if err := srv.startListening(); err != nil {

  14.  

    return err

  15.  

    }

  16.  

    }

  17.  

    ...

  18.  

    go srv.run(dialer)

  19.  

    ...

  20.  

    }

上述代码中,设置了 p2p 服务的基础参数,并根据用户参数开启节点发现(节点发现不在本文的讨论范围内),随后开启 p2p 服务监听,最后开启单独的协程用于处理报文。以下分为服务监听和报文处理两个模块来分析。

服务监听

通过 startListening() 的调用进入到服务监听的流程中,随后在该函数中调用 listenLoop 用一个无限循环处理接受连接,随后通过 SetupConn() 函数为正常的连接建立 p2p 通信链路。在 SetupConn() 中调用 setupConn() 来做具体工作,以下是 setupConn() 的代码片段:

  1.  

    [./p2p/server.go]

  2.  

    func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) error {

  3.  

    ...

  4.  

    if c.id, err = c.doEncHandshake(srv.PrivateKey, dialDest); err != nil {

  5.  

    srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err)

  6.  

    return err

  7.  

    }

  8.  

    ...

  9.  

    phs, err := c.doProtoHandshake(srv.ourHandshake)

  10.  

    ...

  11.  

    }

setupConn() 函数中主要由 doEncHandshake() 函数与客户端交换密钥,并生成临时共享密钥,用于本次通信加密,并创建一个帧处理器 RLPXFrameRW;再调用 doProtoHandshake() 函数为本次通信协商遵循的规则和事务,包含版本号、名称、容量、端口号等信息。在成功建立通信链路,完成协议握手后,处理流程转移到报文处理模块。

下面是服务监听函数调用流程:

报文处理

p2p.Start() 通过调用 run() 函数处理报文,run() 函数用无限循环等待事务,比如上文中,新连接完成握手包后,将由该函数来负责。run() 函数中支持多个命令的处理,包含的命令有服务退出清理、发送握手包、添加新节点、删除节点等。以下是 run() 函数结构:

  1.  

    [./p2p/server.go]

  2.  

    func (srv *Server) run(dialstate dialer) {

  3.  

    ...

  4.  

    for {

  5.  

    select {

  6.  

    case <-srv.quit: ...

  7.  

    case n := <-srv.addstatic: ...

  8.  

    case n := <-srv.removestatic: ...

  9.  

    case op := <-srv.peerOp: ...

  10.  

    case t := <-taskdone: ...

  11.  

    case c := <-srv.posthandshake: ...

  12.  

    case c := <-srv.addpeer: ...

  13.  

    case pd := <-srv.delpeer: ...

  14.  

    }

  15.  

    }

  16.  

    }

为了理清整个网络架构,本文直接讨论 addpeer 分支:当一个新节点添加服务器节点时,将进入到该分支下,根据之前的握手信息,为上层协议生成实例,然后调用 runPeer(),最终通过 p.run() 进入报文的处理流程中。

继续分析 p.run() 函数,其开启了读取数据和 ping 两个协程,用于处理接收报文和维持连接,随后通过调用 startProtocols() 函数,调用指定协议的 Run() 函数,进入具体协议的处理流程。

下面是报文处理函数调用流程

p2p 通信链路交互流程

这里整体看下 p2p 通信链路的处理流程,以及对数据包的封装。

0x04 共享密钥

在 p2p 通信链路的建立过程中,第一步就是协商共享密钥,该小节说明下密钥的生成过程。

迪菲-赫尔曼密钥交换
p2p 网络中使用到的是「迪菲-赫尔曼密钥交换」技术[1]。迪菲-赫尔曼密钥交换(英语:Diffie–Hellman key exchange,缩写为D-H) 是一种安全协议。它可以让双方在完全没有对方任何预先信息的条件下通过不安全信道创建起一个密钥。

简单来说,链接的两方生成随机的私钥,通过随机的私钥得到公钥。然后双方交换各自的公钥,这样双方都可以通过自己随机的私钥和对方的公钥来生成一个同样的共享密钥(shared-secret)。后续的通讯使用这个共享密钥作为对称加密算法的密钥。其中对于 A、B公私钥对满足这样的数学等式:ECDH(A私钥, B公钥) == ECDH(B私钥, A公钥)

共享密钥生成
在 p2p 网络中由 doEncHandshake() 方法完成密钥的交换和共享密钥的生成工作。下面是该函数的代码片段:

  1.  

    [./p2p/rlpx.go]

  2.  

    func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *discover.Node) (discover.NodeID, error) {

  3.  

    ...

  4.  

    if dial == nil {

  5.  

    sec, err = receiverEncHandshake(t.fd, prv, nil)

  6.  

    } else {

  7.  

    sec, err = initiatorEncHandshake(t.fd, prv, dial.ID, nil)

  8.  

    }

  9.  

    ...

  10.  

    t.rw = newRLPXFrameRW(t.fd, sec)

  11.  

    ..

  12.  

    }

如果作为服务端监听连接,收到新连接后调用 receiverEncHandshake() 函数,若作为客户端向服务端发起请求,则调用 initiatorEncHandshake()函数;两个函数区别不大,都将交换密钥,并生成共享密钥,initiatorEncHandshake() 仅仅是作为发起数据的一端;最终执行完后,调用 newRLPXFrameRW() 创建帧处理器。

从服务端的角度来看,将调用 receiverEncHandshake() 函数来创建共享密钥,以下是该函数的代码片段:

  1.  

    [./p2p/rlpx.go]

  2.  

    func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, token []byte) (s secrets, err error) {

  3.  

    authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn)

  4.  

    ...

  5.  

    authRespMsg, err := h.makeAuthResp()

  6.  

    ...

  7.  

    if _, err = conn.Write(authRespPacket); err != nil {

  8.  

    return s, err

  9.  

    }

  10.  

    return h.secrets(authPacket, authRespPacket)

  11.  

    }

共享密钥生成的过程:

  1. 在完成 TCP 连接后,客户端使用服务端的公钥(node_id)加密,发送自己的公钥和包含临时公钥的签名,还有一个随机值 nonce。
  2. 服务端收到数据,获得客户端的公钥,使用椭圆曲线算法从签名中获得客户端的临时公钥;服务端将自己的临时公钥和随机值 nonce 用客户端的公钥加密发送。
  3. 通过上述两步的密钥交换后,对于客户端目前有自己的临时公私钥对和服务端的临时公钥,使用椭圆曲线算法从自己的临时私钥和服务端的临时公钥计算得出共享密钥;同理,服务端按照相同的方式也可以计算出共享密钥。

以下是共享密钥生成图示:

得出共享密钥后,客户端和服务端就可以使用共享密钥做对称加密,完成对通信的加密。

0x05 RLPXFrameRW 帧

在共享密钥生成完毕后,初始化了 RLPXFrameRW 帧处理器;其 RLPXFrameRW 帧的目的是为了在单个连接上支持多路复用协议。其次,由于帧分组的消息为加密数据流产生了天然的分界点,更便于数据的解析,除此之外,还可以对发送的数据进行验证。

RLPXFrameRW 帧包含了两个主要函数,WriteMsg() 用于发送数据,ReadMsg()用于读取数据;以下是 WriteMsg() 的代码片段:

  1.  

    [./p2p/rlpx.go]

  2.  

    func (rw *rlpxFrameRW) WriteMsg(msg Msg) error {

  3.  

    ...

  4.  

    // write header

  5.  

    headbuf := make([]byte, 32)

  6.  

    ...

  7.  

    // write header MAC

  8.  

    copy(headbuf[16:], updateMAC(rw.egressMAC, rw.macCipher, headbuf[:16]))

  9.  

    if _, err := rw.conn.Write(headbuf); err != nil {

  10.  

    return err

  11.  

    }

  12.  

     

  13.  

    // write encrypted frame, updating the egress MAC hash with

  14.  

    // the data written to conn.

  15.  

    tee := cipher.StreamWriter{S: rw.enc, W: io.MultiWriter(rw.conn, rw.egressMAC)}

  16.  

    if _, err := tee.Write(ptype); err != nil {

  17.  

    return err

  18.  

    }

  19.  

    if _, err := io.Copy(tee, msg.Payload); err != nil {

  20.  

    return err

  21.  

    }

  22.  

    if padding := fsize % 16; padding > 0 {

  23.  

    if _, err := tee.Write(zero16[:16-padding]); err != nil {

  24.  

    return err

  25.  

    }

  26.  

    }

  27.  

     

  28.  

    // write frame MAC. egress MAC hash is up to date because

  29.  

    // frame content was written to it as well.

  30.  

    fmacseed := rw.egressMAC.Sum(nil)

  31.  

    mac := updateMAC(rw.egressMAC, rw.macCipher, fmacseed)

  32.  

    _, err := rw.conn.Write(mac)

  33.  

    return err

  34.  

    }

结合以太坊 RLPX 的文档[2]和上述代码,可以分析出 RLPXFrameRW 帧的结构。在一般情况下,发送一次数据将产生五个数据包:

  1.  

    header // 包含数据包大小和数据包源协议

  2.  

    header_mac // 头部消息认证

  3.  

    frame // 具体传输的内容

  4.  

    padding // 使帧按字节对齐

  5.  

    frame_mac // 用于消息认证

接收方按照同样的格式对数据包进行解析和验证。

0x06 RLP 编码

RLP编码 (递归长度前缀编码)提供了一种适用于任意二进制数据数组的编码,RLP 已经成为以太坊中对对象进行序列化的主要编码方式,便于对数据结构的解析。比起 json 数据格式,RLP 编码使用更少的字节。

在以太坊的网络模块中,所有的上层协议的数据包要交互给 p2p 链路时,都要首先通过 RLP 编码;从 p2p 链路读取数据,也要先进行解码才能操作。

以太坊中 RLP 的编码规则[3]。

0x07 LES 协议层

这里以 LES 协议为上层协议的代表,分析在以太坊网络架构中应用协议的工作原理。

LES 服务由 Geth 初始化时启动,调用源码 les 下的 NewLesServer() 函数开启一个 LES 服务并初始化,并通过 NewProtocolManager() 实现以太坊子协议的接口函数。其中 les/handle.go 包含了 LES 服务交互的大部分逻辑。

回顾上文 p2p 网络架构,最终 p2p 底层通过 p.Run() 启动协议,在 LES 协议中,也就是调用 LES 协议的 Run() 函数:

  1.  

    [./les/handle.go#NewProtocolManager()]

  2.  

    Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {

  3.  

    ...

  4.  

    select {

  5.  

    case manager.newPeerCh <- peer:

  6.  

    ...

  7.  

    err := manager.handle(peer)

  8.  

    ...

  9.  

    case <-manager.quitSync:

  10.  

    ...

  11.  

    }

  12.  

    }

可以看到重要的处理逻辑都被包含在 handle() 函数中,handle() 函数的主要功能包含 LES 协议握手和消息处理,下面是 handle() 函数片段:

  1.  

    [./les/handle.go]

  2.  

    func (pm *ProtocolManager) handle(p *peer) error {

  3.  

    ...

  4.  

    if err := p.Handshake(td, hash, number, genesis.Hash(), pm.server); err != nil {

  5.  

    p.Log().Debug("Light Ethereum handshake failed", "err", err)

  6.  

    return err

  7.  

    }

  8.  

    ...

  9.  

    for {

  10.  

    if err := pm.handleMsg(p); err != nil {

  11.  

    p.Log().Debug("Light Ethereum message handling failed", "err", err)

  12.  

    return err

  13.  

    }

  14.  

    }

  15.  

    }

在 handle() 函数中首先进行协议握手,其实现函数是 ./les/peer.go#Handshake(),通过服务端和客户端交换握手包,互相获取信息,其中包含有:协议版本、网络号、区块头哈希、创世块哈希等值。随后用无线循环处理通信的数据,以下是报文处理的逻辑:

  1.  

    [./les/handle.go]

  2.  

    func (pm *ProtocolManager) handleMsg(p *peer) error {

  3.  

    msg, err := p.rw.ReadMsg()

  4.  

    ...

  5.  

    switch msg.Code {

  6.  

    case StatusMsg: ...

  7.  

    case AnnounceMsg: ...

  8.  

    case GetBlockHeadersMsg: ...

  9.  

    case BlockHeadersMsg: ...

  10.  

    case GetBlockBodiesMsg: ...

  11.  

    ...

  12.  

    }

  13.  

    }

处理一个请求的详细流程是:

  1. 使用 RLPXFrameRW 帧处理器,获取请求的数据。
  2. 使用共享密钥解密数据。
  3. 使用 RLP 编码将二进制数据序列化。
  4. 通过对 msg.Code 的判断,执行相应的功能。
  5. 对响应数据进行 RLP 编码,共享密钥加密,转换为 RLPXFrameRW,最后发送给请求方。

下面是 LES 协议处理流程:

0x08 总结

通过本文的分析,对以太坊网络架构有了大致的了解,便于后续的分析和代码审计;在安全方面来讲,由协议所带的安全问题往往比本地的安全问题更为严重,应该对网络层面的安全问题给予更高的关注。

从本文也可以看到,以太坊网络架构非常的完善,具有极高的鲁棒性,这也证明了以太坊是可以被市场所认可的区块链系统。除此之外,由于 p2p 网络方向的资料较少,以太坊的网络架构也可以作为学习 p2p 网络的资料。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

以太坊网络架构解析 的相关文章

  • Movidius神经计算棒3-完整配置开发环境

    上面是我的微信和QQ群 欢迎新朋友的加入 cd ncsdk make install 这里有个题外话 英特尔这个玩意搞得不太友好 很多软件都有版本限制 高了或或者低了都要不得 这倒也没啥事 但是有个受影响的是 官方提供的make insta
  • 【Linux】系统下各个文件目录的作用

    目录名 作用 bin 普通用户的可执行文件 二进制 boot linux内核和系统启动文件 dev 设备文件 etc 系统配置文件 home 用户主目录 initrd 启动时挂在initrdimg映像文件 lib 共享库文件 lost fo

随机推荐

  • MySQL必知必会——第六章过滤数据

    过滤数据 本章将讲授如何使用SELECT语句的WHERE子句指定搜索条件 使用WHERE子句 数据库一般包含大量的数据 很少需要检索表中所有的行 通常只会根据需求来提取部分表数据 只检索所需数据需要指定搜索条件 search criteri
  • hdf5 简介、中文文档、中英对照文档 下载

    hdf5 文档 下载链接 含jar包 源码 pom 组件名称 中文 文档 下载链接 中英对照 文档 下载链接 hdf5 1 12 0 1 5 5 jar hdf5 1 12 0 1 5 5 API文档 中文版 zip hdf5 1 12 0
  • Android应用开发 一课一得

    Android是一种基于Linux平台开发的操作系统 Android应用开发这一门课 以项目为载体 以实做为手段 我通过学习和实践 对Android的开发流程 核心概念和常见技术有了基本的理解 在本学期的Android课程中 我首先学习了A
  • windows编译zookeeker动态库供C++链接使用以及遇到的错误处理方法

    windows下面C 链接zookeeper资料不多 特此记录一下 编译环境VS 2015 一 相关安装包安装下载 1 zookeeper zookeeper3 6 4 下载zip包解压即可 2 ant apache ant 1 9 16
  • 【实践5】Python pandas正则替换Excel表中单元格的标点符号等特殊符号

    简单介绍 这里举例替换特殊符号 有时在进行网页爬虫的时候会获取到一些含有特殊格式或符号的内容 但其中的符号像逗号 百分号这些并不是我们需要的 可以使用正则或是pandas将其替换掉 这个用例是使用正则的规则 通过pandas的apply函数
  • Unity3D_touch事件和点击事件

    现在要实现鼠标点击在哪个位置 物体就朝向哪里运动 所以是将两个部分融合到一起 1 物体的运动 2 鼠标的点击 if Input GetMouseButtonDown 0 0在PC上表示鼠标的左键 在手机上表示落下的第一个手指头 以此类推 好
  • WebLogic Server 远程代码执行漏洞复现 (CVE-2023-21839)

    1 产品简介 Oracle WebLogic Server是一个统一的可扩展平台 用于在本地和云端开发 部署和运行企业应用程序 例如 Java WebLogic Server提供了Java Enterprise Edition EE 和Ja
  • simunlink的“Three-Phase V-I Measurement”所测线电压次序问题

    simunlink的 Three Phase V I Measurement 所测线电压次序问题 仿真实例 很多同学在使用simulink进行仿真时可能会用到 Three Phase V I Measurement 这个模块 在该模块par
  • np.random.normal()函数

    np random normal 的意思是一个正态分布 normal这里是正态的意思 numpy random normal loc 0 scale 1 0 size shape 参数loc float 正态分布的均值 对应着这个分布的中心
  • osg 的warning C4003: “max”宏的实参不足 error C2589: “(” : “::”右边的非法标记

    原来是需要把max用括号括起来避免和windows定义的宏混淆 std numeric limits max 或者 std max 因为Windef h中定义了 ifndef max define max a b a gt b a b en
  • Oracle中的数据类型——NUMBER

    NUMBER类型概述 NUMBER类型可以用来存储0 正数 负数 数据范围是1 10 130 1 10126 不能等于或者大于1 10126 否则Oracle会报错 算数表达式的结果同理 NUMBER类型的定义 NUMBER precisi
  • 计算机e盘丢失了,电脑E盘突然不见了怎么找回_电脑的E盘突然不见了的解决方法...

    电脑一般会有C D E F等多个磁盘 用于储存不同的程序 方便管理 近期 有用户说自己准备打开E盘安装软件 结果发现E盘突然不见了 这样就没办法把软件安装在E盘上 有什么办法能让E盘恢复 方法有的 现在整理具体操作教程给大家 具体方法如下
  • C++强制类型转换

    C 类型转换 C风格的强制转换 在C 基本的数据类型中 可以分为四类 整型 浮点型 字符型 布尔型 其中数值型包括 整型与浮点型 字符型即为char 1 将浮点型数据赋值给整型变量时 舍弃其小数部分 2 将整型数据赋值给浮点型变量时 数值不
  • 手机网站支付宝支付

    1 支付宝开放平台 支付宝手机网站支付 具体的请求参数和返回参数等相关数据 https docs open alipay com 203 107090 2 支付demo 下载手机网站支付相关的demo 这里的demo和APP支付提供的dem
  • Android webview登录手机QQ

    选择我们的应用 在对应的上述我们定义的QQActivity的onCreate或onNewIntent 如果该activity在栈里出现过 里就能响应了 通过intent取出url 找了url特征字符没有发现token或code 才发现在系统
  • 数据分析之Pandas从入门到放弃:代码+实战,9分钟带你推开Pandas大门!!!

    今天整理了一下Pandas的使用方法 应该是全网整理最完整 最简洁易读 立整 的一篇文章 嗯 别不信 确实是这样的 跟着小鱼 带你9分钟推开Pandas的大门 从此走上数据分析师的苦逼之路 Pandas使用方法 1 Pandas的基本定义
  • 制作个人图片api接口

    制作个人图片api接口 前言 准备工作 过程介绍 操作过程 上传图片到github仓库中 在github中进行项目的发布 编写php文件 引用jsDeliver上的文件 在nginx配置文件中配置好php fpm相关配置 测试 测试php配
  • MFC设计

    细说UI线程和Windows消息队列 转载于 http blog csdn net huasonl88 article details 8589396 0 tsina 1 99086 397232819ff9a47a7b7e80a40613
  • Ubuntu18.04版本安装ssh及连接ssh的常见问题

    下面我们来解决Ubuntu18 04版本安装ssh及连接ssh的常见问题 及解决方法 题外话 安装Ubuntu时会提示一句Please remove the installation medium then reboot 提示这段话 可以直
  • 以太坊网络架构解析

    以太坊网络架构解析 版权 0x7F 知道创宇404区块链安全研究团队 https www cnblogs com southx p 9334639 html 0x00 前言 区块链的火热程度一直以直线上升 其中以区块链 2 0 以太坊为代表