全网最详细的 gin框架请求数据绑定Bind 源码解析 -- 帮助你全面了解gin框架的请求数据绑定原理和方法

 在gin框架中,我们可以将多种请求数据(json, form,uri,header等)直接绑定到我们定义的结构体,底层是通过反射方式获取我们定义在结构体上面的tag来实现请求数据到我们的结构体数据的绑定的。 在gin的底层有2大体系的数据绑定一个是Bind,是个是ShouldBind, 下面我们就从数据绑定入口开始一层层的解开gin数据绑定的神秘面纱!

gin中支持的数据绑定类型

        gin框架中的所有数据的绑定都是通过请求类型的 Content-Type这个 MIME类型来完成的,他所支持的类型如下:

// Content-Type MIME of the most common data formats.
const (
	MIMEJSON              = "application/json"
	MIMEHTML              = "text/html"
	MIMEXML               = "application/xml"
	MIMEXML2              = "text/xml"
	MIMEPlain             = "text/plain"
	MIMEPOSTForm          = "application/x-www-form-urlencoded"
	MIMEMultipartPOSTForm = "multipart/form-data"
	MIMEPROTOBUF          = "application/x-protobuf"
	MIMEMSGPACK           = "application/x-msgpack"
	MIMEMSGPACK2          = "application/msgpack"
	MIMEYAML              = "application/x-yaml"
	MIMEYAML2             = "application/yaml"
	MIMETOML              = "application/toml"
)

我们在Bind和ShouldBind  2大序列 中是使用的XXX 定义

// These implement the Binding interface and can be used to bind the data
// present in the request to struct instances.
var (
	JSON          BindingBody = jsonBinding{}
	XML           BindingBody = xmlBinding{}
	Form          Binding     = formBinding{}
	Query         Binding     = queryBinding{}
	FormPost      Binding     = formPostBinding{}
	FormMultipart Binding     = formMultipartBinding{}
	ProtoBuf      BindingBody = protobufBinding{}
	MsgPack       BindingBody = msgpackBinding{}
	YAML          BindingBody = yamlBinding{}
	Uri           BindingUri  = uriBinding{}
	Header        Binding     = headerBinding{}
	TOML          BindingBody = tomlBinding{}
)

上面这些就是gin框架中支持的数据的绑定类型XXX定义, 如 BindJSON,  BindForm,  ShouldBindJSON,    ShouldBinxUri  等。

gin框架中的2大类型的数据绑定方式

        他们实现的功能是一样的,区别在于Bind序列如果数据绑定失败会直接抛异常并退出当前请求,而ShouldBind 则不会中断当前的请求。 原因是 Bind序列使用的是 c.MustBindWith ,注意这里的名字前缀 Must  在go语言的开发中我们通常的做法就是带这个Must的方法,就表示必须要满足的方法, 如果不满足就直接给你个 panic 异常(直接退出当前请求),gin框架也不另外MustXxx 的方法也是必须要满足的,否则panic中断当前请求; 而ShouldBind序列是通过  c.ShouldBindWith 来实现的,他在数据绑定异常时会忽略异常,继续后的的请求。

1.  Bind  序列 示例

他由Bind方法,和 BindXXX 方法主从,他们内部都是调用了c.MustBindWith 方法,这里的XXX 即gin中支持的数据绑定类型,见 gin中支持的数据绑定类型定义 

func (c *Context) Bind(obj any) error {
	b := binding.Default(c.Request.Method, c.ContentType())
	return c.MustBindWith(obj, b)
}

// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
func (c *Context) BindJSON(obj any) error {
	return c.MustBindWith(obj, binding.JSON)
}
// ....

2.  ShouldBind序列 示例

func (c *Context) ShouldBind(obj any) error {
	b := binding.Default(c.Request.Method, c.ContentType())
	return c.ShouldBindWith(obj, b)
}

// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
func (c *Context) ShouldBindJSON(obj any) error {
	return c.ShouldBindWith(obj, binding.JSON)
}
// ......

gin 中数据绑定接口定义

        不管是那个序列的数据绑定,他们都是通过实现以下接口来完成具体的数据绑定的,这个也是go语言的一个核心思想 -- 面向接口编程 !  你没有看错就是面向接口编程,而你常见其他语言,如java 等好像都是说的面向对象编程,而go语言的特别就在于此, go语言中把面向接口编程做到了极致!

        gin框架中为数据绑定定义了3个接口来实现不同类型的数据绑定。 


