Willson Chen

Stay Hungry, Stay Foolish.

Golang 标准库(二)

Golang 标准库(二)

net

net包及其子包 net/http、net/url 包等提供了HTTP、TCP、UDP 等网络协议和相关辅助功能的实现。

HTTP协议

http 服务端的实现主要包括以下类型:

  • http.Server:服务端核心结构,用于管理和监听请求。
  • http.ServeMux:多路复用器结构,用于分发请求。
  • http.ResponseWriter:响应输出接口,用于写入响应内容。
  • http.Request:请求结构,用于读取请求内容。
func httpServer() {

 mux := http.NewServeMux()
 mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte("resp of get " + r.URL.Query().Get("param")))
 })
 mux.HandleFunc("/postform", func(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte("resp of postform " + r.PostFormValue("param")))
 })
 mux.HandleFunc("/postjson", func(w http.ResponseWriter, r *http.Request) {
  reqBody, err := io.ReadAll(r.Body)
  if err != nil {
   fmt.Println(err)
   return
  }
  w.Write([]byte("resp of postjson " + string(reqBody)))
 })
 server := &http.Server{
  Addr:         ":8080",
  Handler:      mux,
  ReadTimeout:  10 * time.Second,
  WriteTimeout: 10 * time.Second,
 }
 err := server.ListenAndServe()
 if err != nil {
  fmt.Println(err)
 }
}

func main() {

 go httpServer()

 time.Sleep(time.Second * 1) //简单等待服务端启动

 processResult := func(resp *http.Response, err error) {
  if err != nil {
   fmt.Println(err)
   return
  }
  defer resp.Body.Close()

  buf, err := io.ReadAll(resp.Body)
  if err != nil {
   fmt.Println(err)
   return
  }
  fmt.Println(string(buf))
 }

 //http get
 processResult(http.Get("http://127.0.0.1:8080/get?param=123"))
 //http post form
 processResult(http.PostForm("http://127.0.0.1:8080/postform", url.Values{"param": {"456"}}))
 //http post json
 processResult(http.Post("http://127.0.0.1:8080/postjson", "application/json", bytes.NewReader([]byte(`{"param":"789"}`))))
}

TCP协议

func processError(err error) { //用于简单处理错误
 if err != nil {
  fmt.Println(err)
  os.Exit(1)
 }
}

func tcpServer() {
 listener, err := net.Listen("tcp", "127.0.0.1:8080") //监听
 processError(err)

 for {
  conn, err := listener.Accept() //建立连接
  processError(err)

  go func() {
   defer conn.Close() //延迟关闭连接

   //先读取数据,约定换行作为边界符
   data, err := bufio.NewReader(conn).ReadString('\n')
   processError(err)
   fmt.Print("server read:", data)

   //再写入数据,data已包含换行
   _, err = conn.Write([]byte("resp " + data))
   processError(err)
  }()
 }
}
func tcpClient() {
 conn, err := net.Dial("tcp", "127.0.0.1:8080") //建立连接
 processError(err)

 defer conn.Close() //延迟关闭连接

 //先写入数据,约定换行作为边界符
 _, err = conn.Write([]byte("hello world\n"))
 processError(err)

 //再读取数据,约定换行作为边界符
 data, err := bufio.NewReader(conn).ReadString('\n')
 processError(err)
 fmt.Print("client read:", data)
}

func main() {
 go tcpServer()
 time.Sleep(time.Second) //简单等待服务端启动
 tcpClient()
}

UDP协议

func processError(err error) { //用于简单处理错误
 if err != nil {
  fmt.Println(err)
  os.Exit(1)
 }
}

