gin源码实战 day1

gin框架源码实战day1

Radix树
这个路由信息:

r := gin.Default()r.GET("/", func1)
r.GET("/search/", func2)
r.GET("/support/", func3)
r.GET("/blog/", func4)
r.GET("/blog/:post/", func5)
r.GET("/about-us/", func6)
r.GET("/about-us/team/", func7)
r.GET("/contact/", func8)

得到的路由树解析:

Priority   Path             Handle
9          \                *<1>
3          ├s               nil
2          |├earch\         *<2>
1          |└upport\        *<3>
2          ├blog\           *<4>
1          |:post      nil
1          |         └\     *<5>
2          ├about-us\       *<6>
1          |        └team\  *<7>
1          └contact\        *<8>

解读:
请添加图片描述
这个图只是把结点的值写在了变上,不要误会,其实值是在点中。

节点:每个节点代表路由路径的一部分。例如,s、earch\ 和 support\ 节点分别代表 /search/ 和 /support/ 路径的组成部分。
分支节点之间的连接线表示路径的分支。例如,├ 和 └ 符号表示从父节点分出的不同子路径。
叶节点:代表完整的路由路径,通常在叶节点处关联具体的处理函数(如 <1>、<2> 等)。这些处理函数用于响应对应路径的 HTTP 请求。
nil 节点:表示该路径节点是参数路径(如 :post)的一部分,或者是路径的中间部分,而不直接关联处理函数。

路由树详细解读
根节点:\ 代表根节点,即路由的起点。它关联了处理根路径 / 请求的函数 *<1>。
s 分支:表示以 s 开头的路径。它进一步分为两个子路径:earch\(对应 /search/,处理函数 *<2>)和 upport\(对应 /support/,处理函数 *<3>)。
blog\ 分支:代表 /blog/ 路径,关联处理函数 *<4>。它有一个参数子路径 :post\,用于匹配如 /blog/:post/ 形式的路径,最终关联的处理函数是 *<5>。
about-us\ 分支:代表 /about-us/ 路径,关联处理函数 *<6>。它有一个静态子路径 team\,对应 /about-us/team/ 路径,处理函数为 *<7>。
contact\ 分支:代表 /contact/ 路径,关联处理函数 *<8>。

符号说明
├ 和 └:分支符号,├ 用于表示除最后一个分支外的节点,└ 用于表示最后一个分支节点。
nil:表示该节点不直接关联处理函数,可能是因为它是一个参数化的路径部分,或者是未到达叶节点的中间节点。
*<数字>:代表与节点关联的处理函数。数字仅为示例,实际上会是指向处理函数的指针。

源码解读:

从这个最简单的例子,来逐步分析源码

package mainimport ("github.com/gin-gonic/gin""log""net/http"
)func main() {r := gin.Default()r.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "ok")})err := r.Run(":8080")if err != nil {log.Fatalln(err)}
}

先看启动的过程,也就是run函数
err := r.Run(“:8080”)这个我们只知道是用来启动的,来看看内部是如何启动的。
run函数的源码:
看源码的时候养成一个习惯,关注主要流程,忽略一些不必要的因素。