// Binding describes the interface which needs to be implemented for binding the
// data present in the request such as JSON request body, query parameters or
// the form POST.
type Binding interface {
	Name() string
	Bind(*http.Request, any) error
}

// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
// but it reads the body from supplied bytes instead of req.Body.
type BindingBody interface {
	Binding
	BindBody([]byte, any) error
}

// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
// but it reads the Params.
type BindingUri interface {
	Name() string
	BindUri(map[string][]string, any) error
}

gin中的数据绑定实现

        gin框架中已经给我们实现了多种常见的数据类型的绑定,见 gin中支持的数据绑定类型 。 当然, 如果已有实现中没有你想要的数据类型的绑定或者你想自己动手来实现, 这个也非常简单, 你只要实现上面定义的对应的接口即可! 不知道怎么实现的话你就参考一下gin中已有的实现,哈哈!

gin框架数据绑定实现截图

form数据绑定实现示例

        这里的数据实现比较多, 我们就以 我们最常用的form数据绑定实现为例,和大家一起来学习一下gin中的数据绑定是如何实现的。

1.  数据绑定入口

        下面的formBinding 绑定是普通form的绑定, 另外还有formPostBinding  POST类型的数据绑定, formMultipartBinding 这个是针对媒体上传类型的数据的绑定实现,我们就不一一列举了,他们的实现思路都差不多。


func (formBinding) Bind(req *http.Request, obj any) error {
	if err := req.ParseForm(); err != nil {
		return err
	}
	if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {
		return err
	}
	if err := mapForm(obj, req.Form); err != nil {
		return err
	}
	return validate(obj)
}
2.  请求form数据解析 req.ParseForm() 

// ParseForm populates r.Form and r.PostForm.
//
// For all requests, ParseForm parses the raw query from the URL and updates
// r.Form.
//
// For POST, PUT, and PATCH requests, it also reads the request body, parses it
// as a form and puts the results into both r.PostForm and r.Form. Request body
// parameters take precedence over URL query string values in r.Form.
//
// If the request Body's size has not already been limited by [MaxBytesReader],
// the size is capped at 10MB.
//
// For other HTTP methods, or when the Content-Type is not
// application/x-www-form-urlencoded, the request Body is not read, and
// r.PostForm is initialized to a non-nil, empty value.
//
// [Request.ParseMultipartForm] calls ParseForm automatically.
// ParseForm is idempotent.
func (r *Request) ParseForm() error {
	var err error
	if r.PostForm == nil {
		if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
			r.PostForm, err = parsePostForm(r)
		}
		if r.PostForm == nil {
			r.PostForm = make(url.Values)
		}
	}
	if r.Form == nil {
		if len(r.PostForm) > 0 {
			r.Form = make(url.Values)
			copyValues(r.Form, r.PostForm)
		}
		var newValues url.Values
		if r.URL != nil {
			var e error
			newValues, e = url.ParseQuery(r.URL.RawQuery)
			if err == nil {
				err = e
			}
		}
		if newValues == nil {
			newValues = make(url.Values)
		}
		if r.Form == nil {
			r.Form = newValues
		} else {
			copyValues(r.Form, newValues)
		}
	}
	return err
}
3. 上传类型数据解析 req.ParseMultipartForm

// ParseMultipartForm parses a request body as multipart/form-data.
// The whole request body is parsed and up to a total of maxMemory bytes of
// its file parts are stored in memory, with the remainder stored on
// disk in temporary files.
// ParseMultipartForm calls [Request.ParseForm] if necessary.
// If ParseForm returns an error, ParseMultipartForm returns it but also
// continues parsing the request body.
// After one call to ParseMultipartForm, subsequent calls have no effect.
func (r *Request) ParseMultipartForm(maxMemory int64) error {
	if r.MultipartForm == multipartByReader {
		return errors.New("http: multipart handled by MultipartReader")
	}
	var parseFormErr error
	if r.Form == nil {
		// Let errors in ParseForm fall through, and just
		// return it at the end.
		parseFormErr = r.ParseForm()
	}
	if r.MultipartForm != nil {
		return nil
	}

	mr, err := r.multipartReader(false)
	if err != nil {
		return err
	}

	f, err := mr.ReadForm(maxMemory)
	if err != nil {
		return err
	}

	if r.PostForm == nil {
		r.PostForm = make(url.Values)
	}
	for k, v := range f.Value {
		r.Form[k] = append(r.Form[k], v...)
		// r.PostForm should also be populated. See Issue 9305.
		r.PostForm[k] = append(r.PostForm[k], v...)
	}

	r.MultipartForm = f

	return parseFormErr
}
4. 数据映射 函数 mapForm , mapFormByTag

   注意这里是一个函数,上面2个ParseForm 和 ParseMultipartForm 都是在请求对象上面的方法。