func udpServer() {
 udp, err := net.ListenUDP("udp", &net.UDPAddr{ //开始监听
  IP:   net.IPv4(0, 0, 0, 0),
  Port: 8080,
 })
 processError(err)

 defer udp.Close() //延迟关闭监听
 for {
  //读取数据
  buf := [512]byte{}
  n, addr, err := udp.ReadFromUDP(buf[:])
  processError(err)
  data := string(buf[0:n])
  fmt.Println("server read:", data, "from:", addr.String())

  //再写入数据
  _, err = udp.WriteToUDP([]byte("resp "+data), addr)
  processError(err)
 }
}
func udpClient() {
 udp, err := net.DialUDP("udp", nil, &net.UDPAddr{
  IP:   net.IPv4(0, 0, 0, 0),
  Port: 8080,
 })
 processError(err)

 defer udp.Close() //延迟关闭

 //先写入数据
 _, err = udp.Write([]byte("hello world"))
 processError(err)

 //再读取数据
 buf := [512]byte{}
 n, addr, err := udp.ReadFromUDP(buf[:])
 processError(err)
 data := string(buf[0:n])
 fmt.Println("client read:", data, "from:", addr.String())
}

func main() {
 go udpServer()
 time.Sleep(time.Second) //简单等待服务端启动
 udpClient()
}

context

context包:提供上下文相关功能,通常只适用于后端接口实现请求上下文的应用场景。

上下文:表示执行某个任务时所处的环境和背景状态。

区分请求参数和请求上下文信息:

  • 请求参数:只适用于某个请求的参数信息。
  • 请求上下文信息:适用于所有请求的信息,如认证信息、链路追踪ID、超时时间等。

使用方法:

  • 在最顶层的协程(如 main函数)中用 context.Background() 创建一个根上下文。
  • 上游通过函数参数的方式传递上下文给下游,可以跨协程传递,上下文操作是并发安全的。
  • 上游使用 context.WithCancel(ctx) 创建一个可取消的子上下文,返回值包括取消函数。
  • 上游使用 context.WithCancelCause(ctx) ,则取消函数包括取消原因。
  • 上游使用 context.WithDeadline(ctx, deadlineTime) 创建一个带截止时间的子上下文。
  • 上游使用 context.WithTimeout(ctx, duration) 创建一个带截止时间的子上下文。
  • 上游使用 context.WithValue(ctx, key, value) 创建一个带某个KV的子上下文。
  • 层级创建的所有上下文形成了一棵树,某个节点被取消或到达截止时间,其所有子孙上下文都会取消,父及兄弟节点则不受影响。
  • 下游通过 ctx.Done()方法获取取消信号,返回的是只读通道,取消时通道被关闭。
  • 下游通过 ctx.Deadline()方法获取截止时间。
  • 下游通过 ctx.Value(key) 获取某个KV的值,只会向所有祖先节点查找,不会向子孙节点查找。
  • 下游通过 ctx.Err() 获取错误信息,获取取消原因通过 context.Cause(ctx)。

使用建议:

  • 将上下文作为函数的第一参数,不要放到结构体中。
  • 不要传递 nil 作为上下文,使用 context.TODO()。
  • 不要使用上下文传递请求参数,不要滥用上下文的 KV 信息。
  • WithValue()的 Key 可以用空struct自定义类型,即避免内存开销又能避免冲突。

底层原理:

  • context.Background()和context.TODO() 返回的是内部的emptyCtx,空上下文。
  • WithCancel() 内部将父上下文包装成cancelCtx,启动守护协程确保父取消信号的传递,另外返回一个用于终止本cancelCtx的闭包函数。
  • WithDeadline()和WithTimeout() 内部将父上下文包装成timerCtx,是在 cancelCtx 基础上增加了定时器,定时器超时则触发取消信号。
  • WithValue() 内部将父上下文包装成valueCtx,只增加了一个 KV 信息。
  • 下游调用 ctx.Value(key)时,如果与当前 ctx 的 key 不等则查找父上下文,直到根节点。
type userNameKey struct{} //定义空struct做key的类型

func process(ctx context.Context) {
 now := time.Now()

 for { //每秒检测一次取消信号
  select {
  case <-ctx.Done(): //上游取消或到达截止时间时,返回已关闭通道
   fmt.Println("process done, err:", ctx.Err())
   return
  default: //有 default 分支的 select 不会阻塞
   time.Sleep(time.Second * 1)
   userName := ctx.Value(userNameKey{}) //获取上下文信息
   fmt.Println(int(time.Since(now).Seconds()), userName)
  }
 }
}

