Go语言net/http包源码学习

news/2024/10/22 17:24:56

0.前言

该笔记为笔者第一次学习go的net/http包源码的时候所记,也许写的并不是很精确,希望大家多多包涵,一起讨论学习。

该笔记很大程度的参考了网名为“小徐先生”的前辈所分享的博客,推荐大家可以先看一看它的博客来一起学习,我的只是照葫芦画瓢还有一些代码更新的讲解而已。

当前笔者追溯的是go版本为1.22.2的源码,对于中间看不太懂的片段,大家可以先看在文末我画的总流程图,再跟着流程图一段段来看。

参考博客链接:Golang HTTP 标准库实现原理 (qq.com)

1.核心数据结构:ServerMux

ServeMux 是 Go 标准库中的 HTTP 请求多路复用器(HTTP request multiplexer),负责将传入的 HTTP 请求路由到相应的处理程序。ServeMux 会根据注册的 URL 模式匹配传入的请求,并调用最合适的处理程序。以下是它的详细作用和 Go 1.22 带来的变化:

1. ServeMux 的主要功能

  • 路由匹配ServeMux 根据请求的 URL 和注册的模式(patterns)进行匹配,并根据匹配结果调用对应的处理函数。模式可以包含方法、主机名和路径的组合。例如:
    • "/index.html" 匹配路径为 /index.html 的任何主机和方法的请求。
    • "GET /static/" 匹配方法为 GET,路径以 /static/ 开头的请求。
    • "example.com/" 匹配主机为 example.com 的任何请求。
  • 通配符支持ServeMux 允许使用通配符匹配 URL 路径的一部分,如 "/b/{bucket}/o/{objectname...}",可以匹配路径中特定的部分。
  • 优先级机制:当多个模式匹配同一个请求时,ServeMux 会选择最具体的模式。例如,"/images/thumbnails/""/images/" 更具体,因此会优先匹配。
  • 尾部斜杠重定向ServeMux 会自动处理路径末尾的斜杠,如果注册了某个路径下的子树,ServeMux 会将缺少尾部斜杠的请求重定向到带有尾部斜杠的路径。
  • 请求清理ServeMux 会清理 URL 路径,例如移除 ... 等特殊路径,确保路径的规范化。

2. Go 1.22 的变更

Go 1.22 对 ServeMux 的模式匹配规则做出了重要的改动,与 Go 1.21 版本相比有若干不兼容的变化。以下是主要变化:

  • 通配符:在 Go 1.21 中,通配符被当作普通的文字路径段处理,比如 "/{x}" 只匹配与之完全相同的路径,而在 Go 1.22 中,它可以匹配任意一个路径段。
  • 模式验证:Go 1.22 开始会对模式的语法进行严格检查,任何无效的模式在注册时会导致程序 panic。例如,"/{""/a{x}" 在 1.21 中是合法的模式,但在 1.22 中会被认为是无效的。
  • 转义处理:Go 1.22 开始,每个路径段都会被解码。之前的版本对整个路径进行转义,而不是逐段处理。例如,"/%61" 在 1.22 中会匹配路径 "/a",但在 1.21 中只会匹配 "/%2561"
  • 匹配方式的改变:Go 1.22 会逐段解码路径,并处理 %2F 等字符的转义,这对于处理带有 %2F 的路径来说,与之前的版本有很大的不同。

如果需要保留 Go 1.21 的行为,可以通过设置环境变量 GODEBUG=httpmuxgo121=1 来恢复旧的模式匹配逻辑。

具体例子

以下代码展示了如何使用 ServeMux 并注册不同的路由:

mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)
mux.HandleFunc("/static/", staticHandler)
mux.HandleFunc("/b/{bucket}/o/{objectname...}", objectHandler)http.ListenAndServe(":8080", mux)

在这个例子中,不同的 URL 模式会被路由到相应的处理函数,Go 1.22 的变化会影响像 "/b/{bucket}/o/{objectname...}" 这种通配符模式的匹配方式。

这些变更旨在提高模式匹配的灵活性和一致性,同时使得 ServeMux 在复杂的路由需求下更具可扩展性。

3.ServeMux结构分析

先来看ServeMux的数据结构