从下面的代码可见, 他这里调用的是mapFormByTag 这个函数,这个即是根据我们定义在结构体中的Tag来映射数据, 这里因为是form类型的数据绑定,所以这个地方的第三个参数就是 form

func mapForm(ptr any, form map[string][]string) error {
	return mapFormByTag(ptr, form, "form")
}

我们接着看看这个mapFormByTag

这里的ptr就是我们要将数据绑定到的我们自定义的结构体对象的指针, form 这个就是上面解析后的请求表单的数据map,   第三个参数 tag 这个就是我们要解析的数据类型的Tag定义名称,这里就的 form 就表示他解析的数据就是我们的结构体TAG中的名称为form的Tag数据, 如我们结构体中的字段Page的定义  Page int `json:"page" form:"page" `  , 这里的tag名称就是form,而对于的字段名称就是 page, 就表示他可以绑定请求参数page的值到 结构体的 Page 字段。


func mapFormByTag(ptr any, form map[string][]string, tag string) error {
	// Check if ptr is a map
	ptrVal := reflect.ValueOf(ptr)
	var pointed any
	if ptrVal.Kind() == reflect.Ptr {
		ptrVal = ptrVal.Elem()
		pointed = ptrVal.Interface()
	}
	if ptrVal.Kind() == reflect.Map &&
		ptrVal.Type().Key().Kind() == reflect.String {
		if pointed != nil {
			ptr = pointed
		}
		return setFormMap(ptr, form)
	}

	return mappingByPtr(ptr, formSource(form), tag)
}

PS: 这里有一个很容易忽略但又非常重要的知识点,就是Elem这个方法的应用时机。 当我们在对一个对象应用函数 reflect.ValueOf() 获取对于的 reflect.Value 对象后, 如果any类型的入参 ptr是一个指针,则获取到的Value对象就必须要调用 .Elem()方法获取指针对应的具体的数据的 reflect.Value后再进行操作,否则就获取不到你想要的数据,因为你拜佛没有找对庙门,哈哈!

5. mappingByPtr函数

这个就是具体的数据绑定映射函数的实现逻辑了, 他这个里面用了递归方式来处理数据的映射,另外还使用了一个 setter 数据设置接口来进行数据的设置。 

从下面的代码我们可以看到,在mapping的第一行就继续了一个tag名称的判断,如果名称是 - 就直接返回 忽略了这个字段的映射处理。  如我们结构体中某个字段的tag 名称是是这样定义的  Name string `form:"-" `  这个就表示会忽略Name这个字段的form数据的绑定


func mappingByPtr(ptr any, setter setter, tag string) error {
	_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
	return err
}


func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
	if field.Tag.Get(tag) == "-" { // just ignoring this field
		return false, nil
	}

	vKind := value.Kind()

	if vKind == reflect.Ptr {
		var isNew bool
		vPtr := value
		if value.IsNil() {
			isNew = true
			vPtr = reflect.New(value.Type().Elem())
		}
		isSet, err := mapping(vPtr.Elem(), field, setter, tag)
		if err != nil {
			return false, err
		}
		if isNew && isSet {
			value.Set(vPtr)
		}
		return isSet, nil
	}

	if vKind != reflect.Struct || !field.Anonymous {
		ok, err := tryToSetValue(value, field, setter, tag)
		if err != nil {
			return false, err
		}
		if ok {
			return true, nil
		}
	}

	if vKind == reflect.Struct {
		tValue := value.Type()

		var isSet bool
		for i := 0; i < value.NumField(); i++ {
			sf := tValue.Field(i)
			if sf.PkgPath != "" && !sf.Anonymous { // unexported
				continue
			}
			ok, err := mapping(value.Field(i), sf, setter, tag)
			if err != nil {
				return false, err
			}
			isSet = isSet || ok
		}
		return isSet, nil
	}
	return false, nil
}
6 setter数据设置接口定义

这个接口就定义了一个方法, TrySet 尝试帮我们设置数据

// setter tries to set value on a walking by fields of a struct
type setter interface {
	TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error)
}
7. 数据设置函数 tryToSetValue 

这里就是数据映射过程中的字段Tag值的获取核心函数。 通过下面的代码我们可以找到gin的数据绑定的Tag中的数据是如何处理的。    详见下面的代码注释