func main() {
 //创建一个超时时间为10秒的可取消上下文
 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
 //创建一个带键值对的子上下文
 ctx = context.WithValue(ctx, userNameKey{}, "wills")

 go process(ctx)             //将上下文传递给下游
 time.Sleep(time.Second * 5) //等待执行 5 秒后取消
 cancel()                    //WithTimeout()返回的闭包函数,上游取消时调用
 time.Sleep(time.Second * 5) //简单等待子线程退出后再退出
}

reflect

reflect 包提供反射相关功能,反射是指程序在运行期对自己进行检测、修改和扩展的能力。
通过反射,可以获取类型、字段、方法等反射信息,可以修改字段值,支持数组、切片、map、指针、接口等复杂类型。使程序在运行期获得极为灵活的能力。
常用方法或接口:

  • reflect.TypeOf()获取任意对象的类型反射信息 reflect.Type。
  • reflect.ValueOf()获取任意对象的值反射信息 reflect.Value。
  • reflect.Type 代表任意对象的类型反射信息,包括:
    • Kind() 所属的原生数据类型,枚举值,如Int/String/Array/Slice/Struct/Interface/Func/UnsafePointer等。
    • NumField() 字段数量,常用于遍历字段。
    • Field() 获取字段反射信息 reflect.StructField。
    • FieldByName() 根据字段名获取字段反射信息 reflect.StructField。
  • reflect.StructField 代表一个结构体字段的反射信息,包括:
    • Name 字段名。
    • Type 字段类型的反射信息 reflect.Type。
    • Tag 字段标签反射信息 reflect.StructTag。
  • reflect.Value 代表任意对象的值反射信息,包括:
    • CanSet() 是否可设置,如首字母小写的字段是不可设置的。
    • CanAddr() 是否可获取地址,CanSet的一定CanAddr,反之则不然。
    • Interface() 返回字段值,以 Interface{}的形式。
    • Set() 设置字段值。
    • Len() 返回数组、切片、字符串等类型值的长度。
    • Index() 返回数组、切片、字符串等类型值的元素值反射信息。
type A struct {
 Astr1     string `ignore:"true"`
 Astr2     string
 AsliceB   []B
 innerStr1 string //内部字段
}
type B struct {
 Bstr1 string
 Bstr2 string `ignore:"true"`
}

// 传入任何类型数据,清空其类型为 string 的公开字段值,如果字段标记 ignore 则忽略。
// 支持嵌套struct和切片类型。
func ClearAllStringFields(obj any) error {

 objType, objValue := reflect.TypeOf(obj), reflect.ValueOf(obj)

 if objType.Kind() == reflect.Slice { //slice需要循环递归处理
  lstLen := objValue.Len()
  for j := 0; j < lstLen; j++ {
   objItem := objValue.Index(j)
   if objItem.Kind() == reflect.Ptr {
    ClearAllStringFields(objItem.Interface())
    continue
   }
   if objItem.CanAddr() {
    ClearAllStringFields(objItem.Addr().Interface())
   }
  }
  return nil
 }

 if objType.Kind() == reflect.Ptr { //指针需要取值
  objType, objValue = objType.Elem(), objValue.Elem()
 }
 if objType.Kind() != reflect.Struct {
  return nil
 }

 fieldNum := objType.NumField()
 for i := 0; i < fieldNum; i++ { //遍历结构体的字段
  curField := objType.Field(i)
  curValue := objValue.Field(i)
  if !curValue.CanSet() { //过滤掉不可修改的字段,首字母小写的字段不可修改
   continue
  }
  if curField.Type.Kind() == reflect.Struct ||
   curField.Type.Kind() == reflect.Slice {
   ClearAllStringFields(curValue.Interface())
   continue
  }

  ignore := curField.Tag.Get("ignore")
  if ignore == "true" {
   continue
  }

  curValue.Set(reflect.Zero(curValue.Type()))
 }
 return nil
}

func main() {

 s := []A{
  {"no", "yes", []B{{Bstr1: "yes", Bstr2: "no"}, {Bstr1: "yes", Bstr2: "no"}}, "no"},
  {"no", "yes", []B{{Bstr1: "yes", Bstr2: "no"}, {Bstr1: "yes", Bstr2: "no"}}, "no"},
 }
 fmt.Printf("before clear:\n%+v\n", s)
 ClearAllStringFields(s)
 fmt.Printf("after clear:\n%+v\n", s)
}