type ServeMux struct {mu       sync.RWMutex //互斥锁tree     routingNode //存储pat对应的handlerindex    routingIndex //用于进行路由冲突检测patterns []*pattern  // TODO(jba): remove if possible(类似与pat的副本,在未来可能会删除)mux121   serveMux121 // used only when GODEBUG=httpmuxgo121=1
}

为了进一步了解它内嵌的结构,我们先来观看routingNode的源码实现。

2.routingNode

// A routingNode is a node in the decision tree.
// The same struct is used for leaf and interior nodes.
type routingNode struct {// 一个页子节点持有一个pat和它注册的handler。pattern *patternhandler Handler// special children keys://     "/"	trailing slash (resulting from {$})//	   ""   single wildcard//	   "*"  multi wildcardchildren   mapping[string, *routingNode] //将路径的一部分映射到子节点中,形成一个路由树。emptyChild *routingNode // 一个优化字段,用于快速访问键为 "" 的子节点,减少查找开销。
}

对于三个special children keys的解释:

  • 对于“/”,它处理了路径中以斜杠为结尾的情况,例如当一个路由模式以斜杠结尾(如 "/images/"),ServeMux 会自动处理不带斜杠的请求(如 "/images") 并将其重定向到带有斜杠的路径("/images/"
  • “”作为一个匹配单个路径段的通配符
  • “*”作为一个匹配多个路径段的通配符

2.1addPattern

//addPattern方法添加一个pattern和它的handler到树中
func (root *routingNode) addPattern(p *pattern, h Handler) {// First level of tree is host.n := root.addChild(p.host) //先获取host对应的routingNode// Second level of tree is method.n = n.addChild(p.method) //接着再获取method对应的routingNode// Remaining levels are path.n.addSegments(p.segments, p, h)
}

2.2addChild

//向一个 routingNode (路由节点)添加一个子节点,并返回该子节点。如果节点已经存在,则直接返回;如果不存在,则创建一个新的子节点并返回。
func (n *routingNode) addChild(key string) *routingNode {if key == "" { //路径为空,如果存在emptyChild则直接返回作为一个单项通配符,如果不存在则创建并返回。if n.emptyChild == nil {n.emptyChild = &routingNode{}}return n.emptyChild}//查找是否存在对应的patif c := n.findChild(key); c != nil {return c}c := &routingNode{}n.children.add(key, c)return c
}

我们再来看对应的findChild方法的实现

// findChild returns the child of n with the given key, or nil
// if there is no child with that key.
func (n *routingNode) findChild(key string) *routingNode {if key == "" {return n.emptyChild}r, _ := n.children.find(key)return r
}

可以看到,最终是直接调用了map去寻找是否存在对应的value。

2.3addSegements

// addSegments 的作用是将路径中的各个段(segments)逐步添加到路由树中,并最终将模式 (pattern) 和处理器 (handler) 设置在路由树的叶子节点。
func (n *routingNode) addSegments(segs []segment, p *pattern, h Handler) {if len(segs) == 0 { //如果segs为空,表示当前为一个叶子节点,此时就将p和h绑定在该节点上n.set(p, h)return}seg := segs[0]if seg.multi { //如果此时当前的segment是多段通配符,通常为*if len(segs) != 1 { //检查当前是否为最后一段,因为多段通配符必须是路径的最后一段。panic("multi wildcard not last")}n.addChild("*").set(p, h) //绑定} else if seg.wild { //若是单段通配符,则进一步递归n.addChild("").addSegments(segs[1:], p, h)} else { //同理n.addChild(seg.s).addSegments(segs[1:], p, h)}
}

3.routingIndex

//routingIndex是一个用于优化路由冲突检测的数据结构
//它的检测思想是通过排除对于给定的pattern,不可能与之冲突的部分,只检测可能产生冲突的部分来达到快速检测的目的。
type routingIndex struct {segments map[routingIndexKey][]*patternmultis []*pattern
}
type routingIndexKey struct {pos int    // 0-based segment positions   string // literal, or empty for wildcard
}
  • sgements:记录了每一个routingIndexKey注册的pattern组,routingIndexKey包含两个字段,pos标识了s出现在pattern中的段的位置。例如,对于一个key{1,"b"},它对应了pattern为"/a/b"、“/a/b/c”,因为b处在第1段。
  • multis:用于存储所有以多段通配符为结尾的patterns

3.1addPattern

func (idx *routingIndex) addPattern(pat *pattern) {if pat.lastSegment().multi { //若以多段通配符结尾,则直接添加idx.multis = append(idx.multis, pat)} else {if idx.segments == nil { //若第一次注册,先初始化idx.segments = map[routingIndexKey][]*pattern{}}for pos, seg := range pat.segments {key := routingIndexKey{pos: pos, s: ""}if !seg.wild {key.s = seg.s //非通配符就赋值}idx.segments[key] = append(idx.segments[key], pat) //添加pat}}
}

3.2possiblyConflictingPatterns

//该函数会调用方法f去对所有可能与pat冲突的pattern进行检测,如果f返回一个非空的错误,那么possiblyConflictingPatterns会立即返回这个错误
func (idx *routingIndex) possiblyConflictingPatterns(pat *pattern, f func(*pattern) error) (err error) {//一个辅助函数,用于对所有的pats进行检测apply := func(pats []*pattern) error {if err != nil {return err}for _, p := range pats {err = f(p)if err != nil {return err}}return nil}//首先对多段通配符进行进测,因为这些pattern可能与任何传入的pat冲突if err := apply(idx.multis); err != nil {return err}//处理以/为结尾的路径段if pat.lastSegment().s == "/" {return apply(idx.segments[routingIndexKey{s: "/", pos: len(pat.segments) - 1}])}//如果模式不是以/为结尾,那么函数会检测与它的相同位置有相同的字面值或是单段通配符的模式,//通过遍历模式的每一个段,函数寻找可能冲突的模式,并在可能冲突最少的段上进行匹配//函数在查找时会计算每个段位置的匹配模式数量,并选择pattern数量最少的段进行匹配,这样可以优化冲突检测的效率。var lmin, wmin []*pattern //分别存储literal匹配的pattern集合和通配符wildcard匹配的pattern集合min := math.MaxInthasLit := false //标识是否遇到了literalfor i, seg := range pat.segments {if seg.multi { //跳过多段匹配符,因为已经在上面检测过break}if !seg.wild { //对于非通配符hasLit = true//索引中查找所有与当前字面值 seg.s 和位置 i 匹配的模式集合(lpats)lpats := idx.segments[routingIndexKey{s: seg.s, pos: i}]//查找所有与位置 i 匹配的通配符模式集合(wpats)wpats := idx.segments[routingIndexKey{s: "", pos: i}]if sum := len(lpats) + len(wpats); sum < min {lmin = lpatswmin = wpatsmin = sum}}}if hasLit {//对可能的冲突进行匹配检测apply(lmin)apply(wmin)return err}//该pattern是由通配符组成,需要检查和任意的pat会不会冲突for _, pats := range idx.segments {apply(pats)}return err
}

该方法的主要作用是优化了冲突检测的效率,当我们需要为一个路由注册handler的时候,会先检查是否存在路由模式匹配冲突,这时候使用该方法可以加快检测效率。

4.循序渐进学Serve服务

在main函数中,使用以下几行代码即可启动一个Server

func main() {http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("pong"))})http.ListenAndServe(":8091", nil)
}

我们调用了HandleFunc函数,为"/ping"这一个路由注册了一个处理函数,然后通过ListenAndServe启动监听,通过这个实例,我们跟进HandleFunc查看具体的源码过程。

4.1HandlerFunc函数实现

// HandleFunc registers the handler function for the given pattern in [DefaultServeMux].
// The documentation for [ServeMux] explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {if use121 {DefaultServeMux.mux121.handleFunc(pattern, handler)} else {DefaultServeMux.register(pattern, HandlerFunc(handler))}
}