func (engine *Engine) Run(addr ...string) (err error) {defer func() { debugPrintError(err) }()if engine.isUnsafeTrustedProxies() {debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")}address := resolveAddress(addr)debugPrint("Listening and serving HTTP on %s\n", address)err = http.ListenAndServe(address, engine.Handler())return
}

该方法是用来启动 Gin 应用并监听 HTTP 请求的。
具体分析:
方法签名

func (engine *Engine) Run(addr ...string) (err error) {

engine *Engine:Engine 是 Gin 框架的核心结构体,代表整个 Gin 应用。这里的 engine 是一个指向 Engine 实例的指针,表示 Run 方法是 Engine 的一个方法。
addr …string:这是一个可变参数,表示 Run 方法可以接受一个或多个字符串参数,这些字符串代表监听地址(包括端口号)。如果没有提供参数,默认监听地址是 :8080。
(err error):这是方法的返回值,表示方法执行结束后可能返回的错误。

Engine结构体解读:

type Engine struct {RouterGroup// RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a// handler for the path with (without) the trailing slash exists.// For example if /foo/ is requested but a route only exists for /foo, the// client is redirected to /foo with http status code 301 for GET requests// and 307 for all other request methods.RedirectTrailingSlash bool// RedirectFixedPath if enabled, the router tries to fix the current request path, if no// handle is registered for it.// First superfluous path elements like ../ or // are removed.// Afterwards the router does a case-insensitive lookup of the cleaned path.// If a handle can be found for this route, the router makes a redirection// to the corrected path with status code 301 for GET requests and 307 for// all other request methods.// For example /FOO and /..//Foo could be redirected to /foo.// RedirectTrailingSlash is independent of this option.RedirectFixedPath bool// HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the// current route, if the current request can not be routed.// If this is the case, the request is answered with 'Method Not Allowed'// and HTTP status code 405.// If no other Method is allowed, the request is delegated to the NotFound// handler.HandleMethodNotAllowed bool// ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that// match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was// fetched, it falls back to the IP obtained from// `(*gin.Context).Request.RemoteAddr`.ForwardedByClientIP bool// AppEngine was deprecated.// Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD// #726 #755 If enabled, it will trust some headers starting with// 'X-AppEngine...' for better integration with that PaaS.AppEngine bool// UseRawPath if enabled, the url.RawPath will be used to find parameters.UseRawPath bool// UnescapePathValues if true, the path value will be unescaped.// If UseRawPath is false (by default), the UnescapePathValues effectively is true,// as url.Path gonna be used, which is already unescaped.UnescapePathValues bool// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.// See the PR #1817 and issue #1644RemoveExtraSlash bool// RemoteIPHeaders list of headers used to obtain the client IP when// `(*gin.Engine).ForwardedByClientIP` is `true` and// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.RemoteIPHeaders []string// TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by// that platform, for example to determine the client IPTrustedPlatform string// MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm// method call.MaxMultipartMemory int64// UseH2C enable h2c support.UseH2C bool// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.ContextWithFallback booldelims           render.DelimssecureJSONPrefix stringHTMLRender       render.HTMLRenderFuncMap          template.FuncMapallNoRoute       HandlersChainallNoMethod      HandlersChainnoRoute          HandlersChainnoMethod         HandlersChainpool             sync.Pooltrees            methodTreesmaxParams        uint16maxSections      uint16trustedProxies   []stringtrustedCIDRs     []*net.IPNet
}

Engine 结构体是 Gin Web 框架的核心,它继承了 RouterGroup,提供了路由和中间件的管理,同时也包含了很多与 HTTP 服务运行相关的配置。
解读:
1.RouterGroup:

type RouterGroup struct {Handlers HandlersChainbasePath stringengine   *Engineroot     bool
}

RouterGroup 是 Gin 框架中用于组织路由和中间件的结构体,它允许开发者将具有共同前缀的路由组织在一起,并且可以共享中间件。这样做有助于代码的组织和复用,使得路由和中间件的管理更加方便和高效。下面是对 RouterGroup 结构体字段的解读

Handlers HandlersChain: HandlersChain 类型,代表一系列的中间件处理函数。这些处理函数会按照添加的顺序被执行,并且会被绑定到 RouterGroup 下的所有路由上。这意味着,所有在此 RouterGroup 中注册的路由都会先通过这些中间件的处理。
这个地方我当初看我还是没懂,这里我做进一步解读:
HandlersChain 解释
HandlersChain 是一个中间件处理函数的切片(slice),在 Gin 中用于存储一组中间件。中间件是在处理 HTTP 请求之前或之后运行的函数,通常用于执行一些预处理操作(如日志记录、身份验证、数据验证等)或后处理操作(如设置响应头等)。在 RouterGroup 中注册的中间件会自动应用到该组下的所有路由上。
中间件执行流程
当一个请求到达 Gin 服务器时,Gin 会根据请求的 URL 和方法匹配路由。如果匹配成功,Gin 会按照 HandlersChain 中的顺序执行相应的中间件,最后执行路由的主处理函数。每个中间件可以选择:

1.直接返回响应,不再调用后续的中间件或主处理函数。
2.修改请求或响应的内容,然后调用下一个中间件或主处理函数。
3.执行一些不依赖请求内容的操作,如日志记录。
例子:
假设我们有一个 Web 应用,我们想为 /api 路径下的所有路由添加日志和身份验证两个中间件

package mainimport ("github.com/gin-gonic/gin""net/http"
)// 日志中间件
func LoggerMiddleware() gin.HandlerFunc {return func(c *gin.Context) {// 记录请求开始的日志// 注意:这里只是示例,实际使用时可能需要记录更多详细信息println("Starting request:", c.Request.URL.Path)c.Next() // 调用下一个中间件或主处理函数// 请求处理完毕后,记录日志println("Request finished")}
}// 身份验证中间件
func AuthMiddleware() gin.HandlerFunc {return func(c *gin.Context) {// 检查用户是否已认证// 这里只是简单示例,实际应用中应该是更复杂的逻辑token := c.GetHeader("Authorization")if token != "valid-token" {// 如果用户未认证,返回 401 状态码并终止请求c.AbortWithStatus(http.StatusUnauthorized)return}c.Next() // 用户已认证,继续处理请求}
}func main() {r := gin.Default()apiGroup := r.Group("/api")// 为/api路由组添加日志和身份验证中间件apiGroup.Use(LoggerMiddleware(), AuthMiddleware())// 注册一个路由apiGroup.GET("/users", func(c *gin.Context) {// 假设这是获取用户列表的处理函数c.JSON(http.StatusOK, gin.H{"message": "获取用户列表"})})r.Run(":8080")
}

在解读之前先补充gin.Context

type Context struct {writermem responseWriterRequest   *http.RequestWriter    ResponseWriterParams   Paramshandlers HandlersChainindex    int8fullPath stringengine       *Engineparams       *ParamsskippedNodes *[]skippedNode// This mutex protects Keys map.mu sync.RWMutex// Keys is a key/value pair exclusively for the context of each request.Keys map[string]any// Errors is a list of errors attached to all the handlers/middlewares who used this context.Errors errorMsgs// Accepted defines a list of manually accepted formats for content negotiation.Accepted []string// queryCache caches the query result from c.Request.URL.Query().queryCache url.Values// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,// or PUT body parameters.formCache url.Values// SameSite allows a server to define a cookie attribute making it impossible for// the browser to send this cookie along with cross-site requests.sameSite http.SameSite
}

gin.Context 是 Gin 框架中一个非常核心的结构体,它在 Gin 处理 HTTP 请求的过程中被广泛使用。gin.Context 封装了 Go 原生的 http.Request 和 http.ResponseWriter,提供了丰富的方法和属性,方便开发者对请求和响应进行操作。通过 gin.Context,开发者可以访问请求的数据、设置响应的数据、管理中间件、执行特定的路由函数等。
字段解读:
HTTP 请求和响应
writermem: 内部使用的 responseWriter,用于缓存响应写入器的状态。
Request: 指向原始的 http.Request 对象,包含了 HTTP 请求的所有信息,如头部、URL、请求体等。
Writer: ResponseWriter 接口的实例,用于构造 HTTP 响应。它封装了底层的 http.ResponseWriter,提供了更多的功能,如状态码的设置、写入响应头和体。

路由和处理
Params: 路由参数集合,每个参数包含键和值。例如,对于路由 /user/:name,Params 会包含一个键为 “name” 的参数。
handlers: HandlersChain 类型,存储了当前路由匹配到的所有中间件和处理函数。
index: 当前执行到的中间件或处理函数在 handlers 中的索引。
fullPath: 匹配到的完整路由路径。

应用和路由上下文
engine: 指向 Engine 的指针,即当前 Context 所属的 Gin 应用实例。
params: 一个指向路由参数 Params 的指针,用于内部处理。
skippedNodes: 内部使用,用于优化路由匹配过程。

并发控制和请求数据
mu: sync.RWMutex,保护 Keys 字段的读写操作。
Keys: 为每个请求独有的键值对存储空间,可以在中间件和处理函数间共享数据。
Errors: 存储在处理请求过程中产生的错误信息列表

内容协商和缓存
Accepted: 手动指定的用于内容协商的格式列表。
queryCache: 缓存了从 Request.URL.Query() 解析出的查询字符串参数。
formCache: 缓存了从 POST、PATCH 或 PUT 请求体解析出的表单数据。

Cookie 和安全
sameSite: http.SameSite 类型,用于设置 SameSite Cookie 属性,这有助于防止 CSRF 攻击。

gin.Context 的主要功能和用法包括:
请求数据处理:gin.Context 提供了多种方法来获取请求的参数,包括 URL 路径参数(:param 和 *param),查询字符串参数(Query),表单值(PostForm),以及 JSON、XML 等格式的请求体数据(BindJSON、BindXML 等)。

响应设置:开发者可以通过 gin.Context 设置 HTTP 响应的状态码、头部(Headers)、以及响应体。gin.Context 支持直接返回 JSON、XML、HTML 等格式的响应体,通过方法如 JSON、XML、HTML 等实现。

中间件管理:gin.Context 允许在处理函数中动态地添加或跳过后续的中间件执行,通过 Next、Abort 或 AbortWithStatus 等方法控制请求的处理流程。

错误处理:gin.Context 提供了 Error 方法,允许在请求处理过程中记录错误信息。这些错误信息可以在后续的中间件或请求处理函数中被检索和处理。

请求/响应上下文:gin.Context 在整个请求处理流程中被传递,作为请求上下文存在。它允许在中间件和处理函数之间共享数据,通过 Set、Get 方法存取上下文中的值。


HandlersChain例子解读:
在这个例子中,我们为 /api 路由组添加了两个中间件:LoggerMiddleware 和 AuthMiddleware。这意味着,对于 /api/users 的所有请求,首先会执行日志中间件记录请求开始,然后执行身份验证中间件检查用户是否已认证。如果用户未认证,请求将被终止,并返回 401 Unauthorized。如果用户已认证,请求最终会到达主处理函数,返回用户列表。

basePath string: 字符串类型,表示该 RouterGroup 的基础路径(Base Path)。所有在该组中注册的路由都会以这个路径为前缀。例如,如果 basePath 是 /api,那么在此组中注册的一个 /users 路径实际上会被解析为 /api/users。

*engine Engine: 指向 Engine 的指针,Engine 是 Gin 应用的核心结构体。这个字段表明了 RouterGroup 与一个 Engine 实例是关联的,通过这种方式,RouterGroup 可以访问 Engine 提供的功能和配置,如注册新的路由、添加中间件等。

root bool: 布尔类型,标识该 RouterGroup 是否是根路由组。在 Gin 中,根路由组是直接与 Engine 实例关联的路由组,而非根路由组则是通过调用根路由组的 Group 方法创建的子路由组。这个字段通常被内部使用,以区分根路由组和其他子路由组。

到这里就是engine第一个字段的解读


继续解读engine的字段
基本HTTP服务配置
RedirectTrailingSlash: 如果为 true,当路径匹配不成功但是去掉或添加尾部斜线后能匹配到时,会自动重定向到正确的路径。
RedirectFixedPath: 如果为 true,当请求的路径没有直接的处理函数时,Gin 会尝试修正路径(比如去掉多余的斜线、进行大小写不敏感的匹配)并重定向到修正后的路径。
HandleMethodNotAllowed: 如果为 true,当请求的方法(GET、POST等)不被允许时,会返回 405 Method Not Allowed 错误。
ForwardedByClientIP: 如果为 true,会尝试从请求头中解析出客户端的真实 IP 地址。

与请求路径和参数解析相关
UseRawPath: 如果为 true,Gin 会使用 url.RawPath 来查找参数。
UnescapePathValues: 如果为 true,路径中的参数值将被解码。
RemoveExtraSlash: 如果为 true,即使 URL 中包含额外的斜线也可以解析参数。

关于请求体的配置
MaxMultipartMemory: 设置解析 multipart/form-data 类型的请求体时允许的最大内存占用量。

高级配置
UseH2C: 如果为 true,启用 h2c 支持。
ContextWithFallback: 如果为 true,在某些情况下允许 Context 使用备用的方法来处理 Deadline、Done、Err 和 Value。

其他重要字段
RemoteIPHeaders: 定义了一组头部字段,用于在启用 ForwardedByClientIP 时解析客户端 IP 地址。
TrustedPlatform: 设置信任的平台,用于处理特定平台下的头部字段。
delims, secureJSONPrefix, HTMLRender, FuncMap: 分别用于模板渲染的定界符、安全 JSON 前缀、HTML 渲染器以及模板函数映射。
allNoRoute, allNoMethod, noRoute, noMethod: 分别用于处理未找到路由和不被允许的方法的处理函数链。
pool: 用于存储 Context 对象的池,优化内存使用。
trees: 存储所有路由的前缀树,用于快速匹配路由。
maxParams, maxSections: 分别用于限制路由参数和路径段的最大数量,提高路由匹配的效率。
trustedProxies, trustedCIDRs: 分别用于存储信任的代理服务器列表和 CIDR,用于解析客户端真实 IP 地址。


现在终于可以看刚开始的例子了:
方法体

defer func() { debugPrintError(err) }()

这行代码使用了 defer 关键字来确保在 Run 方法结束前执行 debugPrintError(err)。这是一个错误处理的模式,用于在方法退出时打印出现的任何错误。

if engine.isUnsafeTrustedProxies() {...
}

这段代码检查是否信任了所有代理,如果是,将打印一个安全警告。这是因为过度信任代理可能导致安全问题,尤其是在解析客户端 IP 地址时.

address := resolveAddress(addr)

调用 resolveAddress 函数解析提供的地址参数 addr。如果 addr 为空,则函数返回默认的监听地址(例如 :8080)。
这个函数也可以做一个解读:
这个函数实际上是gin框架里面的函数,一开始我还以为是net/http里的。
这个函数只是一个辅助函数,用于处理启动服务器时提供的地址参数。如果没有提供参数,这个函数会返回一个默认值比如(:8080),这意味着服务器将监听网络接口上的8080端口。

func resolveAddress(addr []string) string {switch len(addr) {case 0:if port := os.Getenv("PORT"); port != "" {debugPrint("Environment variable PORT=\"%s\"", port)return ":" + port}debugPrint("Environment variable PORT is undefined. Using port :8080 by default")return ":8080"case 1:return addr[0]default:panic("too many parameters")}
}

它的作用是根据提供的参数来解析服务器应当监听的地址。这个函数处理了几种不同的情况,以决定最终的监听地址。以下是对这个函数行为的逐条解读:
我这里一开始有一个疑问的,哪里来的切片,问题出现在这里:
Run(addr …string),看看里面的参数处理
addr …string 在 Go 语言中使用的是一种称为“变参函数”(Variadic Function)的特性,它允许你传递零个或多个 string 类型的参数给函数。这种参数在函数内部被处理为一个 string 类型的切片(slice)
解释
… 符号:这个符号放在类型之前,表示该函数接受任意数量的该类型的参数。在这个例子中,addr …string 表示 Run 函数可以接受任意数量的 string 参数。
在函数内部
在 Run 函数的内部,addr 会被当作一个 []string 切片来处理。这意味着你可以对它执行所有切片操作,如 len(addr) 来获取传入参数的数量,或通过索引访问各个参数等。
直接举个例子:
不传递任何参数:Run(),这时 addr 作为一个空的 string 切片。
传递一个参数:Run(“:8080”),这时 addr 包含一个元素 “:8080”。
传递多个参数:Run(“:8080”, “:8081”),这时 addr 包含两个元素 “:8080” 和 “:8081”。
总的来说就是会把字符串转切片。它并不是像我之前理解的那样一个一个的加,而是针对的传递多个参数这种情况。

参数
addr []string: 一个字符串切片,包含了可能被用来指定监听地址的参数。
函数逻辑
1.没有提供参数 (len(addr) == 0):
首先函数会检查环境变量 PORT 是否被设置。如果设置了,函数将使用这个环境变量的值作为端口号,并返回一个地址字符串,格式为 “:” + port,意味着监听所有网络接口上的这个端口。
如果环境变量 PORT 没有被设置,函数会通过打印一条调试信息来通知使用默认的 :8080 端口,并返回 “:8080” 作为监听地址。

提供了一个参数 (len(addr) == 1):

如果 addr 切片中只有一个元素,函数直接返回这个元素作为监听地址。这允许直接通过参数来指定完整的监听地址,例如 “127.0.0.1:8080” 或 “:8080”。
提供了多于一个参数 (len(addr) > 1):

如果 addr 切片中包含多于一个元素,函数将通过 panic 抛出一个错误,提示“too many parameters”。这是为了避免在启动服务器时出现参数上的混淆,确保启动行为的明确性。

debugPrint("Listening and serving HTTP on %s\n", address)

打印一条消息,告知正在监听的 HTTP 地址。


err = http.ListenAndServe(address, engine.Handler())这个函数的内部实现:
func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}return server.ListenAndServe()
}

使用标准库 net/http 的 ListenAndServe 函数来监听解析出的地址并启动 HTTP 服务器。engine.Handler() 返回一个处理 HTTP 请求的处理器,它将被用于处理所有到达监听地址的 HTTP 请求。
继续解读:
addr: 一个字符串,指定服务器监听的地址和端口。例如,“:8080” 表示监听本机的 8080 端口。

handler: 实现了 http.Handler 接口的对象。http.Handler 是一个接口,要求实现一个方法 ServeHTTP(ResponseWriter, *Request)。这个方法用于处理所有的 HTTP 请求。
*ServeHTTP(ResponseWriter, Request)
是 Go 语言 net/http 包中定义的 http.Handler 接口的唯一方法。任何想要处理 HTTP 请求的对象都需要实现这个接口。这个方法提供了处理 HTTP 请求并生成响应的基础设施。

w http.ResponseWriter: http.ResponseWriter 是一个接口,提供了向客户端发送响应的方法。通过这个接口,你可以写入响应体(Write 方法),设置响应状态码(WriteHeader 方法),以及添加或修改响应头部(Header 方法返回一个可以修改的 http.Header 对象)。

*r http.Request: *http.Request 是一个指向 http.Request 结构体的指针,它包含了这个 HTTP 请求的所有信息,比如 URL、头部、查询参数、表单数据、请求体等。通过这个参数,你可以读取和分析客户端发送的请求。

这个方法的工作流程:
当 HTTP 服务器接收到一个请求时,它会构造一个 http.Request 对象,并找到合适的处理器来处理这个请求。然后,它调用这个处理器的 ServeHTTP 方法,传入一个 http.ResponseWriter 和 *http.Request 作为参数:

读取请求: *使用 r http.Request 来获取请求的详细信息,比如请求的路径、方法、头部、请求体等。
处理请求: 根据请求的内容,执行相应的逻辑,可能会涉及读取数据库、执行计算、调用其他服务等操作。
发送响应: 使用 w http.ResponseWriter 向客户端发送响应。这包括设置响应状态码、响应头部以及写入响应体。

http.ListenAndServe内部实现:
内部首先会创建一个 http.Server 结构体实例,然后调用这个实例的 ListenAndServe 方法来启动服务器。

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

创建 http.Server 实例: 创建一个 http.Server 结构体实例,其中 Addr 字段设置为函数参数提供的地址,Handler 字段设置为处理请求的处理器。
调用 ListenAndServe: 接下来,调用这个 http.Server 实例的 ListenAndServe 方法。这个方法会让服务器开始监听指定的地址,当有 HTTP 请求到达时,使用提供的处理器来处理这些请求。

再次深挖这个函数

func (srv *Server) ListenAndServe() error {if srv.shuttingDown() {return ErrServerClosed}addr := srv.Addrif addr == "" {addr = ":http"}ln, err := net.Listen("tcp", addr)if err != nil {return err}return srv.Serve(ln)
}

这个函数用于启动一个 HTTP 服务器并监听指定的地址。

func (srv *Server) ListenAndServe() error {

关于函数:
*srv Server: *Server 表示这是 Server 结构体的一个方法,其中 srv 是对当前 Server 实例的引用。
返回类型是 error,如果服务器启动成功并运行,则正常情况下不会返回(因为它会一直运行直到被关闭)。如果启动过程中遇到错误,将返回相应的错误。

函数体

if srv.shuttingDown() {return ErrServerClosed
}

这段代码检查服务器是否已经在关闭过程中。如果是,则返回 ErrServerClosed 错误。这是为了防止在服务器关闭后再次尝试启动它。

addr := srv.Addr
if addr == "" {addr = ":http"
}

这里设置要监听的地址。如果 Server 实例的 Addr 字段为空,将默认使用 “:http”。“:http” 是一个特殊的地址,表示使用 HTTP 默认的端口号(80)监听所有网络接口。

ln, err := net.Listen("tcp", addr)
if err != nil {return err
}

使用 net.Listen 函数尝试监听上面确定的地址。这个函数第一个参数是网络类型 “tcp”,第二个参数是地址。如果监听成功,net.Listen 返回一个 net.Listener 接口的实例 ln,用于接受来自客户端的连接。如果监听失败(例如,地址已被占用),将返回错误。

return srv.Serve(ln)

调用 Server 实例的 Serve 方法,并将之前创建的监听器 ln 作为参数。Serve 方法会启动一个循环,接受客户端的连接请求,并为每个请求启动一个 goroutine 来处理。这个方法通常会一直运行,直到服务器被关闭。如果 Serve 方法因为某些原因返回(通常是监听器遇到错误),那么 ListenAndServe 也会返回相应的错误。

func (srv *Server) Serve(l net.Listener) error {if fn := testHookServerServe; fn != nil {fn(srv, l) // call hook with unwrapped listener}origListener := ll = &onceCloseListener{Listener: l}defer l.Close()if err := srv.setupHTTP2_Serve(); err != nil {return err}if !srv.trackListener(&l, true) {return ErrServerClosed}defer srv.trackListener(&l, false)baseCtx := context.Background()if srv.BaseContext != nil {baseCtx = srv.BaseContext(origListener)if baseCtx == nil {panic("BaseContext returned a nil context")}}var tempDelay time.Duration // how long to sleep on accept failurectx := context.WithValue(baseCtx, ServerContextKey, srv)for {rw, err := l.Accept()if err != nil {if srv.shuttingDown() {return ErrServerClosed}if ne, ok := err.(net.Error); ok && ne.Temporary() {if tempDelay == 0 {tempDelay = 5 * time.Millisecond} else {tempDelay *= 2}if max := 1 * time.Second; tempDelay > max {tempDelay = max}srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)time.Sleep(tempDelay)continue}return err}connCtx := ctxif cc := srv.ConnContext; cc != nil {connCtx = cc(connCtx, rw)if connCtx == nil {panic("ConnContext returned nil")}}tempDelay = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew, runHooks) // before Serve can returngo c.serve(connCtx)}
}

我直接解读:
初始化部分:

if fn := testHookServerServe; fn != nil {fn(srv, l) // call hook with unwrapped listener
}

这部分代码检查是否存在一个名为 testHookServerServe 的测试钩子(一个可能在测试中使用的全局变量)。如果这个钩子被设置了(不为 nil),则会使用当前的服务器实例 (srv) 和监听器 (l) 调用它。这主要用于内部测试,允许在实际处理连接之前拦截和修改服务器的行为。

origListener := l
l = &onceCloseListener{Listener: l}
defer l.Close()

这段代码首先保存原始监听器的引用,然后用一个 onceCloseListener 包装原始监听器,确保它只能被关闭一次。通过 defer 语句确保在 Serve 方法结束时关闭监听器,释放相关资源。

if err := srv.setupHTTP2_Serve(); err != nil {return err
}

这里尝试为服务器设置 HTTP/2 支持。如果设置失败,例如因为环境不支持 HTTP/2,方法会返回错误

if !srv.trackListener(&l, true) {return ErrServerClosed
}
defer srv.trackListener(&l, false)

这段代码在服务器的内部跟踪结构中注册当前的监听器。如果服务器已经关闭,trackListener 会返回 false,并且方法会返回 ErrServerClosed 错误。使用 defer 确保在方法退出时取消对监听器的跟踪。

上下文准备

baseCtx := context.Background()
if srv.BaseContext != nil {baseCtx = srv.BaseContext(origListener)if baseCtx == nil {panic("BaseContext returned a nil context")}
}

这段代码创建了一个基础上下文 baseCtx。如果服务器的 BaseContext 函数被设置了,它会被调用来生成一个针对当前监听器的自定义上下文。如果 BaseContext 返回 nil,则触发 panic,因为预期 BaseContext 应总是返回有效的上下文。

接受连接的循环

var tempDelay time.Duration // how long to sleep on accept failure
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {rw, err := l.Accept()if err != nil {...continue}connCtx := ctxif cc := srv.ConnContext; cc != nil {connCtx = cc(connCtx, rw)if connCtx == nil {panic("ConnContext returned nil")}}tempDelay = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew, runHooks)go c.serve(connCtx)
}

这是主要的循环,服务器在这里不断接受新的连接。每次尝试接受连接时可能遇到错误,比如因为网络问题导致的临时错误。如果发生了临时错误,服务器会等待一个延迟后再次尝试接受连接。这个延迟会在每次失败后增加,直到达到最大值。
如果接受连接成功,将创建一个新的连接上下文 connCtx,可能通过调用 ConnContext 函数进行定制。然后为每个接受的连接创建一个新的连接对象 (srv.newConn) 并调用其 serve 方法在新的 goroutine 中处理连接。
这个循环是无限的,直到服务器关闭或遇到非临时错误为止。。


返回值是 error 类型,如果服务器正常启动,则返回 nil。如果启动过程中出现错误,如端口被占用,将返回一个错误对象。

最后,方法返回。如果在监听过程中发生错误(例如,地址已被占用),err 将被赋值,并且通过之前 defer 的调用打印出来。


关于处理函数

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

这个Handler是接口,这个接口实现了一个唯一方法:

type Handler interface {ServeHTTP(ResponseWriter, *Request)
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := engine.pool.Get().(*Context)c.writermem.reset(w)c.Request = reqc.reset()engine.handleHTTPRequest(c)engine.pool.Put(c)
}

首先解释engine.pool,这个是sync.Pool类型的字段,用于高效的重用对象。以减少垃圾回收(GC)的压力。sync.Pool 是 Go 语言标准库中提供的一种用于存储和重用临时对象的机制,它可以显著减少内存分配和回收的开销,特别是在高并发环境下。

它主要提供两个方法Get() 和 Put(obj interface{})。sync.Pool 的使用不需要关心内部如何存储对象,只需要知道这两个方法的用途:

Get() interface{}: 从 Pool 中获取一个对象。如果 Pool 中没有可用对象,则会调用 Pool 在初始化时指定的 New 函数来创建一个新对象。
Put(obj interface{}): 将一个对象放回 Pool 中,使其可以被后续的 Get() 调用重用。放入 Pool 的对象应该是可以安全重用的。

Gin 中的用途
在 Gin 框架中,engine.pool 通常用于重用 Context 对象。每个 HTTP 请求都会创建一个 Context 对象,该对象包含了处理该请求所需的所有信息和方法。由于 HTTP 请求非常频繁,不断地创建和销毁 Context 对象会给垃圾回收带来压力,影响性能

通过使用 sync.Pool 来重用 Context 对象,Gin 可以显著减少内存分配的次数,提高性能。具体做法是:

当 Gin 处理一个新的 HTTP 请求时,会通过 engine.pool.Get() 获取一个 Context 对象。如果 pool 中没有可用的对象,则会创建一个新的 Context 对象。
请求处理完成后,Gin 会在发送响应之前调用 engine.pool.Put(context) 将这个 Context 对象放回 pool 中,使其可以被后续的请求重用。
这种模式是高性能 HTTP 服务器常用的优化技巧之一。

小提示,在gin框架中,上下文Context结构体就是爹。它封装了每个 HTTP 请求的所有相关信息(字段)和处理过程所需的方法。通过 Context 对象,你可以访问请求数据(如参数、头部、体)、控制响应(设置状态码、发送数据)以及调用中间件或路由处理函数。

Request: 指向 http.Request 的指针,包含了原始的 HTTP 请求信息,如 URL、头部、查询参数、表单数据等。Writer: ResponseWriter 类型,用于构造和发送 HTTP 响应。它提供了设置响应状态码、写入响应头部和正文的方法。Params: 路由参数的集合,允许你通过名称获取动态路由参数的值。handlers: 处理当前请求的 HandlersChain,即一系列的处理函数。Gin 通过它来实现中间件和最终的路由处理函数。index: 当前正在执行的处理函数在 handlers 中的索引,控制着处理函数链的执行过程。Keys: 一个 map[string]interface{} 类型,用于在中间件和处理函数之间传递数据。Errors: 存储在处理请求过程中发生的错误。请求数据处理
Context 提供了多种方法来获取请求的数据:Query: 获取 URL 的查询参数。
DefaultQuery: 获取查询参数,如果指定的参数不存在,则返回默认值。
PostForm: 获取表单数据。
Param: 获取动态路由参数。
BindJSON: 将请求体中的 JSON 数据绑定到一个 Go struct。
响应设置
通过 Context,你可以轻松地设置响应数据和状态:JSON: 发送 JSON 格式的响应。
HTML: 发送 HTML 格式的响应。
Status: 设置响应的 HTTP 状态码。
Header: 设置响应头部。
中间件和路由处理
Next: 调用此方法会继续执行下一个处理函数。
Abort: 停止调用链中剩余的处理函数,通常用于中间件中条件不满足时提前终止请求处理。

现在看这个源码就是砍瓜切菜。

然后继续往下走,最重要的就是下面这个请求处理函数,用于处理每个HTTP请求:
engine.handleHTTPRequest©
追一下源码:

func (engine *Engine) handleHTTPRequest(c *Context) {httpMethod := c.Request.MethodrPath := c.Request.URL.Pathunescape := falseif engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {rPath = c.Request.URL.RawPathunescape = engine.UnescapePathValues}if engine.RemoveExtraSlash {rPath = cleanPath(rPath)}// Find root of the tree for the given HTTP methodt := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}root := t[i].root// Find route in treevalue := root.getValue(rPath, c.params, c.skippedNodes, unescape)if value.params != nil {c.Params = *value.params}if value.handlers != nil {c.handlers = value.handlersc.fullPath = value.fullPathc.Next()c.writermem.WriteHeaderNow()return}if httpMethod != http.MethodConnect && rPath != "/" {if value.tsr && engine.RedirectTrailingSlash {redirectTrailingSlash(c)return}if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {return}}break}if engine.HandleMethodNotAllowed {for _, tree := range engine.trees {if tree.method == httpMethod {continue}if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {c.handlers = engine.allNoMethodserveError(c, http.StatusMethodNotAllowed, default405Body)return}}}c.handlers = engine.allNoRouteserveError(c, http.StatusNotFound, default404Body)
}

下面逐段解读:
请求路径和解码设置

httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {rPath = c.Request.URL.RawPathunescape = engine.UnescapePathValues
}if engine.RemoveExtraSlash {rPath = cleanPath(rPath)
}

获取请求方法和路径:首先,代码获取了请求的 HTTP 方法和路径(.Method 和 .Path)。
选择使用 RawPath:如果 engine.UseRawPath 被设置为 true 且 RawPath 不为空,那么将使用 RawPath 作为请求路径,这可能是因为开发者希望处理特定的编码情况。
unescape:作用是指示在路由匹配和参数提取过程中是否需要对 URL 路径进行解码。尽管在提供的代码段中没有直接看到 unescape 的使用,但它在实际的路由匹配和参数处理逻辑中起到了关键作用,特别是在处理那些需要根据配置决定是否解码路径参数的场景中。
路径解码设置:如果选择使用 RawPath,根据 engine.UnescapePathValues 决定是否对路径参数进行解码。
移除多余的斜杠:如果 engine.RemoveExtraSlash 被设置为 true,那么将调用 cleanPath(rPath) 函数来移除路径中的多余斜杠。

如果看的不舒服,说明这些字段要学习一下:
c.Request.Method
c.Request 是一个 *http.Request 对象,它是 Go 标准库中定义的,表示一个 HTTP 请求
.Method 字段包含了 HTTP 请求的方法(如 “GET”、“POST” 等)。

c.Request.URL.Path
c.Request.URL 是一个 *url.URL 对象,代表解析后的 URL。
.Path 字段包含了 URL 的路径部分,这是经过解码的路径,比如 /user/john。

c**.Request.URL.RawPath**
.RawPath 也是 *url.URL 对象的字段,它包含未经解码的原始路径。这在处理某些特殊字符时很有用,因为解码后的路径可能与原始路径不完全相同。

engine.UseRawPath
UseRawPath 是 Engine 结构体的一个字段,表示是否应该使用 RawPath 而不是 Path 来获取请求的路径。这个字段允许开发者根据需要选择使用原始路径还是解码后的路径。

engine.UnescapePathValues
UnescapePathValues 是 Engine 结构体的另一个字段,当设置为 true 时,表示在路由匹配和参数提取过程中,应该对路径中的百分号编码(URL 编码)的值进行解码。这对于需要在路径参数中使用特殊字符的情况很有用。

engine.RemoveExtraSlash
RemoveExtraSlash 是 Engine 结构体的字段,指示 Gin 在处理请求路径时是否应该移除多余的斜杠。例如,将 //user//john/ 处理为 /user/john。

路由匹配

//通过engine.trees拿到所有的路由树,这个切片装了所有的路由树。
t := engine.trees
//然后开始遍历所有的路由树
for i, tl := 0, len(t); i < tl; i++ {//先匹配路由树的方法,因为路由树是按不同方法就是不同的路由树if t[i].method != httpMethod {//不对的话就跳过,因为方法都对不上那就显然不是这颗树。就可以跳过了。continue}//这里就是匹配成功了,然后把这颗路由树的根节点取出来。root := t[i].root//这个方法就是实现在当前路由树种查找与请求rpath匹配的路由。这个方法返回了一个结构体。包含与匹配路由相关的参数: (params)、处理函数 (handlers) 和完整路径 (fullPath)。//关于这个函数的参数://第一个就是要进行查找的路由//c.params 用于存放从路径中解析出的参数。//c.skippedNodes 是用于内部路由匹配优化的。//unescape 指示是否需要对路径参数进行解码。value := root.getValue(rPath, c.params, c.skippedNodes, unescape)if value.params != nil {//如果找到路由包含的参数,则将这些参数赋值给当前请求的Context。c.Params = *value.params}//如果找到匹配的路由并且存在对应的处理函数,则更新当前Context的处理函数链,设置当前请求的完整路径,然后再调用c.Next执行处理函数链。再调用c.writermem.WriteHeaderNow() 立即写入响应头部,然后返回以结束处理流程。if value.handlers != nil {c.handlers = value.handlersc.fullPath = value.fullPathc.Next()c.writermem.WriteHeaderNow()return}...
}

遍历所有的路由树 (engine.trees),查找与当前请求方法匹配的树。
使用请求路径 rPath 来在找到的路由树中查找匹配的路由节点。
如果找到匹配的路由,更新上下文 c 的参数、处理函数链 (handlers) 和完整路径 (fullPath),然后执行处理函数链,并立即写入响应头部。

看不懂就看这个:
engine.trees
engine.trees 是一个存储了不同 HTTP 方法(如 GET、POST)对应的路由树的切片。每个路由树负责管理一个特定 HTTP 方法的所有路由规则。这种数据结构使得基于请求方法和路径的路由查找变得非常高效。
在 Gin 中,路由树是一种优化的数据结构,用于快速匹配 URL 路径到相应的处理函数。每个树节点可能代表 URL 路径的一部分,并且树中的路径可能包含参数(如 /user/:name 中的 :name)。
它的源码还有,为什么会是这样:

type methodTree struct {method stringroot   *node
}type methodTrees []methodTree

methodTree 和 methodTrees 是 Gin 框架用于构造和存储路由信息的数据结构。

methodTree
methodTree 结构体代表了一个特定 HTTP 方法(如 GET、POST)的路由树。这个结构体包含两个字段

method: 字符串类型,表示 HTTP 方法。这告诉 Gin 这棵树包含的所有路由都是为哪种 HTTP 方法定义的,例如 “GET” 或 “POST”。

root: 指向 node 类型的指针。这个 node 是当前方法路由树的根节点。在这棵树中,每个节点可能代表路由路径的一部分,并且树中的路径可能包含参数(如 /user/:name 中的 :name)。

理解总结:由于不同的方法那就对应着不同的路由树,那么这个切片中每一个元素就是代表了一棵路由树,然后从内部结构体来看,里面两个字段,存了这棵路由树的方法名和路由树的根结点

这种结构使得基于请求方法和路径的高效路由匹配成为可能。每当一个新路由被添加到 Gin 应用中时,它会被插入到相应 HTTP 方法的 methodTree 的路由树中。

然后就是工作流程
当 Gin 收到一个 HTTP 请求时,它首先检查请求的方法。然后,在 methodTrees 中查找与该方法相匹配的 methodTree。找到后,Gin 使用请求的路径在对应的 methodTree 的路由树中进行匹配,以找到最合适的处理函数。

如果请求的路径与路由树中的某个路由模式匹配,对应的处理函数就会被执行。如果没有找到匹配的路由,Gin 将返回 404 Not Found 错误响应(或者如果配置了处理 405 Method Not Allowed 的逻辑,也可能返回 405)。


里面的c.Next()我也要说说怎么实现的:

func (c *Context) Next() {c.index++for c.index < int8(len(c.handlers)) {c.handlers[c.index](c)c.index++}
}

这个方法的主要作用是控制中间件和处理函数的执行流程。c.Next() 控制着当前请求的处理函数链 (c.handlers) 的执行。这个链就相当于是以恶个数组,然后数组里面存的是函数,在 Gin 中,每个请求都有一个与之关联的处理函数链,这个链表可能包含多个中间件和一个最终的路由处理函数。

递增 c.index:c.index 是一个 int8 类型的字段,表示当前正在执行的处理函数在 c.handlers 中的索引。在调用 Next() 方法时,首先将 c.index 增加 1,以便从下一个处理函数开始执行。

执行处理函数链:for 循环遍历 c.handlers,只要 c.index 的值小于 c.handlers 的长度,就执行当前索引对应的处理函数 c.handlersc.index。每执行完一个处理函数后,c.index 再次增加 1,移动到下一个处理函数。

执行流程控制
当在某个中间件或处理函数中调用 c.Next() 时,Gin 会继续执行当前请求的处理函数链中的下一个处理函数。

与此同时,还有两个函数非常的常用:
判断是否已经终止处理器调用。

func (c *Context) IsAborted() bool {return c.index >= abortIndex
}

这个函数用来终止处理器调用。

func (c *Context) Abort() {c.index = abortIndex
}

可以看到就是检查索引,终止操作时通过把索引直接改成上限。由于到了上限根据c.Next()的代码,到了上限就不会往后执行了。

还有一些相关的方法是终止处理器调用,并设置响应体。

func (c *Context) AbortWithStatus(code int) {c.Status(code)c.Writer.WriteHeaderNow()c.Abort()
}

其实看了内部的终止操作,还是基于C.Abort()来实现终止,这些只不过附加了一些可能用到的功能。

重定向和固定路径

if httpMethod != http.MethodConnect && rPath != "/" {if value.tsr && engine.RedirectTrailingSlash {redirectTrailingSlash(c)return}if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {return}
}

对于非 CONNECT 方法的请求,如果找到的路由节点建议进行尾部斜杠重定向 (value.tsr 为 true) 并且 engine.RedirectTrailingSlash 为 true,则执行重定向。
如果启用了固定路径重定向 (engine.RedirectFixedPath),且存在可以通过修正路径得到的匹配路由,则进行重定向。

处理 405 Method Not Allowed

if engine.HandleMethodNotAllowed {for _, tree := range engine.trees {if tree.method == httpMethod {continue}if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {c.handlers = engine.allNoMethodserveError(c, http.StatusMethodNotAllowed, default405Body)return}}
}

默认处理和 404 Not Found

c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)

如果没有匹配到任何路由,设置当前上下文的处理函数为全局的未找到路由的处理函数 (engine.allNoRoute),并返回 404 Not Found 错误。

总结,handleHTTPRequest 方法是 Gin 框架用于路由分发和请求处理的核心逻辑。它根据请求的路径和方法查找注册的路由,执行匹配的处理函数,或者根据配置执行重定向、返回 404 Not Found 或 405 Method Not Allowed 错误。


以上就是今天所看的源码,至少了解了这些内容,我个人感觉也没那么难受了。一开始看简直是眼花缭乱。

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

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

相关文章

5G端到端案例三:锚点基站侧5G连接与VOLTE专载建立流程冲突导致CSFB回落问题

1. 问题描述&#xff1a; NSA组网场景下&#xff0c;语音业务仍使用4G VoLTE方案&#xff0c;在拉网测试中&#xff0c;发现存在较多流程交叉导致的VOLTE接入失败的问题。 流程冲突时的空口信令表现为&#xff0c;终端添加SCG流程与语音专载流程冲突时&#xff0c;专有承载建…

重点媒体如何投稿?考核稿件投稿指南

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 机构组织&#xff0c;国企央企都需要定期将相关新闻投递到央媒&#xff0c;官媒&#xff0c;或者地方重点媒体中&#xff0c;那么如何进行投稿了&#xff0c;今天就与大家分享下。 央媒投…

vue-nextTick(nextTick---入门到离职系列)

官方定义 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法&#xff0c;获取更新后的 DOM。 个人理解 假设我们更改了某个 dom 元素内部的文本&#xff0c;而这时候我们想直接打印这个更改之后的文本是需要 dom 更新之后才会实现的。 小案例 <tem…

聊一聊EGO-Planner膨胀系数的大小对无人机避障飞行的影响

EGO-Planner简介 EGO-Planner作为业界知名的无人机轨迹规划算法&#xff0c;其优势在于能够在复杂环境中快速规划出安全、平滑且动态可行的飞行轨迹。在这个算法中&#xff0c;膨胀系数发挥着关键作用。它通过扩大障碍物的感知范围&#xff0c;提供额外的安全边距&#xff0c;…

NOIP2018-J-4-对称二叉树的题解

原题描述&#xff1a; 题目描述 时间&#xff1a;1s 空间&#xff1a;256M 一棵有点权的有根树如果满足以下条件&#xff0c;则被轩轩称为对称二叉树&#xff1a; 1. 二叉树&#xff1b; 2. 将这棵树所有节点的左右子树交换&#xff0c;新树和原树对应位置的结构相同且…

【深度学习】LoRA: Low-Rank Adaptation of Large Language Models,论文解读

文章&#xff1a; https://arxiv.org/abs/2106.09685 文章目录 摘要介绍LoRA的特点什么是低秩适应矩阵&#xff1f;什么是适应阶段&#xff1f;低秩适应矩阵被注入到预训练模型的每一层Transformer结构中&#xff0c;这一步是如何做到的&#xff1f; 摘要 自然语言处理的一个重…

计算机网络-网络互联与互联网(一)

1.常用网络互联设备&#xff1a; 1层物理层&#xff1a;中继器、集线器2层链路层&#xff1a;网桥、交换机3层网络层&#xff1a;路由器、三层交换机4层以上高层&#xff1a;网关 2.网络互联设备&#xff1a; 中继器Repeater、集线器Hub&#xff08;又叫多端口中继器&#xf…

图论(算法竞赛、蓝桥杯)--Dijkstra算法最短路

1、B站视频链接&#xff1a;D02 最短路 Dijkstra 算法_哔哩哔哩_bilibili 题目链接&#xff1a;【模板】单源最短路径&#xff08;弱化版&#xff09; - 洛谷 #include <bits/stdc.h> using namespace std; #define INF 2147483647 int n,m,s,a,b,c; const int N100010…

Redis的主从复制和哨兵模式

Redis的主从复制和哨兵模式 Redis集群搭建&#xff08;一主二从&#xff09;replication 主从复制配置文件 redis.confRedis主从复制工作原理全量复制增量复制redis主从复制策略 搭建集群 &#xff08;主从复制引入&#xff09; 哨兵模式概念哨兵配置文件 sentinel.conf哨兵配置…

ArcgisForJS如何使用ArcGIS Server发布的切片地图服务?

文章目录 0.引言1.准备海量地理数据2.ArcGIS Server发布切片地图服务3.ArcgisForJS使用ArcGIS Server发布的切片地图服务 0.引言 ArcGIS Server是一个由Esri开发的地理信息系统&#xff08;GIS&#xff09;服务器软件&#xff0c;它提供了许多功能&#xff0c;包括发布切片地图…

java面试设计模式篇

面试专题-设计模式 前言 在平时的开发中&#xff0c;涉及到设计模式的有两块内容&#xff0c;第一个是我们平时使用的框架&#xff08;比如spring、mybatis等&#xff09;&#xff0c;第二个是我们自己开发业务使用的设计模式。 面试官一般比较关心的是你在开发过程中&#…

挑战杯 基于卷积神经网络的乳腺癌分类 深度学习 医学图像

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度&#xff0c;召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…

Oracle迁移到mysql-导出mysql所有索引和主键

导出建库表索引等&#xff1a; [rootlnpg ~]# mysqldump -ugistar -pxxx -h192.168.207.143 --no-data -d lndb > lndb20230223-1.sql 只导出索引&#xff1a;参考&#xff1a;MYSQL导出现有库中的索引脚本_mysql 导出数据库所有表的主键和索引-CSDN博客 -- MYSQL导出现有…

TCP Keepalive 和 HTTP Keep-Alive

HTTP 的Keep-Alive 在 HTTP 1.0 中默认是关闭的&#xff0c;如果浏览器要开启 Keep-Alive&#xff0c;它必须在请求的包头中添加&#xff1a; Connection: Keep-Alive然后当服务器收到请求&#xff0c;作出回应的时候&#xff0c;它也添加一个头在响应中&#xff1a; Connec…

相信未来:技术的进步意味着重构

十年以来&#xff0c;呼声最高&#xff1a;AI、BigData、Cloud Service。 以本人看来&#xff0c;仅AI技术的进步和应用&#xff0c;整个软件行业&#xff0c;所有软件将被重构。 提醒&#xff1a;非大学毕业、非计算机及相关专业&#xff0c;在IT这个行业&#xff0c;特别是…

主流开发语言和开发环境:探索编程世界的基础

在当今这个快速发展的技术时代&#xff0c;软件开发已经成为推动创新的重要力量。无论是构建下一代应用、开发先进的算法还是创建复杂的系统&#xff0c;选择合适的编程语言和开发环境都是至关重要的。在本文中&#xff0c;我们将探讨当前流行的几种主流开发语言以及它们常用的…

MATLAB使用绘图plot制作动态GIF

文章目录 1 前言2 DemoDemo 1 - 不使用函数Demo 2 - 使用函数 1 前言 在PPT展示或者博客创作中&#xff0c;有时需要插入动态图如GIF&#xff0c;来演示算法效果或者结果。在MATLAB中&#xff0c;可以通过一些代码&#xff0c;将绘图plot转化为动态的GIF。 其大致方法为&…

【MySQL】如何理解事务

一、引出事务 假设我们有一张用户表&#xff0c;如图所示&#xff1a; 我们现在的需求是&#xff1a;小红向小明转账100块 那么我们可以执行如下指令&#xff1a; update user set salary salary-100 where id1;//第一条语句小红转一百 update user set salary salary100 …

学习使用在mysql中查询指定字段字符串包含多个字符串的方法

学习使用在mysql中查询指定字段字符串包含多个字符串的方法 使用LIKE关键字使用REGEXP关键字使用FIND_IN_SET函数使用INSTR函数和AND关键字 使用LIKE关键字 SELECT * FROM table_name WHERE column_name LIKE %string1% AND column_name LIKE %string2%;使用LIKE关键字&#x…

RabbitMQ(一):消息队列MQ

目录 1 消息队列MQ1.1 MQ简介1、什么是MQ2、MQ的优势流量削峰应用解耦异常处理数据分发分布式事务 3、消息中间件的弊端4、常用的MQ 1.2 MQ中几个基本概念1.3 MQ的通信模式1.4 消息的发布策略1.5 常用消息中间件协议1、AMQP协议2、MQTT协议3、OpenMessage协议4、kafaka协议 1 消…