golang微服務網關之網絡基礎知識
面試中被面試官經常問到的一些關於tcp網絡知識問題 今天依依給大家分析下(毫無保留分享:)
- 三次握手
- 四次揮手
- 為啥time_wait需要等待2MSL?
- 為啥會出現大量的close_wait?
- 什么時候會出現FIN-WAIT?
- TCP為啥需要流量控制?
- 如何調整網絡負載?
- tcp為啥需要擁塞控制?
- 慢開始和擁塞避免?
- 快速重傳和快速恢復?
- 為什么出現粘包/拆包?
為啥time_wait需要等待2MSL?
1,MSL:Maximum Segment Lifetime,30秒-1分鍾
2,保證TCP協議的全雙工連接能夠可靠關閉
3,保證這次連接的重復數據段從網絡中消失
為啥會出現大量的close_wait?
1,首先close_wait一般書現在被動方關閉
2,並發請求太多導致
3,被動關閉方未及時釋放端口資源導致
CLOSE_WAIT產生原因
close_wait是被動關閉連接是形成的,根據TCP狀態機,服務器端收到客戶端發送的FIN,TCP協議棧會自動發送ACK,鏈接進入close_wait狀態。但如果服務器端不執行socket的close()操作,狀態就不能由close_wait遷移到last_ack,則系統中會存在很多close_wait狀態的連接;
說白的就是並發可能有點大,io不能及時切換過去,I/O線程被意外阻塞,I/O操作處理不及時,鏈路不能被及時釋放
TCP為啥需要流量控制?
如何調整網絡負載,tcp為啥需要擁塞控制?
-
慢開始和擁塞避免
-
快速重傳和快速恢復
所謂慢開始,tcp剛開始一點點傳遞試探一下網絡的承受能力,以免擾亂網絡通道的秩序
上圖中,圖標3處遇到網絡擁塞,就把擁塞的窗口直接降為1了,然后重新開始慢開始,一點點遞增
為了優化慢開始所以對算法進行了優化:快重傳和快恢復
快速重傳;當收到3個重復ACK 執行快重傳:
會把當前擁塞窗口降為原來的一般。然后把擁塞避免的預值降為原來的一半,進入一個快速恢復的階段
快速恢復:因為受到3次重復ack,丟包,只要是在這個階段丟的包,會重復發送一遍,直到把所有丟失的包重新發送完畢后就會退出快速恢復階段,
然后進入擁塞避免階段
為什么出現粘包/拆包?
上圖:
發送方由應用程序發送應用的報文,根據應用數據報文大小的不同,它會占用2個或者1個,應用的數據實際會發送到tcp的緩沖區里面(發送緩沖區)。真正發送是由linux內核走tcp連接發送;
tcp根據緩沖區大小來決定是否要粘包,粘包:多次請求合並到一個tcp報文中,拆包:一次請求拆到多個tcp報文里面,至於數據如何被包裝都是由tcp底層去完成的。
因為我運用的其實是應用層,不需要關心它的細節,數據會流入接收方的接收緩沖區,接收方通過socket的reverve方法去獲取到數據。
我們是在應用層通過socket 直接從bufer緩沖區拿取數據
如何獲取完整應用的數據報文?
如何獲取完整的數據報文?
實例代碼:

package unpack import ( "encoding/binary" "errors" "io" ) const Msg_Header = "12345678" func Encode(bytesBuffer io.Writer, content string) error { //msg_header+content_len+content //8+4+content_len if err := binary.Write(bytesBuffer, binary.BigEndian, []byte(Msg_Header)); err != nil { return err } clen := int32(len([]byte(content))) if err := binary.Write(bytesBuffer, binary.BigEndian, clen); err != nil { return err } if err := binary.Write(bytesBuffer, binary.BigEndian, []byte(content)); err != nil { return err } return nil } func Decode(bytesBuffer io.Reader) (bodyBuf []byte, err error) { MagicBuf := make([]byte, len(Msg_Header)) if _, err = io.ReadFull(bytesBuffer, MagicBuf); err != nil { return nil, err } if string(MagicBuf) != Msg_Header { return nil, errors.New("msg_header error") } lengthBuf := make([]byte, 4) if _, err = io.ReadFull(bytesBuffer, lengthBuf); err != nil { return nil, err } length := binary.BigEndian.Uint32(lengthBuf) bodyBuf = make([]byte, length) if _, err = io.ReadFull(bytesBuffer, bodyBuf); err != nil { return nil, err } return bodyBuf, err }

package main import ( "fmt" "github.com/e421083458/gateway_demo/demo/base/unpack/unpack" "net" ) func main() { conn, err := net.Dial("tcp", "localhost:9090") defer conn.Close() if err != nil { fmt.Printf("connect failed, err : %v\n", err.Error()) return } unpack.Encode(conn, "hello world 0!!!") }

package main import ( "fmt" "github.com/e421083458/gateway_demo/demo/base/unpack/unpack" "net" ) func main() { //simple tcp server //1.監聽端口 listener, err := net.Listen("tcp", "0.0.0.0:9090") if err != nil { fmt.Printf("listen fail, err: %v\n", err) return } //2.接收請求 for { conn, err := listener.Accept() if err != nil { fmt.Printf("accept fail, err: %v\n", err) continue } //3.創建協程 go process(conn) } } func process(conn net.Conn) { defer conn.Close() for { bt, err := unpack.Decode(conn) if err != nil { fmt.Printf("read from connect failed, err: %v\n", err) break } str := string(bt) fmt.Printf("receive from client, data: %v\n", str) } }
golang創建udp服務和客戶端

package main import ( "fmt" "net" ) func main() { //step 1 連接服務器 conn, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP: net.IPv4(127, 0, 0, 1), Port: 9090, }) if err != nil { fmt.Printf("connect failed, err: %v\n", err) return } for i := 0; i < 100; i++ { //step 2 發送數據 _, err = conn.Write([]byte("hello server!")) if err != nil { fmt.Printf("send data failed, err : %v\n", err) return } //step 3 接收數據 result := make([]byte, 1024) n, remoteAddr, err := conn.ReadFromUDP(result) if err != nil { fmt.Printf("receive data failed, err: %v\n", err) return } fmt.Printf("receive from addr: %v data: %v\n", remoteAddr, string(result[:n])) } }

package main import ( "fmt" "net" ) func main() { //step 1 監聽服務器 listen, err := net.ListenUDP("udp", &net.UDPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 9090, }) if err != nil { fmt.Printf("listen failed, err:%v\n", err) return } //step 2 循環讀取消息內容 for { var data [1024]byte n, addr, err := listen.ReadFromUDP(data[:]) if err != nil { fmt.Printf("read failed from addr: %v, err: %v\n", addr, err) break } go func() { //todo sth //step 3 回復數據 fmt.Printf("addr: %v data: %v count: %v\n", addr, string(data[:n]), n) _, err = listen.WriteToUDP([]byte("received success!"), addr) if err != nil { fmt.Printf("write failed, err: %v\n", err) } }() } }
golang創建tcp服務器和客戶端

package main import ( "fmt" "net" ) func main() { //1、監聽端口 listener, err := net.Listen("tcp", "0.0.0.0:9090") if err != nil { fmt.Printf("listen fail, err: %v\n", err) return } //2.建立套接字連接 for { conn, err := listener.Accept() if err != nil { fmt.Printf("accept fail, err: %v\n", err) continue } //3. 創建處理協程 go process(conn) } } func process(conn net.Conn) { defer conn.Close() //思考題:這里不填寫會有啥問題? for { var buf [128]byte n, err := conn.Read(buf[:]) if err != nil { fmt.Printf("read from connect failed, err: %v\n", err) break } str := string(buf[:n]) fmt.Printf("receive from client, data: %v\n", str) } }

package client import ( "bufio" "fmt" "net" "os" "strings" ) func main() { doSend() fmt.Print("doSend over") doSend() fmt.Print("doSend over") //select {} } func doSend() { //1、連接服務器 conn, err := net.Dial("tcp", "localhost:9090") defer conn.Close() //思考題:這里不填寫會有啥問題? if err != nil { fmt.Printf("connect failed, err : %v\n", err.Error()) return } //2、讀取命令行輸入 inputReader := bufio.NewReader(os.Stdin) for { // 3、一直讀取直到讀到\n input, err := inputReader.ReadString('\n') if err != nil { fmt.Printf("read from console failed, err: %v\n", err) break } // 4、讀取Q時停止 trimmedInput := strings.TrimSpace(input) if trimmedInput == "Q" { break } // 5、回復服務器信息 _, err = conn.Write([]byte(trimmedInput)) if err != nil { fmt.Printf("write failed , err : %v\n", err) break } } }
客戶端:defer conn.Close() //思考題:這里不填寫會有啥問題?(連接一直在建立狀態,除非tcp連接探測后才會關閉)
服務端:defer conn.Close() //思考題:這里不填寫會有啥問題?
客戶端發起了關閉,服務端沒有關閉,此時按照四次揮手圖分析:
客戶端是主動關閉方,客戶端此時處於FIN-WAIT-2;
服務端屬於被動關閉方,服務端處於CLOSE-WAIT狀態;
golang創建http服務
服務端:
- 創建路由器;
- 設置路由規則;
- 創建服務器;
- 監聽端口並提供服務;
客戶端:
- 創建連接池:
- 創建客戶端;
- 請求數據;
- 讀取內容;

package main import ( "fmt" "io/ioutil" "net" "net/http" "time" ) func main() { // 創建連接池 transport := &http.Transport{ DialContext: (&net.Dialer{ Timeout: 30 * time.Second, //連接超時 KeepAlive: 30 * time.Second, //探活時間 }).DialContext, MaxIdleConns: 100, //最大空閑連接 IdleConnTimeout: 90 * time.Second, //空閑超時時間 TLSHandshakeTimeout: 10 * time.Second, //tls握手超時時間 ExpectContinueTimeout: 1 * time.Second, //100-continue狀態碼超時時間 } // 創建客戶端 client := &http.Client{ Timeout: time.Second * 30, //請求超時時間 Transport: transport, } // 請求數據 resp, err := client.Get("http://127.0.0.1:1210/bye") if err != nil { panic(err) } defer resp.Body.Close() // 讀取內容 bds, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(bds)) }

package main import ( "log" "net/http" "time" ) var ( Addr = ":1210" ) func main() { // 創建路由器 mux := http.NewServeMux() // 設置路由規則 mux.HandleFunc("/bye", sayBye) // 創建服務器 server := &http.Server{ Addr: Addr, WriteTimeout: time.Second * 3, Handler: mux, } // 監聽端口並提供服務 log.Println("Starting httpserver at "+Addr) log.Fatal(server.ListenAndServe()) } func sayBye(w http.ResponseWriter, r *http.Request) { time.Sleep(1 * time.Second) w.Write([]byte("bye bye ,this is httpServer")) }
golang http服務器源碼分析:
在分析httpserver源碼之前,請看看此文章 ,了解下type func的用法,函數式一等公民概念,不然下面代碼可能難以理解。
從最簡單的例子開始:
package main import ( "log" "net/http" "time" ) var ( Addr = ":1210" ) func main() { // 創建路由器 mux := http.NewServeMux() // 設置路由規則 mux.HandleFunc("/bye", sayBye) // 創建服務器 server := &http.Server{ Addr: Addr, WriteTimeout: time.Second * 3, Handler: mux, } // 監聽端口並提供服務 log.Println("Starting httpserver at "+Addr) log.Fatal(server.ListenAndServe()) } func sayBye(w http.ResponseWriter, r *http.Request) { time.Sleep(1 * time.Second) w.Write([]byte("bye bye ,this is httpServer")) }
來看看HandleFunc是啥?
// HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) }
HandlerFunc(handler)
此處就是用到了type關鍵字 把一個函數轉成HandlerFunc 類型,並且實現了ServeHTTP方法
type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
ServeHTTP方法又實現了Handler接口
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
通過回調思路最終執行了sayBye()
mu:一把鎖
m:存放着路由和回調函數
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry es []muxEntry // slice of entries sorted from longest to shortest. hosts bool // whether any patterns contain hostnames }
h 注冊的函數
pattern 注冊的路由
type muxEntry struct { h Handler pattern string }
注冊路由
mux.Handle(pattern, HandlerFunc(handler))
開啟服務:
func (srv *Server) ListenAndServe() error { if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(ln) }
func (srv *Server) Serve(l net.Listener) error
處理鏈接:
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { if r.ProtoAtLeast(1, 1) { w.Header().Set("Connection", "close") } w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) h.ServeHTTP(w, r) }
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
httpClient源碼簡單解析:
先看看一個簡單的例子:
package main import ( "fmt" "io/ioutil" "net" "net/http" "time" ) func main() { // 創建連接池 transport := &http.Transport{ DialContext: (&net.Dialer{ Timeout: 30 * time.Second, //連接超時 KeepAlive: 30 * time.Second, //探活時間 }).DialContext, MaxIdleConns: 100, //最大空閑連接 IdleConnTimeout: 90 * time.Second, //空閑超時時間 TLSHandshakeTimeout: 10 * time.Second, //tls握手超時時間 ExpectContinueTimeout: 1 * time.Second, //100-continue狀態碼超時時間 } // 創建客戶端 client := &http.Client{ Timeout: time.Second * 30, //請求超時時間 Transport: transport, } // 請求數據 resp, err := client.Get("http://127.0.0.1:1210/bye") if err != nil { panic(err) } defer resp.Body.Close() // 讀取內容 bds, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(bds)) }
分析以后繼續。。。。。。。。
下一篇 網絡代理之HTTP代理