Go语言网络编程
该包提供了网络I/O的可移植接口,支持TCP/IP、UDP、DNS查询、Unix域套接字。
尽管此包提供了访问低级网络原语,但是大部分客户端仅仅需要Dial、Listen、Accept等基本函数,以及关联的Conn、Listener接口。
Dial函数用于连接到正在监听的服务器:
1 2 3 4 5 6 7 8 9 10 11 |
// 发起TCP连接 conn, err := net.Dial("tcp", "hongkong.gmem.cc:80") if err == nil { // Conn是Writer,支持写入 fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n") // Conn还是Reader,下面读取直到遇到第一个换行 bs := bufio.NewScanner(conn) for bs.Scan() { fmt.Println(bs.Text()) } } |
Listen函数用于创建服务器并监听端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import ( "net" "bufio" "fmt" "time" ) func handleConn(conn net.Conn) { conn.Write([]byte("欢迎\n")) } func Server() { // 返回一个Listener ln, _ := net.Listen("tcp", ":8080") for { // 每接收到一个连接,就交给子Goroutine处理 conn, _ := ln.Accept() go handleConn(conn) } } func Client() { conn, _ := net.Dial("tcp", "localhost:8080") bs := bufio.NewScanner(conn) bs.Split(bufio.ScanLines) bs.Scan() msg := bs.Text() conn.Close() fmt.Println(msg) } func main() { go Server() for i := 0; i < 10; i++ { go Client() } time.Sleep(time.Second) } |
使用net包可以完成DNS查询:
1 2 3 4 5 6 7 8 |
addrs, _ := net.LookupHost("tk.gmem.cc") for _, addr := range addrs { println(addr) // 打印IP地址 } names, _ := net.LookupAddr("108.61.247.199") for _, name := range names { println(name) // 打印反查得到的域名 } |
此包提供了HTTP客户端和服务器的实现。
简单的客户端代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// GET请求 resp, err := http.Get("http://tokyo.gmem.cc") if err == nil { println(resp.StatusCode) body, _ := ioutil.ReadAll(resp.Body) println(string(body)) } // 通过POST提交表单 resp, _ := http.PostForm("http://tokyo.gmem.cc/article/new", url.Values{ "name": {"Alex"}, "id": "1", }) // 通过POST上传文件 resp, _ := http.Post("http://tokyo.gmem.cc/avatar/upload", "image/jpeg", &imgBuf) // 客户端必须负责关闭响应体,且必须判断resp是否为nil if resp != nil { // 读取并丢弃剩余的响应主体数据。确保在keepalive http连接行为开启的情况下,可以被另一个请求复用 defer resp.Body.Close() } // 标准http库默认只在HTTP服务器要求关闭时才会关闭网络连接。要在请求完成后立即关闭连接,使用下面的头 req.Header.Add("Connection", "close") // 禁用HTTP keep-alive。在向大量服务器发送少量请求时可以禁用 tr := &http.Transport{DisableKeepAlives: true} client := &http.Client{Transport: tr} |
如果需要控制请求头、重定向策略和其它设置,可以使用Client:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 重定向策略 新请求 已经被重定向的请求,最老的为第一个元素 // 默认策略是允许10次重定向 redirectPolicy := func(req *http.Request, via []*http.Request) error { return nil } hc := &http.Client{CheckRedirect: redirectPolicy} // 创建一个请求对象 req, _ := http.NewRequest("GET", "http://tk.gmem.cc", nil) // 设置请求头 req.Header.Add("Accept", "text/html") // 执行请求 resp, _ := hc.Do(req) |
如果要控制传输相关的属性,例如代理、TLS配置、Keep-Alive、压缩,可以使用Transport:
1 2 3 4 5 6 7 8 9 10 |
tr := &http.Transport{ // 最大空闲的TCP连接数 MaxIdleConns: 10, // 空闲超时时间 IdleConnTimeout: 30 * time.Second, // 是否禁用压缩 DisableCompression: true, } client := &http.Client{Transport: tr} resp, _ := client.Get("https://tk.gmem.cc") |
进行超时控制:
1 2 3 4 5 6 7 8 |
req, err := http.NewRequest("GET", u.String(), nil) // 超时自动取消的上下文 // 默认是后台上下文 ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second) defer cancel() // 使用超时上下文进行请求 r, err := http.DefaultClient.Do(req.WithContext(ctx)) defer r.Body.Close() b, err := ioutil.ReadAll(r.Body) |
如果服务器使用不受信任证书,可以跳过证书校验:
1 |
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} |
创建简单的服务器:
1 2 3 4 5 6 7 8 9 10 11 |
// 为DefaultServeMux添加Handler http.HandleFunc("/greetings", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello %v", r.URL.Query().Get("name")) }); // handle为nil则使用DefaultServeMux http.ListenAndServe(":8800", nil) wg := sync.WaitGroup{} wg.Add(1) wg.Wait() |
如果需要定制各项参数,可以使用Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
type reqHandler int func (rh reqHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // 从URL抽取请求参数 values, ok := r.URL.Query()["key"] // 读取Cookie cookie, err := r.Cookie("token") // 抽取表单参数 r.ParseForm() // 读取单个参数 value := r.Form["key"][0] // 读取全部参数 var params map[stirng][]string = r.PostForm // 读取请求体中的JSON并反序列化 decoder := json.NewDecoder(r.Body) var user User err := decoder.Decode(&user) // 关闭请求体 defer req.Body.Close() } func main() { s := &http.Server{ Addr: ":8080", Handler: reqHandler(0), ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } s.ListenAndServe() } |
要实现服务器端的请求处理多路器(multiplexer),参考下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
mux := http.DefaultServeMux // 每个路径都必须由http.Handler处理 mux.Handle("/metrics", promhttp.Handler()) // 处理器示例 mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { // 写响应头 w.WriteHeader(http.StatusOK) // 写响应体 w.Write([]byte("OK")) }) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // 读取请求体 body, err := ioutil.ReadAll(r.Body) defer r.Body.Close() // 解析为对象 payload := &flaggerv1.CanaryWebhookPayload{} err = json.Unmarshal(body, payload) if err != nil { w.WriteHeader(http.StatusBadRequest) return } w.WriteHeader(http.StatusAccepted) }) // 创建HTTP服务器 srv := &http.Server{ Addr: ":" + port, Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 1 * time.Minute, IdleTimeout: 15 * time.Second, } // 在后台启动服务器 go func() { if err := srv.ListenAndServe(); err != http.ErrServerClosed { logger.Fatalf("HTTP server crashed %v", err) } }() // 等待信号 <-stopCh // 时限内关闭服务器 ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() if err := srv.Shutdown(ctx); err != nil { logger.Errorf("HTTP server graceful shutdown failed %v", err) } else { logger.Info("HTTP server stopped") } |
此包支持对URL进行操控:
1 2 3 4 5 6 |
// 解析绝对URL promURL, err := url.Parse("http://prometheus.istio-system.svc.k8s.gmem.cc:9090") // 解析相对URL u, err := url.Parse("./api/v1/status/flags") // 组合 u = promURL.ResolveReference(u) |
更加人性化、简单的HTTP客户端,执行下面的命令安装:
1 |
go get github.com/imroc/req |
1 2 3 4 5 6 |
// 先创建请求对象,再发请求 r := req.New() r.Get(url) // 或者直接调用函数 req.Get(url) |
1 2 3 4 5 6 7 8 9 10 11 |
header := req.Header{ "Accept": "application/json", "Authorization": "Basic YWRtaW46YWRtaW4=", } param := req.Param{ "name": "imroc", "cmd": "add", } r, err = req.Post("http://foo.bar/api", header, param) |
1 2 |
req.Post(url, req.BodyJSON(&foo)) req.Post(url, req.BodyXML(&bar)) |
1 2 |
res, err = req.Post("http://foo.bar/api", header, param) res.Response().Header.Get(headers.ContentType) |
1 2 3 4 |
// 按JSON来解析,绑定到对象 r.ToJSON(&buf) // 按XML来解析,绑定到对象 r.ToXML(&baz) |
1 2 3 4 5 6 7 |
// 禁用Cookie req.EnableCookie(false) cookie := new(http.Cookie) // 发送Cookie req.Get(url, cookie) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
req.Post(url, req.File("imroc.png"), req.File("/Users/roc/Pictures/*.png")) // 细粒度控制 file, _ := os.Open("imroc.png") req.Post(url, req.FileUpload{ File: file, FieldName: "file", // 表单字段名 FileName: "avatar.png", // 上传时使用的文件名 }) // 监听上传进度 progress := func(current, total int64) { fmt.Println(float32(current)/float32(total)*100, "%") } req.Post(url, req.File("/Users/roc/Pictures/*.png"), req.UploadProgress(progress)) fmt.Println("upload complete") |
1 2 3 4 5 6 7 8 9 10 11 |
r, _ := req.Get(url) r.ToFile("imroc.png") // 监听下载进度 progress := func(current, total int64) { fmt.Println(float32(current)/float32(total)*100, "%") } r, _ := req.Get(url, req.DownloadProgress(progress)) r.ToFile("hello.mp4") fmt.Println("download complete") |
默认情况下,环境变量http_proxy、https_proxy自动作为代理服务器。你也可以设置自己的代理服务器:
1 |
req.SetProxyUrl("http://my.proxy.com:23456") |
1 |
req.SetTimeout(50 * time.Second) |
1 2 3 4 |
client := &http.Client{Timeout: 30 * time.Second} req.Get(url, client) req.SetClient(client) |
Gorilla是一个Web工具箱,提供了若干个包,具体参考Gorilla学习笔记。
Gin是目前Star数最高的Go语言的Web框架,具体参考Gin学习笔记。
用于构建REST-style的WebService。支持GET、POST、PUT、DELETE、PATCH(更新资源的部分内容)、OPTIONS(获取目标URI的通信选项)等方法。
用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import ( restful "github.com/emicklei/go-restful/v3" ) // Restful容器,其中可以注册多个WebService // 默认容器:restful.DefaultContainer // DefaultContainer = NewContainer() // DefaultContainer.ServeMux = http.DefaultServeMux // c := restful.NewContainer() c.ServeMux = http.NewServeMux() c.Router(restful.CurlyRouter{}) c.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) { logStackOnRecover(s, panicReason, httpWriter) }) c.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) { serviceErrorHandler(s, serviceErr, request, response) }) // WebService,需要添加到Restful容器中 ws := new(restful.WebService) ws. Path("/users"). Consumes(restful.MIME_XML, restful.MIME_JSON). Produces(restful.MIME_JSON, restful.MIME_XML) c.Add(u.WebService()) http.ListenAndServe(":8080", c.ServeMux) |
请参考gRPC学习笔记。
包golang.org/x/crypto/ssh提供了SSH协议的支持:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
func main() { sess, err := connect("root", "lavender", "xenial-100", 22) if err != nil { log.Fatalf(err.Error()) } // 将SSH会话的标准输出/错误重定向到当前应用的 sess.Stdout = os.Stdout sess.Stderr = os.Stderr // 执行命令 sess.Run("uname -a") // 执行命令,并获取其标准输出 result, _ := sess.Output("uname -a") fmt.Printf("%s", result) // 要执行多个命令,可以 stdinBuf, _ := ssh.StdinPipe() sess.Shell() stdinBuf.Write([]byte("ls")) stdinBuf.Write([]byte("\n")) stdinBuf.Write([]byte("uname")) stdinBuf.Write([]byte("\n")) // 需要调用exit,让会话退出,否则Wait()调用永久阻塞 stdinBuf.Write([]byte("exit\n")) // 需要调用此函数,否则可能过早退出,命令却没有执行 ssh.Wait() sess.Close() } func connect(user, password, host string, port int) (*ssh.Session, error) { var ( auth []ssh.AuthMethod addr string clientConfig *ssh.ClientConfig client *ssh.Client err error ) auth = make([]ssh.AuthMethod, 0) // 身份认证方法,支持密码或sshkey auth = append(auth, ssh.Password(password)) clientConfig = &ssh.ClientConfig{ User: user, Auth: auth, Timeout: 30 * time.Second, // 允许任意的服务器公钥 HostKeyCallback: ssh.InsecureIgnoreHostKey(), } addr = fmt.Sprintf("%s:%d", host, port) // 发起连接 if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil { return nil, err } // 创建会话 return client.NewSession() } |
cmux是一个连接复用器,允许在同一个端口上提供不同类型的服务 —— 包括gRPC、SSH、HTTPS、HTTP、Go RPC,等等。
cmux仅仅需要检测一个连接的最开始几个字节,因此对性能的影响是微不足道的。
注意点:
- 关于TLS:包net/http基于断言来识别TLS连接,由于cmux使用 lookahead-implementing的连接来装饰底层TCP连接,导致net/http的断言会失败。后果是,你可以使用cmux来服务HTTPS,但是不会为你的Handler设置http.Request.TLS
- 一个连接,自始自终必须使用同一协议。也就是说,一个Connection要么使用gRPC,要么使用REST,而不能随意切换
- 关于Java的gRPC客户端:此客户端会在接收到服务器返回的SETTINGS帧之前一直阻塞,你应当使用下面的代码:
1grpcl := m.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import "github.com/soheilhy/cmux" // 创建主监听器 l, err := net.Listen("tcp", ":80") if err != nil { log.Fatal(err) } // 为监听器创建cmux m := cmux.New(l) // 按照声明顺序,逐个去匹配协议 // 匹配gRPC grpcL := m.Match(cmux.HTTP2HeaderField("content-type", "application/grpc")) // 匹配HTTP httpL := m.Match(cmux.HTTP1Fast()) // 所有不匹配的其它连接请求,都看作Go RPC/TCP trpcL := m.Match(cmux.Any()) // 为不同协议创建服务器 //gRPC grpcS := grpc.NewServer() grpchello.RegisterGreeterServer(grpcS, &server{}) // HTTP httpS := &http.Server{ Handler: &helloHTTP1Handler{}, } // Go RPC/TCP trpcS := rpc.NewServer() trpcS.Register(&ExampleRPCRcvr{}) // 将各协议的服务器注册到muxed的监听器 go grpcS.Serve(grpcL) go httpS.Serve(httpL) go trpcS.Accept(trpcL) // 开始服务 m.Serve() |
Leave a Reply