Go语言学习笔记(六)net & net/http
加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959
net
import "net"
net包提供了可移植的网络I/O接口,包括TCP/IP、UDP、域名解析和Unix域socket。
虽然本包提供了对网络原语的访问,大部分使用者只需要Dial、Listen和Accept函数提供的基本接口;以及相关的Conn和Listener接口。crypto/tls包提供了相同的接口和类似的Dial和Listen函数。
Listen函数创建的服务端:
ln, err := net.Listen("tcp", ":8080") if err != nil { // handle error } for { conn, err := ln.Accept() if err != nil { // handle error continue } go handleConnection(conn) }
Dial函数和服务端建立连接:
conn, err := net.Dial("tcp", "google.com:80") if err != nil { // handle error } fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n") status, err := bufio.NewReader(conn).ReadString('\n') // ...
TCPConn
TCPConn代表一个TCP网络连接,实现了Conn接口。
Conn接口
Conn接口代表通用的面向流的网络连接。多个线程可能会同时调用同一个Conn的方法。
type Conn interface { // Read从连接中读取数据 // Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Read(b []byte) (n int, err error) // Write从连接中写入数据 // Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Write(b []byte) (n int, err error) // Close方法关闭该连接 // 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误 Close() error // 返回本地网络地址 LocalAddr() Addr // 返回远端网络地址 RemoteAddr() Addr // 设定该连接的读写deadline,等价于同时调用SetReadDeadline和SetWriteDeadline // deadline是一个绝对时间,超过该时间后I/O操作就会直接因超时失败返回而不会阻塞 // deadline对之后的所有I/O操作都起效,而不仅仅是下一次的读或写操作 // 参数t为零值表示不设置期限 SetDeadline(t time.Time) error // 设定该连接的读操作deadline,参数t为零值表示不设置期限 SetReadDeadline(t time.Time) error // 设定该连接的写操作deadline,参数t为零值表示不设置期限 // 即使写入超时,返回值n也可能>0,说明成功写入了部分数据 SetWriteDeadline(t time.Time) error }
栗子一(tcp)
tcp服务端
package main import ( "fmt" "net" ) func process(conn net.Conn) { defer conn.Close() for { buf := make([]byte, 512) n, err := conn.Read(buf) if err != nil { fmt.Println("read err:", err) return } fmt.Println("read:", string(buf[:n])) } } func main() { fmt.Println("server start...") listen, err := net.Listen("tcp", "0.0.0.0:8000") if err != nil { fmt.Println("listen failed, err:", err) return } for { conn, err := listen.Accept() if err != nil { fmt.Println("accept failed, err:", err) continue } go process(conn) } }
tcp客户端
package main import ( "bufio" "fmt" "net" "os" "strings" ) func main() { conn, err := net.Dial("tcp", "localhost:8000") if err != nil { fmt.Println("err dialing:", err.Error()) return } defer conn.Close() inputReader := bufio.NewReader(os.Stdin) for { input, _ := inputReader.ReadString('\n') trimedInput := strings.Trim(input, "\r\n") if trimedInput == "Q" { return } _, err := conn.Write([]byte(trimedInput)) if err != nil { fmt.Println("err conn.write:", err) return } } }
栗子二(http)
封装一个http连接,请求百度
package main import ( "fmt" "io" "net" ) func main() { conn, err := net.Dial("tcp", "www.baidu.com:80") if err != nil { fmt.Println("err dialing:", err.Error()) return } defer conn.Close() msg := "GET / HTTP/1.1\r\n" msg += "Host: www.baidu.com\r\n" msg += "Connection: close\r\n" // msg += "Connection: keep-alive\r\n" msg += "\r\n\r\n" _, err = io.WriteString(conn, msg) if err != nil { fmt.Println("io write string failed, err:", err) return } buf := make([]byte, 4096) for { count, err := conn.Read(buf) if err != nil { break } fmt.Println(string(buf[:count])) } }
net/http
import "net/http"
http包提供了HTTP客户端和服务端的实现。
Get、Head、Post和PostForm函数发出HTTP/ HTTPS请求。
resp, err := http.Get("http://example.com/") ... resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf) ... resp, err := http.PostForm("http://example.com/form", url.Values{"key": {"Value"}, "id": {"123"}})
程序在使用完回复后必须关闭回复的主体。
resp, err := http.Get("http://example.com/") if err != nil { // handle error } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) // ...
要管理HTTP客户端的头域、重定向策略和其他设置,创建一个Client:
client := &http.Client{ CheckRedirect: redirectPolicyFunc, } resp, err := client.Get("http://example.com") // ... req, err := http.NewRequest("GET", "http://example.com", nil) // ... req.Header.Add("If-None-Match", `W/"wyzzy"`) resp, err := client.Do(req) // ...
要管理代理、TLS配置、keep-alive、压缩和其他设置,创建一个Transport:
tr := &http.Transport{ TLSClientConfig: &tls.Config{RootCAs: pool}, DisableCompression: true, } client := &http.Client{Transport: tr} resp, err := client.Get("https://example.com")
Client和Transport类型都可以安全的被多个go程同时使用。出于效率考虑,应该一次建立、尽量重用。
ListenAndServe使用指定的监听地址和处理器启动一个HTTP服务端。处理器参数通常是nil,这表示采用包变量DefaultServeMux作为处理器。Handle和HandleFunc函数可以向DefaultServeMux添加处理器。
http.Handle("/foo", fooHandler) http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) }) log.Fatal(http.ListenAndServe(":8080", nil))
要管理服务端的行为,可以创建一个自定义的Server:
s := &http.Server{ Addr: ":8080", Handler: myHandler, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } log.Fatal(s.ListenAndServe())
栗子server
import ( "fmt" "net/http" ) func Hello(w http.ResponseWriter, r *http.Request) { fmt.Println("Hello World.") fmt.Fprintf(w, "Hello World.\n") } func main() { http.HandleFunc("/", Hello) err := http.ListenAndServe("0.0.0.0:6000", nil) if err != nil { fmt.Println("http listen failed.") } }
栗子client
import ( "fmt" "io/ioutil" "net/http" ) func main() { res, err := http.Get("http://www.baidu.com") if err != nil { fmt.Println("Get error:", err) return } data, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Println("Get data error:", err) return } fmt.Println(string(data)) }
栗子head
import ( "fmt" "net/http" "time" ) var url = []string{ "http://www.baidu.com", "http://www.google.com", "http://pan.263.net", } func main() { for _, v := range url { http.DefaultClient.Timeout = time.Second * 2 resp, err := http.Head(v) if err != nil { fmt.Printf("head %s failed, err: %v\n", v, err) continue } fmt.Printf("head %s succ, status: %v\n", v, resp.Status) } }
栗子(form)
import ( "fmt" "io" "log" "net/http" ) const form = ` <html> <body> <form action="#" method="post" name="bar"> <input type="text" name="in"/> <input type="text" name="in"/> <input type="submit" value="Submit"/> </form> </body> </html> ` func SimpleServer(w http.ResponseWriter, request *http.Request) { io.WriteString(w, "Hello World.") } func FormServer(w http.ResponseWriter, request *http.Request) { w.Header().Set("Content-Type", "text/html") switch request.Method { case "GET": io.WriteString(w, form) case "POST": request.ParseForm() io.WriteString(w, request.Form["in"][1]) io.WriteString(w, "\n") io.WriteString(w, request.FormValue("in")) } } func logPanics(handle http.HandlerFunc) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { defer func() { if x := recover(); x != nil { log.Printf("[%v] caught panic: %v", request.RemoteAddr, x) } }() handle(writer, request) } } func main() { http.HandleFunc("/test1", SimpleServer) http.HandleFunc("/test2", logPanics(FormServer)) err := http.ListenAndServe("0.0.0.0:6000", nil) if err != nil { fmt.Println("http listen failed.") } }
栗子(template)
package main import ( "fmt" "html/template" "net/http" ) type Person struct { Title string Name string Age int } func SimpleServer(w http.ResponseWriter, request *http.Request) { indexFailPath := "./index.html" t, err := template.ParseFiles(indexFailPath) if err != nil { fmt.Println("parse file err:", err) return } p := Person{Name: "Nick", Age: 18, Title: "Good."} if err = t.Execute(w, p); err != nil { fmt.Println("There was an error:", err.Error()) return } } func main() { http.HandleFunc("/test1", SimpleServer) err := http.ListenAndServe("0.0.0.0:9000", nil) if err != nil { fmt.Println("http listen failed.") } }
<html> <head> <title> {{.Title}} </title> </head> <body> <p>{{.Name}}</p> {{if gt .Age 18}} <p>MAN: {{.Name}}</p> {{else}} <p>Kid: {{.Name}}</p> {{end}} </body> </html>
更多用法
- not 非 {{if not .condition}} {{end}}
- and 与 {{if and .condition1 .condition2}} {{end}}
- or 或 {{if or .condition1 .condition2}} {{end}}
- eq 等于 {{if eq .var1 .var2}} {{end}}
- ne 不等于 {{if ne .var1 .var2}} {{end}}
- lt 小于 (less than) {{if lt .var1 .var2}} {{end}}
- le 小于等于 {{if le .var1 .var2}} {{end}}
- gt 大于 {{if gt .var1 .var2}} {{end}}
- ge 大于等于 {{if ge .var1 .var2}} {{end}}
- range 循环 {{range.}} {{end }}
Appendix
大端字节序的实现
data, err := json.Marshal("hello world") if err != nil { return } var buf [4]byte packLen := uint32(len(data)) fmt.Println("packlen:", packLen) // 前4个字节表示data大小 binary.BigEndian.PutUint32(buf[0:4], packLen) n, err := conn.Write(buf[:]) if err != nil || n != 4 { fmt.Println("write data failed") return } _, err = conn.Write([]byte(data)) if err != nil { return }
Http 状态码
const ( StatusContinue = 100 StatusSwitchingProtocols = 101 StatusOK = 200 StatusCreated = 201 StatusAccepted = 202 StatusNonAuthoritativeInfo = 203 StatusNoContent = 204 StatusResetContent = 205 StatusPartialContent = 206 StatusMultipleChoices = 300 StatusMovedPermanently = 301 StatusFound = 302 StatusSeeOther = 303 StatusNotModified = 304 StatusUseProxy = 305 StatusTemporaryRedirect = 307 StatusBadRequest = 400 StatusUnauthorized = 401 StatusPaymentRequired = 402 StatusForbidden = 403 StatusNotFound = 404 StatusMethodNotAllowed = 405 StatusNotAcceptable = 406 StatusProxyAuthRequired = 407 StatusRequestTimeout = 408 StatusConflict = 409 StatusGone = 410 StatusLengthRequired = 411 StatusPreconditionFailed = 412 StatusRequestEntityTooLarge = 413 StatusRequestURITooLong = 414 StatusUnsupportedMediaType = 415 StatusRequestedRangeNotSatisfiable = 416 StatusExpectationFailed = 417 StatusTeapot = 418 StatusInternalServerError = 500 StatusNotImplemented = 501 StatusBadGateway = 502 StatusServiceUnavailable = 503 StatusGatewayTimeout = 504 StatusHTTPVersionNotSupported = 505 )