当调用公开的函数HandleFunc时,会默认将路由注册到context包下的DefaultServeMux中,它是一个ServeMux的具体实现。

本笔记探讨的是1.22版本开始的Server模式,于是跟进register函数查看。

func (mux *ServeMux) register(pattern string, handler Handler) {if err := mux.registerErr(pattern, handler); err != nil {panic(err)}
}func (mux *ServeMux) registerErr(patstr string, handler Handler) error {if patstr == "" {return errors.New("http: invalid pattern")}if handler == nil {return errors.New("http: nil handler")}if f, ok := handler.(HandlerFunc); ok && f == nil {return errors.New("http: nil handler")}pat, err := parsePattern(patstr) //将patstr转换为标准的pattern结构if err != nil {return fmt.Errorf("parsing %q: %w", patstr, err)}// Get the caller's location, for better conflict error messages.// Skip register and whatever calls it.//这里使用了runtime.Caller函数来获取调用这个函数的代码位置(即调用者的信息),主要是为了在发生错误时,能在错误信息中提供更准确的源代码位置。这可以帮助开发者更方便地定位问题。//runtime.Caller()中的参数3表示获取调用栈中第三层的调用信息,在这里层级的顺序大概是//1.runtimer.Caller本身//2.当前方法registerErr的调用//3.调用registerErr的函数_, file, line, ok := runtime.Caller(3)if !ok {pat.loc = "unknown location"} else {pat.loc = fmt.Sprintf("%s:%d", file, line)}//以下方法用于检测新注册的路由pat是否会和已有的路由pat2存在冲突的逻辑mux.mu.Lock()defer mux.mu.Unlock()// Check for conflict.if err := mux.index.possiblyConflictingPatterns(pat, func(pat2 *pattern) error {if pat.conflictsWith(pat2) {d := describeConflict(pat, pat2)return fmt.Errorf("pattern %q (registered at %s) conflicts with pattern %q (registered at %s):\n%s",pat, pat.loc, pat2, pat2.loc, d)}return nil}); err != nil {return err}//若不存在冲突,则为对应的pat注册handler方法mux.tree.addPattern(pat, handler) mux.index.addPattern(pat)mux.patterns = append(mux.patterns, pat)return nil
}