func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
	var tagValue string
	var setOpt setOptions

    // 通过反射获取结构体字段tag对应的数据,
	tagValue = field.Tag.Get(tag)
    //将获取到的tag数据再使用逗号分隔 
	tagValue, opts := head(tagValue, ",")

	if tagValue == "" { // default value is FieldName
		tagValue = field.Name
	}
	if tagValue == "" { // when field is "emptyField" variable
		return false, nil
	}

	var opt string
	for len(opts) > 0 {
		opt, opts = head(opts, ",")

        // 如果获取到的tag值中包含了 default=xx  则对这个字段设置默认值
		if k, v := head(opt, "="); k == "default" {
			setOpt.isDefaultExists = true
			setOpt.defaultValue = v
		}
	}

	return setter.TrySet(value, field, tagValue, setOpt)
}

        根据上面的代码 举例说明:  field.Tag.Get(tag)  这个就是获取我们在结构体中设置的tag对应的值, 如 假设tag为form, 我们有一个结构体中的字段定义是  Page int `json:"page" form:"page,default=1" `     这里的代码field.Tag.Get(tag)  获取到的内容就是 page,default=1

tagValue, opts := head(tagValue, ",") 这个获取到的是tagValue就是 page, opts的值就是default=1

这个定义的意思就是 将请求表单中的 page 对应的字段帮我们绑定到我们定义的这个结构体的 Page字段上面,如果请求表单中没有相关的数据则使用这里定义的默认值1(default=1就是定义默认值), 这个地方就是如何给绑定数据设置默认值的方式, 这个知识点gin官方文档和示例可没有哦!! 这个就是通过这里的源码发现的使用方法。

form的setter接口执行 TrySet
// TrySet tries to set a value by request's form source (like map[string][]string)
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) {
	return setByForm(value, field, form, tagValue, opt)
}
 form数据设置函数 setByForm

通过下面的代码,可见他可以设置的数据类型有 切片, 数组,还有可序列化的数据(默认),这个可序列化的数据类型就包含所有的可以被序列化的数据。


func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
	vs, ok := form[tagValue]
	if !ok && !opt.isDefaultExists {
		return false, nil
	}

	switch value.Kind() {
	case reflect.Slice:
		if !ok {
			vs = []string{opt.defaultValue}
		}
		return true, setSlice(vs, value, field)
	case reflect.Array:
		if !ok {
			vs = []string{opt.defaultValue}
		}
		if len(vs) != value.Len() {
			return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
		}
		return true, setArray(vs, value, field)
	default:
		var val string
		if !ok {
			val = opt.defaultValue
		}

		if len(vs) > 0 {
			val = vs[0]
		}
		if ok, err := trySetCustom(val, value); ok {
			return ok, err
		}
		return true, setWithProperType(val, value, field)
	}
}
可序列化的数据 设置
// trySetCustom tries to set a custom type value
// If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true`
// to skip the default value setting.
func trySetCustom(val string, value reflect.Value) (isSet bool, err error) {
	switch v := value.Addr().Interface().(type) {
	case BindUnmarshaler:
		return true, v.UnmarshalParam(val)
	}
	return false, nil
}

 这个就是可序列化的数据的设置的具体逻辑,这里也是根据反射方式先获取要设置的结构体的字段的类型,然后根据不同的类型来设置具体的值。  细心的你应该能够注意到,我们上面提到的小知识点 指针类型的数据需要先调用 .Elem()方法 的应用,见下面的 case reflect.Ptr:  


func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
	switch value.Kind() {
	case reflect.Int:
		return setIntField(val, 0, value)
	case reflect.Int8:
		return setIntField(val, 8, value)
	case reflect.Int16:
		return setIntField(val, 16, value)
	case reflect.Int32:
		return setIntField(val, 32, value)
	case reflect.Int64:
		switch value.Interface().(type) {
		case time.Duration:
			return setTimeDuration(val, value)
		}
		return setIntField(val, 64, value)
	case reflect.Uint:
		return setUintField(val, 0, value)
	case reflect.Uint8:
		return setUintField(val, 8, value)
	case reflect.Uint16:
		return setUintField(val, 16, value)
	case reflect.Uint32:
		return setUintField(val, 32, value)
	case reflect.Uint64:
		return setUintField(val, 64, value)
	case reflect.Bool:
		return setBoolField(val, value)
	case reflect.Float32:
		return setFloatField(val, 32, value)
	case reflect.Float64:
		return setFloatField(val, 64, value)
	case reflect.String:
		value.SetString(val)
	case reflect.Struct:
		switch value.Interface().(type) {
		case time.Time:
			return setTimeField(val, field, value)
		case multipart.FileHeader:
			return nil
		}
		return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
	case reflect.Map:
		return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
	case reflect.Ptr:
		if !value.Elem().IsValid() {
			value.Set(reflect.New(value.Type().Elem()))
		}
		return setWithProperType(val, value.Elem(), field)
	default:
		return errUnknownType
	}
	return nil
}

