Go 源码之 gin 框架
Go 源码之 gin 框架
go源码之gin - Jxy 博客
一、总结
-
gin.New()初始化一个实例:gin.engine,该实例实现了http.Handler接口。实现了ServeHTTP方法
-
注册路由、注册中间件,调用addRoute将路由和中间件注册到 methodTree 前缀树(节省空间,搜索快)下,methodTree 的非叶子节点是相同 method 的 url 的最长公共前缀字符串,叶子节点是完整的 url 路径
-
http请求会执行ServeHTTP方法,内部会根据请求的 method 和 url 到前缀树 methodTree 中匹配 url,然后遍历 handlers 数组,依次执行c.next()执行,可以通过c.Abort()进行中断
-
流程:
-
启动:
初始化engine;初始化一个长度为 9 的 methodTrees 数组;调用 addRoute 注册全局中间件、路由到 methodTree 数组中
-
处理:
gin.engine 实现了 http.handler 接口,实现了 ServeHTTP(ResponseWriter, *Request),所有的请求都会经过 ServeHTTP 处理;
ServeHTTP 方法:从 methodTrees 中找到本次请求的 httpMethod 的 Tree–>根据请求Url Path 找到Tree下对应的节点nodeValue(包含了中间件handler)—> 执行c.Next() 依次执行handler,,handler内部 可以调用c.JSON等往响应的http写入数据
-
-
注意点:
- 路由中间件的执行顺序和添加顺序一致,遵循先进先出规则
- c.JSON()等是http请求的末端函数,如果要添加后置拦截器,需要在此之前执行c.Next()即可, c.Abort()为终止执行后续的中间件
-
使用 sync.Pool 来复用上下午对象
-
gin 底层依旧是依赖 net/http 包,本质上是一个路由处理器,实现了 http.handler 接口,实现了 ServeHTTP
-
维护了 method 数组,每个元素是一个 radix 树(压缩前缀树)
-
gin 的中间件是使用切片实现的,添加中间件也就是切片追加元素的过程,中间件按追加先后顺序依次执行
-
gin 允许为不同的路由组添加不同的中间件
-
路由组本质上是一个模版,维护了路径前缀、中间件等信息,让用户省去重复配置相同前缀和中间件的操作
-
新路由组继承父路由组的所有处理器
-
如果上下文需要并发处理使用,需要使用上下文副本copy
二、源码
(一)engine结构
// Engine的实例,包括了路由组,路由树,中间件和其他等一系列配置
type Engine struct {// 路由组RouterGroup// 如果当前路径无法匹配,但存在带有(不带有)尾斜杠的路径处理程序,RedirectTrailingFlash将启用自动重定向// 例如,如果请求了/foo/,但只有/foo的路由存在,则客户端将被重定向到/foo// GET请求的http状态代码为301,所有其他请求方法的http状态为307。RedirectTrailingSlash bool// RedirectFixedPath如果启用,如果没有为其注册句柄,则路由器将尝试 修复 当前请求路径。// 首先删除像../或//这样的多余路径元素。然后,路由器对清理后的路径进行不区分大小写的查找。如果可以找到该路由的句柄,则路由器对GET请求使用状态代码301,// 对所有其他请求方法使用状态代码307,重新定向到正确的路径。例如/FOO和/..//FOO可以重定向到/FOO。// RedirectTrailingFlash独立于此选项。RedirectFixedPath bool// HandleMethodNotAllowed如果启用,则如果当前请求无法路由,则路由器会检查当前路由是否允许其他方法。// 如果是这种情况,则使用“Method Not Allowed”和 HTTP状态代码405 回答请求。// 如果不允许其他方法,则将请求委托给NotFound处理程序。HandleMethodNotAllowed bool// ForwardedByClientIP(如果启用),将从与存储在“(*gin.Engine).RemoteIPHeaders”中的标头匹配的请求标头解析客户端IP。// 如果未提取任何IP,它将返回到从“(*gin.Context).Request.Remoddr”获取的IP。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(如果启用),url.RawPath将用于查找参数。UseRawPath bool// UnescapePathValues如果为true,则路径值将被取消转义。// 如果UseRawPath为false(默认情况下),则UnescapePathValues实际上为true,// 作为url。将使用路径,该路径已未被覆盖。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.Delims// https://cloud.tencent.com/developer/article/1580456secureJSONPrefix string HTMLRender render.HTMLRender// FuncMap是定义从名称到函数的映射的映射类型。// 每个函数必须有一个返回值或两个返回值,其中第二个返回值具有类型错误。// 在这种情况下,如果在执行期间第二个(error)参数的计算结果为非nil,则执行终止,Execute返回该错误。// FuncMap在“text/template”中具有与FuncMap相同的基本类型,复制到此处,因此客户端无需导入“text/template”。FuncMap template.FuncMap trees methodTrees // 请求的method数组,每个元素是一个前缀树}
(二)ServeHTTP(核心处理http方法)
// 所有的http请求最终都会走这里
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := engine.pool.Get().(*Context) // 从池里取一个contextc.writermem.reset(w) // 重置c.Request = req // 赋值请求c.reset() // 重置数据engine.handleHTTPRequest(c) // 核心处理函数engine.pool.Put(c) // 放回缓冲池
}
// 核心处理函数
func (engine *Engine) handleHTTPRequest(c *Context) {// 请求的Method类型,如GET等httpMethod := c.Request.Method// 请求的路径rPath := 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)}// 从methodTree中匹配method和请求urlt := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}root := t[i].root// 从前缀树methodTree中匹配到叶子节点valuevalue := 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() // 依次从handlers数组中FIFO执行中间件c.writermem.WriteHeaderNow() // 最终写入http响应流responseWriter中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)
}
(三)methodTrees
在New() 中会初始化容量为9,匹配9个http.Method
每个节点都是压缩前缀树:将相同请求method的url计算出最长公共前缀字符串然后作为子节点
type methodTrees []methodTree// 压缩前缀树,存储了http.Method的请求路径
type methodTree struct {method stringroot *node
}
type node struct {path string // 存储:共同的最长前缀字符indices stringwildChild boolnType nodeTypepriority uint32children []*node // 有共同的最长前缀字符path的url pathhandlers HandlersChainfullPath string // 叶子节点存储的是完整的请求路径
}
// 构建树的函数
func addRoute(){}
r := gin.Default()r.Use(gin.Recovery(), gin.Logger())r.GET("/user/GetUserInfo", func(context *gin.Context) {})r.GET("/user/GetManyUserInfo", func(context *gin.Context) {})r.Run(":9091")
(四)context结构
// gin.Context是gin框架中最重要的一部分
type Context struct {writermem responseWriter // 响应的数据流Request *http.Request // 请求句柄Writer ResponseWriter // 响应的Writerhandlers HandlersChain // 中间件handler数组index int8 // handler数组的下标,表示已经执行的下标fullPath string // 完整的url路径engine *Engine
}
(五)RouterGroup
// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
// RouterGroup在内部用于配置路由器,RouterGroup与前缀和处理程序(中间件)数组相关联。
type RouterGroup struct {Handlers HandlersChain // 中间件handlerbasePath string // 基础路径engine *Engine root bool // 是否是根节点
}
(六)c.GET()
// 调用了handler,httpMethod=http.MethodGet,其他什么c.POST等都是差不多
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {return group.handle(http.MethodGet, relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {// 计算出绝对路径absolutePath := group.calculateAbsolutePath(relativePath)// 将请求的handler和group的全局handler合并handlers = group.combineHandlers(handlers)// 添加路由到前缀树methodTree中group.engine.addRoute(httpMethod, absolutePath, handlers)return group.returnObj()
}
// 根据请求的method和path,构建前缀树methodTree
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {assert1(path[0] == '/', "path must begin with '/'")assert1(method != "", "HTTP method can not be empty")assert1(len(handlers) > 0, "there must be at least one handler")debugPrintRoute(method, path, handlers)// 从数组methodTrees中获取对应method的根节点root := engine.trees.get(method)if root == nil {// 不存在,常见根节点root = new(node)root.fullPath = "/"engine.trees = append(engine.trees, methodTree{method: method, root: root})}// 构建前缀树root.addRoute(path, handlers)// Update maxParamsif paramsCount := countParams(path); paramsCount > engine.maxParams {engine.maxParams = paramsCount}if sectionsCount := countSections(path); sectionsCount > engine.maxSections {engine.maxSections = sectionsCount}
}
(七)c.JSON
func (c *Context) JSON(code int, obj any) {c.Render(code, render.JSON{Data: obj})
}
// 将数据写入c.Writer,但是还没有响应http
func (c *Context) Render(code int, r render.Render) {c.Status(code)if !bodyAllowedForStatus(code) {r.WriteContentType(c.Writer)c.Writer.WriteHeaderNow()return}if err := r.Render(c.Writer); err != nil {panic(err)}
}
(八)c.Next()
非常巧妙的设计,这个设计可以用来暂停执行当前handler,先执行后面的handler,然后再执行当前handler后面的代码
// handlers是一个中间件执行函数的数组,[]HandlerFunc
func (c *Context) Next() {// index记录 已经执行到数组[]HandlerFunc的下标,// index++ 继续执行后面的handlerFuncc.index++ for c.index < int8(len(c.handlers)) {c.handlers[c.index](c)c.index++}
}
(九)c.Abort()
可以用来终止后面所有handler的执行,
// 这里将 c.index的值改了超级大,在c.Next()中会判断c.index<len(handler),从而达到终止handler执行的效果
func (c *Context) IsAborted() bool {return c.index >= abortIndex
}
三、常见问题
如何设置前置拦截器和后置拦截器
-
方法一:
利用 handler 的存储结构:所有的 handler 会按顺序添加到数数组 []HandlerFunc 中,执行的是按FIFO遍历执行,所有先添加的handler会先执行,也就是说越先添加的就是前置拦截器,越晚添加的就是后置拦截器
r := gin.Default()r.Use(gin.Recovery()) // 前置拦截器r.GET("/user/GetUserInfo", func(context *gin.Context) {}) // 中间执行函数r.Use(func(context *gin.Context) {}) // 后置拦截器
-
方法二
使用c.Next()方法,在handler函数内部,可以先执行一部分代码,然后执行c.Next(),会遍历执行后续的handler,当所有的handler结束后,在执行当前handler c.Next()之后的代码
r := gin.Default()r.Use(, func(c *gin.Context) {// 前置代码c.Next() // 执行所有handler// 后置代码}) r.GET("/user/GetUserInfo", func(context *gin.Context) {}) // 中间执行函数
有劳各位看官 点赞、关注➕收藏
,你们的支持是我最大的动力!!!
接下来会不断更新 golang
的一些底层源码及个人开发经验
(个人见解)!!!
同时也欢迎大家在评论区提问、分享您的经验和见解!!!
相关文章:
Go 源码之 gin 框架
Go 源码之 gin 框架 go源码之gin - Jxy 博客 一、总结 gin.New()初始化一个实例:gin.engine,该实例实现了http.Handler接口。实现了ServeHTTP方法 注册路由、注册中间件,调用addRoute将路由和中间件注册到 methodTree 前缀树(节…...
BM19 寻找峰值(二分查找)
import java.util.*; public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * param nums int整型一维数组 * return int整型*/public int findPeakElement (int[] nums) {// write code hereint lef…...
4.数组和切片【go】
数组是具有固定数量的元素的序列,而切片是对数组的一个连续片段的引用。切片是Go中常用的数据结构 数组(Array) 数组是一个具有固定长度且元素类型相同的序列。在Go中,数组的长度是其类型的一部分,因此[5]int和[10]int是不同的数组类型。数组的长度在声明时必须指定,并…...
Abaqus周期性边界代表体单元Random Sphere RVE 3D (Mesh)插件
插件介绍 Random Sphere RVE 3D (Mesh) - AbyssFish 插件可在Abaqus生成三维具备周期性边界条件(Periodic Boundary Conditions, PBC)的随机球体骨料及骨料-水泥界面过渡区(Interfacial Transition Zone, ITZ)模型。即采用周期性代表性体积单元法(Periodic Representative Vol…...
家庭记账本(源码+文档)
家庭记账本系统(小程序、ios、安卓都可部署) 文件包含内容程序简要说明含有功能项目截图客户端我的界面图表明细添加账单登录页明细注册页个人资料 后台管理用户管理后台登录页分类管理 文件包含内容 1、搭建视频 2、流程图 3、开题报告 4、数据库 5、参…...
深度学习评价指标(1):目标检测的评价指标
1. 简述 在计算机视觉/深度学习领域,每一个方向都有属于自己的评价指标。通常在评估一个模型时,只需要计算出相应的评价指标,便可以评估算法的性能。同时,所谓SOTA,皆是基于某一评价指标进行的评估。 接下来࿰…...
jmeter性能压测的标准和实战中会遇到的问题
1.性能标准建议 CPU 使用率:不超过 70% 内存使用率:不超过 70% 磁盘:%util到达80%严重繁忙 (os.disIO.filesystem.writeKbPS 每秒写入的千字节) 响应时间:95%的响应时间不超过8000ms 事务成功率:…...
6-82 求链式线性表的倒数第K项
给定一系列正整数,请设计一个尽可能高效的算法,查找倒数第K个位置上的数字。 输入格式: 输入首先给出一个正整数K,随后是若干非负整数,最后以一个负整数表示结尾(该负数不算在序列内,不要处理)。 输出格式: 输出倒数第K个位置上的数据。如果这个位置不存在,输出错误…...
CDH集群hive初始化元数据库失败
oracle数据库操作: 报错如下:命令 (Validate Hive Metastore schema (237)) 已失败 截图如下: 后台日志部分摘录: WARNING: Use “yarn jar” to launch YARN applications. SLF4J: Class path contains multiple SLF4J binding…...
【ESP32S3 Sense接入语音识别+MiniMax模型对话】
1. 前言 围绕ESP32S3 Sense接入语音识别MiniMax模型对话展开,首先串口输入“1”字符,随后麦克风采集2s声音数据,对接百度在线语音识别,将返回文本结果丢入MiniMax模型,进而返回第二次结果文本,实现语言对话…...
【Java初阶(七)】接口
❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ 🚚我的代码仓库: 33的代码仓库🚚 目录 1.前言2.接口2.1语法规则2.2接口使用2.3接口特性2.4实现多个接口2.5接口使用实例2.6Clonable接口和深拷贝 3.Object类3.1对象比较equals方法3.2hashcod…...
Mac OS上使用matplotlib库显示中文字体
文章目录 问题描述解决步骤参考文章 问题描述 如果我们想要使用matplotlib画图的话,可能会出现下面的这种warning: UserWarning: Glyph 24212 (\N{CJK UNIFIED IDEOGRAPH-5E94}) missing from current font.解决步骤 解决这个问题,可以按照下面的做法…...
IP种子是什么?理解和应用
在网络世界中,IP种子是一个广泛应用于文件共享和网络下载领域的概念。它是一种特殊的标识符,用于识别和连接到基于对等网络(P2P)协议的文件共享网络中的用户或节点。本文将深入探讨IP种子的含义、作用以及其在网络中的应用。 IP地…...
车载以太网AVB交换机 gptp透明时钟 5口 全千兆 SW1500
全千兆车载以太网交换机 一、产品简要分析 5端口千兆车载以太网交换机,包含4个通道的1000BASE-T1接口使用罗森博格H-MTD和泰科MATEnet双接口,1个通道1000BASE-T标准以太网(RJ45接口),可以实现车载以太网多通道交换,千兆和百兆车载…...
Can‘t connect to server on ‘localhost‘ (10061)
问题:电脑关机重启后,连接不上mysql了,报错信息如下:2002 - Cant connect to server on localhost (10061)解决办法:很大的原因是mysql服务没有启动,需要你重启一下mysql: 以管理员的身份运行cm…...
虹科Pico汽车示波器 | 免拆诊断案例 | 2018款东风风神AX7车发动机怠速抖动、加速无力
一、故障现象 一辆2018款东风风神AX7车,搭载10UF01发动机,累计行驶里程约为5.3万km。该车因发动机怠速抖动、加速无力及发动机故障灯异常点亮而进厂维修,维修人员用故障检测仪检测,提示气缸3失火;与其他气缸对调点火线…...
zookeeper如何管理客户端与服务端之间的链接?(zookeeper sessions)
zookeeper客户端与服务端之间的链接用zookeeper session表示。 zookeeper session有三个状态: CONNECTING, ASSOCIATING, CONNECTED, CONNECTEDREADONLY, CLOSED, AUTH_FAILED, NOT_CONNECTED(start时的状态) 1、CONNECTING 。 表明客户…...
【Java多线程】7——阻塞队列线程池
7 线程池 ⭐⭐⭐⭐⭐⭐ Github主页👉https://github.com/A-BigTree 笔记仓库👉https://github.com/A-BigTree/tree-learning-notes 个人主页👉https://www.abigtree.top ⭐⭐⭐⭐⭐⭐ 如果可以,麻烦各位看官顺手点个star~&#x…...
同步复位和异步复位的优缺点
同步复位 优点:能确保电路是100%的; 同步复位可以综合处更小的触发器; 可以保证复位只发生在有效时钟边沿,过滤掉复位信号毛刺; 内部逻辑产生的复位信号,采用同步复位可以有效过滤掉毛刺。 缺点:…...
Code Review(代码审查)
代码审查是软件开发生命周期的重要组成部分。它能显著提高开发人员的代码质量。 这个过程就像写一本书。作者写好了内容,出版社编辑对其进行了校审,所以没有出现任何错误,例如将“你”与“你的”混淆。这个案例中,代码审查是阅读…...
《拆解一切问题》如何成为解决难题的高手 - 三余书屋 3ysw.net
拆解一切问题:如何成为解决难题的高手 今天给大家分享的这本书叫做《拆解一切问题》,标题看起来确实有点虚,在没有读这本书之前,会让人感觉似乎只要读完学会书中的内容,就可以解决一切问题了。但事实上这种认识是误解…...
matlab——基于三维激光扫描点云的树冠体积计算方法
目录 一、算法原理1、原理概述2、参考文献二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法原理 1、原理概述 针对树冠形状不规则,树冠体积难以测量和计算的问题,提出一种基于三…...
如何在jupyter使用新建的虚拟环境以及改变jupyter启动文件路径。
对于刚刚使用jupyter的新手来说,经常不知道如何在其中使用新建的虚拟环境内核,同时,对于默认安装的jupyter,使用jupyter notebook命令启动 jupyter 以后往往默认是C盘的启动路径,如下图所示,这篇教程将告诉…...
Exception in thread “main“ com.fasterxml.jackson.databind.JsonMappingException:
问题:jaskson反序列化超出最大长度 Caused by: com.fasterxml.jackson.core.exc.StreamConstraintsException: String length (5043456) exceeds the maximum length (5000000) 场景:前端传递过大base64 原因: jaskon默认已经限制了最大长…...
第三十九章 保护与 IRIS 的 Web 网关连接
文章目录 第三十九章 保护与 IRIS 的 Web 网关连接配置 Web 网关的连接安全最低连接安全性(不推荐)简单的用户名/密码验证 第三十九章 保护与 IRIS 的 Web 网关连接 本页介绍用于保护从 Web Gateway 到 IRIS 的连接的选项。与 IRIS 的 Web 网关连接可以…...
java数据结构与算法刷题-----LeetCode127. 单词接龙
java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/123063846 文章目录 广度优先双分裂蛇 广度优先双分裂蛇 解题思路:时间复…...
pytorch中的torch.nn.Linear
torch.nn.Linear是pytorch中的线性层,应该是最常见的网络层了,官方文档:torch.nn.Linear。 torch.nn.Linear(in_features, out_features, biasTrue, deviceNone, dtypeNone)其中,in_features表示输入的维度;out_featu…...
03-MySQl数据库的-用户管理
一、创建新用户 mysql> create user xjzw10.0.0.% identified by 1; Query OK, 0 rows affected (0.01 sec) 二、查看当前数据库正在登录的用户 mysql> select user(); ---------------- | user() | ---------------- | rootlocalhost | ---------------- 1 row …...
知乎:多云架构下大模型训练,如何保障存储稳定性?
知乎,中文互联网领域领先的问答社区和原创内容平台,2011 年 1 月正式上线,月活跃用户超过 1 亿。平台的搜索和推荐服务得益于先进的 AI 算法,数百名算法工程师基于数据平台和机器学习平台进行海量数据处理和算法训练任务。 为了提…...
JWFD流程图转换为矩阵数据库的过程说明
在最开始设计流程图的时候,请务必先把开始节点和结束节点画到流程图上面,就是设计器面板的最开始两个按钮,先画开始点和结束点,再画中间的流程,然后保存,这样提交到矩阵数据库就不会出任何问题,…...
深圳做商城网站建设/公司网站搭建
tar -c 打tar包 -x 解tar包 -g 用gzip来压缩/解压缩文件 -j 使用‘bzip2’程序进行文件的压缩 -v 查看压缩过程 -f 指定文件转载于:https://blog.51cto.com/ceyes/706279...
免费的建设网站软件下载/提升关键词排名seo软件
方式1、 直接保存视频预览的数据帧 在预览方法中imgData数据就是视频帧 android默认的视频采集格式是NV21,(属于YUV420) 在onPreviewFrame中传进来的byte[] data即为NV21格式。 NV21 的存储格式是,以4 X 4 图片为例子 占用内…...
局域网内建立网站/百度公司电话是多少
MP4文件的基本单元是“box”,这些box既可以包括data,也可以包括metadata。MP4文件标准允许多种方式来组织data box和metadata box。将metadata放在data之前,客户端应用程序可以在播放video/audio之前获得更多的关于video/audio的信息…...
容桂做网站/seo包年优化
1 在windows下运行python程序1)从DOS命令行运行python脚本用python解释器来执行python脚本,在windows下面python解释器是python.exe,我的python文件安装在D:\Python27,在这个目录下可以看到python解释器。完整的命令应该是:python解释器 .p…...
企通互联的网站建设失败/合肥网站快速优化排名
古人云 “读万卷书,行万里路。” 书籍是人类进步的阶梯、培养阅读习惯,当一个人爱上读书的时候,眼睛都是发光的。 在小编看来,学习理念是【先广度后深度】,先把Java知识体系的东西都了解到,工作上先会用&…...
怎么样自己做企业网站/seo
女儿“恋父情节”的特殊期,父母需要用正确的方法帮孩子度过,否则伤害深远“俄狄浦斯”源于希腊神话,欧洲文学史上的一个悲剧人物,他一出生就遭到了父亲的嫌弃,被丢弃于环境恶劣的荒山中,结果被牧羊人解救&a…...