4.2启动Serve服务的ListenAndServe

调用net/http包下的公开的ListenAndServe函数,就可以实现对服务端的一键启动功能,跟进该函数看看它都做了什么

1.ListenAndServe

func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}return server.ListenAndServe()
}

该方法创建了一个Server实例,并且调用该server的ListenAndServe方法,Server实例中若handler为nil,则自动调用DefaultServeMux。

2.server.ListenAndServe

func (srv *Server) ListenAndServe() error {if srv.shuttingDown() {return ErrServerClosed}addr := srv.Addrif addr == "" {addr = ":http" //若srv.Addre为“”,则默认为此}ln, err := net.Listen("tcp", addr)//启用TCP监听if err != nil {return err}return srv.Serve(ln)
}

该方法使用TCP连接Server中的地址,并且使用该实例中注册的Handler去处理业务

3.server.Serve

var ServerContextKey = &contextKey{"http-server"}type contextKey struct {name string
}
func (srv *Server) Serve(l net.Listener) error {// ...ctx := context.WithValue(baseCtx, ServerContextKey, srv)for {rw, err := l.Accept()// ...connCtx := ctx// ...c := srv.newConn(rw)// ...go c.serve(connCtx)}
}

Serve方法是go进行服务端处理连接的核心方法,在这段核心片段中,首先先创建了一个带有键值对的上下文ctx,其中键是ServerContextKey,对应着一个服务器实例srv。

接着使用for循环的方式,负责监听并接受来自Listener的连接(l.Accept()),每一次的循环都会尝试从监听器中获取一个新的连接rw。

每一个连接都会关联一个connCtx,在默认的情况它就是之前创建的ctx

使用srv.newConn创建了一个新的连接对象c,封装了rw作为http连接,最后启动了一个新的协程异步处理该连接的请求,这个c.serve方法会处理来自客户端HTTP的请求。

可以看到,整体方法的思路是采用for循环监听连接,并且为之分配一个协程进行处理。

4.conn.Serve

接着我们来看conn.Serve,它作为实际处理请求的核心,非常重要,它的整个方法代码比较长,我们分成几部分,着重看请求处理的部分。

第一个部分,进行设置上下文ctx和异常处理

ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
var inFlightResponse *response
defer func() {if err := recover(); err != nil && err != ErrAbortHandler {const size = 64 << 10buf := make([]byte, size)buf = buf[:runtime.Stack(buf, false)]c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)}if inFlightResponse != nil {inFlightResponse.cancelCtx()}if !c.hijacked() {if inFlightResponse != nil {inFlightResponse.conn.r.abortPendingRead()inFlightResponse.reqBody.Close()}c.close()c.setState(c.rwc, StateClosed, runHooks)}
}()