ok, 至此,gin框架中的数据请求绑定源码都扒完了...   后面就是如何使用了,当你了解了他的原理后使用那就是小菜一碟了, 本文就不做讨论了。。。。。。

如果本文对你有帮助,欢迎点赞,收藏,评论, 你的支持就是我们继续产出优质内容的动力哦 :) 

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

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

相关文章

华为HCIP Datacom H12-821 卷19

1.多选题 如图所示,RTA 的 GE0/0/0、GE0/0/1 接口分别连接部门 1 和 2,其网段分别为 10.1.2.0/24、 10.1.3.0/24 网段,为限制部门 1 和 2 之间的相互访问,在 RTA 上部署 traffic-filter,以下哪些部署方式是正 确? A、配置 ACL3000 拒绝源为 10.1.2.0/24 目的为 10.1.3.0…

matlab仿真 通信信号和系统分析(上)

&#xff08;内容源自详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真 刘学勇编著第三章内容&#xff0c;有兴趣的读者请阅读原书&#xff09; 一、求离散信号卷积和 主要还是使用卷积函数conv&#xff0c;值得注意的是&#xff0c;得到的卷积和长度结果为81&#xff0…

【正点原子K210连载】第十四章 按键输入实验 摘自【正点原子】DNK210使用指南-CanMV版指南

1&#xff09;实验平台&#xff1a;正点原子ATK-DNK210开发板 2&#xff09;平台购买地址https://detail.tmall.com/item.htm?id731866264428 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/docs/boards/xiaoxitongban 第十四章 按键输入实…

短信验证码API的防护策略?怎么优化更新?

短信验证码API的定制化服务怎么样&#xff1f;如何选择API服务&#xff1f; 短信验证码API成为保护用户账户和数据的重要工具&#xff0c;对短信验证码API的防护也显得尤为重要。AoKSend将探讨短信验证码API的防护策略&#xff0c;帮助企业和开发者确保系统的安全性和可靠性。…

FatFs(文件系统)

1官网 FatFs - 通用 FAT 文件系统模块 (elm-chan.org) FatFs 是用于小型嵌入式系统的通用 FAT/exFAT 文件系统模块。FatFs 模块是按照 ANSI C &#xff08;C89&#xff09; 编写的&#xff0c;并且与磁盘 I/O 层完全分离。因此&#xff0c;它独立于平台。它可以集成到资源有限…

2024 vue3入门教程:01vscode终端命令创建第一个vue项目

参考vue官网手册&#xff1a;https://cn.vuejs.org/guide/quick-start.html 一、找个盘符&#xff0c;新建文件夹存储以后得vue项目 我的是e盘下创建了vueproject 二、使用vscode打开存储vue项目的文件夹 因为我生成过项目&#xff0c;所以有文件&#xff0c;你们初次是没有…

【第五节】C/C++数据结构之图

目录 一、图的基本概念 1.1 图的定义 1.2 图的其他术语概念 二、图的存储结构 2.1 邻接矩阵 2.2 邻接表 三、图的遍历 3.1 广度优先遍历 3.2 深度优先遍历 四、最小生成树 4.1 最小生成树获取策略 4.2 Kruskal算法 4.3 Prim算法 五、最短路径问题 5.1 Dijkstra算…

WPF----自定义滚动条ScrollViewer

滚动条是项目当中经常用到的一个控件&#xff0c;大部分对外项目都有外观的需求&#xff0c;因此需要自定义&#xff0c;文中主要是针对一段动态的状态数据进行展示&#xff0c;并保证数据始终在最新一条&#xff0c;就是需要滚动条滚动到底部。 1&#xff0c;xaml中引入 <…

【大模型系列】Language-Vision Transformer(LaVIT, ICLR2024)

Title&#xff1a;Unified Language-Vision Pretraining in LLM with Dynamic Discrete Visual TokenizationPaper&#xff1a;https://arxiv.org/abs/2309.04669Github&#xff1a;https://github.com/jy0205/LaVITAuthor&#xff1a;Yang Jin&#xff0c; 北大&#xff0c;快…

