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)
}