使用了defer设置了一个错误处理机制,如果在请求期间发生了panic,会捕获错误并且记录日志。

接着第二个部分为处理TLS(HTTPS)连接

if tlsConn, ok := c.rwc.(*tls.Conn); ok {tlsTO := c.server.tlsHandshakeTimeout()if tlsTO > 0 {dl := time.Now().Add(tlsTO)c.rwc.SetReadDeadline(dl)c.rwc.SetWriteDeadline(dl)}if err := tlsConn.HandshakeContext(ctx); err != nil {if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")re.Conn.Close()return}c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)return}...
}

该代码段进行TLS握手,若握手成功便会记录TLS的状态。

接着来到了读取并且处理HTTP请求的阶段

ctx, cancelCtx := context.WithCancel(ctx)
c.cancelCtx = cancelCtx
defer cancelCtx()c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

对于每一个连接,都会分配一个新的读取器bufr和写入器bufw,提高读取和写入的效率

请求处理循环

for {w, err := c.readRequest(ctx)if err != nil {// 错误处理,比如请求过大或不支持的传输编码}// 如果 Expect: 100 continue,支持继续if req.expectsContinue() { ... }// 处理请求,调用服务器的处理函数serverHandler{c.server}.ServeHTTP(w, w.req)// 完成请求并判断是否需要复用连接w.finishRequest()if !w.shouldReuseConnection() {return}// 设置连接为空闲状态c.setState(c.rwc, StateIdle, runHooks)c.curReq.Store(nil)
}
  • 这个循环会反复读取请求、处理请求、并且发送响应
  • 使用serverHandler.ServeHTTP来调用对应的处理程序,比如用户设置的Server或者DefaultServerMux

最终处理完请求会检查是否可以复用连接,如果不能复用(例如客户端请求 Connection: close),则关闭连接。

5.serverHandler.ServeHTTP

在ServeHTTP方法中,会对Handler做判断,倘若没有声明,则取全局单例DefaultServeMux进行路由匹配

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {handler := sh.srv.Handlerif handler == nil {handler = DefaultServeMux}if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {handler = globalOptionsHandler{}}​	handler.ServeHTTP(rw, req)
}

6.ServeMux.ServeHTTP

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {if r.RequestURI == "*" {if r.ProtoAtLeast(1, 1) {w.Header().Set("Connection", "close")}w.WriteHeader(StatusBadRequest)return}var h Handlerif use121 {h, _ = mux.mux121.findHandler(r)} else {h, _, r.pat, r.matches = mux.findHandler(r)}h.ServeHTTP(w, r)
}
  • 首先处理了特殊的RequestURI为“*”的请求
  • 接着对于1.21版本开始的go,它会去查找request对应的Handler,返回对应的pattern和匹配结果

一旦找到了与请求路径匹配的处理器 h,调用该处理器的 ServeHTTP 方法来处理当前请求。

7.ServeMux.findHandler

// ...
host = stripHostPort(r.Host)
path = cleanPath(path)
n, matches, u = mux.matchOrRedirect(host, r.Method, path, r.URL)
// ...
return n.handler, n.pattern.String(), n.pattern, matches
  • 通过 stripHostPort 去掉主机名中的端口信息,然后使用 cleanPath 净化请求路径,去除多余的 /.
  • 调用 mux.matchOrRedirect 进行匹配,如果匹配成功,则返回相应的路由节点 (n) 和匹配的参数 (matches)
  • 最终会返回对应的handler

8.ServeMux.matchOrRedirect

func (mux *ServeMux) matchOrRedirect(host, method, path string, u *url.URL) (_ *routingNode, matches []string, redirectTo *url.URL) {mux.mu.RLock()defer mux.mu.RUnlock()n, matches := mux.tree.match(host, method, path)//...return n, matches, nil
}

核心是调用了routingNode的match方法去找到对应的node节点。

5.回顾总结(可视化总流程)

我们以下面的main函数为例子,再重新回顾一下启动服务器的具体流程

func main() {http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("pong"))})http.ListenAndServe(":8091", nil)
}