Android Native 客户端属性配置系统使用说明

Android Native 客户端属性配置系统使用说明 背景和问题现代 android 开发基本都基于 gradle 属性设置来进行定制化编译,随着项目的迭代,工程结构越发复杂,配置属性越来越多,越来越多的配置使得上手难度越来越大。 解决方案设计一般而言,在 android 开发中,Gradle 属性系…

141个图表,完美展示数据分类别关系!

本文介绍使用Python工具seaborn详细实现分类关系图表&#xff0c;包含8类图141个代码模版。 分类关系图表用于展示数字变量和一个或多个分类变量之间的关系&#xff0c;可以进一步分为&#xff1a;箱形图&#xff08;box plot&#xff09;、增强箱形图&#xff08;enhanced bo…

阿里云物联网应用层开发:第二部分,云产品流转

文章目录 1、云产品流转概述2、我们需要创建多少个云产品流转&#xff1f;3、阿里云物联网平台产品云流转实现3-1 创建数据源3-2 创建数据目的3-2 创建解析器,并关联数据、编写脚本 1、云产品流转概述 云产品流转可以看成是一个转发的功能&#xff0c;阿里云官方的解释是&…

【操作与配置】Xshell安装使用

Xshell是一款功能强大的远程管理工具&#xff0c;主要用于通过SSH&#xff08;Secure Shell&#xff09;、TELNET等协议连接和管理远程服务器。它支持多种会话管理、脚本编写、端口转发等功能&#xff0c;适合系统管理员和开发者使用。 安装 官网下载&#xff1a;家庭/学校免费…

linux企业级CDN/100万并发架构设计/企业故障案例、网站优化架构原则

高薪思想&#xff1a;财富来源于信息差 什么是cdn&#xff1f; cdn全称是contenct delivery network内容分发网络 cdn是一套分布式&#xff0c;缓存&#xff0c;集群&#xff0c;范围可以是全球或全国&#xff0c;运行的实质是通过智能DNS获取用户的来源地域以及上网线路 为…

13_网络安全

目录 网络安全协议 网络安全协议 PGP协议 网络安全技术 防火墙技术 入侵检测系统 入侵防御系统 杀毒软件 蜜罐系统 计算机病毒与木马 网络安全协议 网络安全协议 物理层主要使用物理手段隔离、屏蔽物理设备等&#xff0c;其他层都是靠协议来保证传输的安全&#xff…

【C++】内存分区

目录 内存分区代码运行前后区别各分区详细解释C内存申请和释放 内存分区 不同的操作系统对程序内存的管理和划分会有所不同。 此处是C内存区域划分主要是针对通用的情况&#xff0c;并不限定在某个特定操作系统上 一般分为4个区&#xff08;有时把全局区拆分成数据区未初始化…

Go线程调度器

基本结构 字段gcwaiting、stopwait和stopnoted都是串行运行时任务执行前后的辅助协调手段 gcwaiting字段的值用于表示是否需要停止调度 在停止调度前&#xff0c;该值会被设置为1在恢复调度之前&#xff0c;该值会被设置为0这样做的作用是&#xff0c;一些调度任务在执行时只…

阿里云智能编程助手的安装使用

https://help.aliyun.com/document_detail/2590613.html 通义灵码&#xff0c;是阿里云出品的一款基于通义大模型的智能编码辅助工具&#xff0c;提供行级/函数级实时续写、自然语言生成代码、单元测试生成、代码优化、注释生成、代码解释、研发智能问答、异常报错排查等能力&a…

企业互联网建站源码系统 附带完整的安装代码包以及搭建部署教程

系统概述 企业互联网建站源码吸系统是一款集众多先进功能于一身的建站工具。它提供了丰富的模板和组件&#xff0c;允许企业根据自身需求和品牌形象进行个性化定制&#xff0c;快速搭建出具有独特风格的网站。 代码示例 系统特色功能一览 1.用户友好界面&#xff1a;系统采用…

24 年程序员各岗位薪资待遇汇总(最新)

大家好&#xff0c;我是程序员鱼皮。今天分享 24 年 6 月最新的程序员各岗位薪资待遇汇总。 数据是从哪儿来的呢&#xff1f;其实很简单&#xff0c;BOSS 直聘上有一个免费的薪酬查询工具&#xff0c;只要认证成为招聘者就能直接看&#xff0c;便于招聘者了解市场&#xff0c;…