首先我们为"/ping"路径注册了一个handler方法,go会自动将我们写的func转换为实现了Handler接口的HandlerFunc类型,并且将其添加到默认分路器的节点中。

DefaultServeMux.register(pattern, HandlerFunc(handler)) //将我们写的func转换为HandlerFunc

HandlerFunc和ServeMux都实现了Handler接口,所以都可以被看作为handler,handler用于处理一个request。

接着,我们启用监听,假设在监听中,我们访问了localhost:8091/ping,最终流程如下。

最终我们启动main服务,在网页打开localhost:8091/ping,就能看到网页输出pong啦

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

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

相关文章

linux之core文件调试

linux之core文件调试 前言 有时候程序会异常退出而不带任何日志,此时就可以使用 core 文件进行分析,它会记录程序运行的内存,寄存器,堆栈指针等信息 什么是core文件 通常在 Linux 下遇到程序异常退出或者中止,我们都会使用 core 文件进行分析,其中包含了程序运行时的内存…

物联网从层次结构上分为几层,各层的主要作用是什么

物联网的层次结构包括感知层、网络层和核心层,每个层次都扮演着不可或缺的角色。感知层负责数据采集,网络层实现数据传输,核心层则进行数据处理和决策。这种层次结构的设计使得物联网能够高效地运行,为人们的生活和工作带来了巨大的便利和效益。1. 感知层(Perception Laye…

移动开发(四):.NET MAUI中Android应用修改安装图标和启动页面

今天继续给大家分享.NET MAUI中开发的Android应用如何修改安装图标和启动页面,希望对大家使用Net开发安卓APP提供一些帮助! 一、更换APP应用图标 这里我们直接编辑项目文件 MyFirstMauiApp.csproj来修改APP应用图标 官方案例默认的组合图标,其中ForegroundFile表示前景图像(…

将NC栅格表示时间维度的数据提取出来的方法

本文介绍基于Python语言,逐一读取大量.nc格式的多时相栅格文件,导出其中所具有的全部时间信息的方法~本文介绍基于Python语言,逐一读取大量.nc格式的多时相栅格文件,导出其中所具有的全部时间信息的方法。.nc是NetCDF(Network Common Data Form)文件的扩展名,表示一种常…

哪种IDE能同时写java和前端代码

在选择IDE(集成开发环境)来同时编写Java和前端代码时,几个主要的选择包括IntelliJ IDEA、Eclipse、和Visual Studio Code。IntelliJ IDEA提供了强大的Java开发支持和广泛的前端开发插件,Eclipse以其插件生态系统著称,可以通过安装相应的插件支持Java和前端开发,而Visual …

2024.10.22总结

byd放三道黑是吧本文于 github 博客同步更新。 今天打两场 byd放三道黑是吧。 第一场: A: CF1261F 将区间拆分为 \([x2^{i},(x+1)2^{i})\) 的形式,发现两个区间中的数两两异或后形成的仍为一个区间,将 A,B 都拆分后区间两两异或会得到 \(O(n^2\log^2n)\) 个区间,取并即为答…

【FMC163】基于VITA57.1标准的双通道3GSPS AD采集、双通道12GSPS DA回放FMC子卡模块(100%国产化)

板卡概述 FMC163是一款基于VITA57.1标准的实现2路14-bit、3GSPS ADC采集功能、2路14-bit 12GSPS DA回放FMC子卡模块。该模块遵循VITA57.1标准,可直接与FPGA载卡配合使用,该板卡支持对6GHz的射频信号进行数字化采样以及信号生成,板内集成了高性能的时钟管理模块,具有极高的收…

玄机蓝队靶场_应急响应_71:实战evtx-文件分析

windows日志排查工具: https://www.cnblogs.com/starrys/p/17129993.htmlwindows日志事件ID,参考文章:https://peterpan.blog.csdn.net/article/details/139887217下载日志分析工具FullEventLogView.exe https://www.nirsoft.net/utils/fulleventlogview-x64.zip 分别打开三个…