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 EncodebytesBuffer io.Writer, content string) error { //msg_header+content_len+content //8+4+content_len if err := binary.WritebytesBuffer, binary.BigEndian, []byteMsg_Header)); err != nil { return err } clen := int32len[]bytecontent))) if err := binary.WritebytesBuffer, binary.BigEndian, clen); err != nil { return err } if err := binary.WritebytesBuffer, binary.BigEndian, []bytecontent)); err != nil { return err } return nil } func DecodebytesBuffer io.Reader) bodyBuf []byte, err error) { MagicBuf := make[]byte, lenMsg_Header)) if _, err = io.ReadFullbytesBuffer, MagicBuf); err != nil { return nil, err } if stringMagicBuf) != Msg_Header { return nil, errors.New"msg_header error") } lengthBuf := make[]byte, 4) if _, err = io.ReadFullbytesBuffer, lengthBuf); err != nil { return nil, err } length := binary.BigEndian.Uint32lengthBuf) bodyBuf = make[]byte, length) if _, err = io.ReadFullbytesBuffer, bodyBuf); err != nil { return nil, err } return bodyBuf, err }
D:gocode1.14.3gocodegateway_demodemoaseunpackunpackcodec.go
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 ", err.Error)) return } unpack.Encodeconn, "hello world 0!!!") }
D:gocode1.14.3gocodegateway_demodemoaseunpack cp_clientmain.go
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 ", err) return } //2.接收请求 for { conn, err := listener.Accept) if err != nil { fmt.Printf"accept fail, err: %v ", err) continue } //3.创建协程 go processconn) } } func processconn net.Conn) { defer conn.Close) for { bt, err := unpack.Decodeconn) if err != nil { fmt.Printf"read from connect failed, err: %v ", err) break } str := stringbt) fmt.Printf"receive from client, data: %v ", str) } }
D:gocode1.14.3gocodegateway_demodemoaseunpack cp_servermain.go
golang创建udp服务和客户端
package main import "fmt" "net" ) func main) { //step 1 连接服务器 conn, err := net.DialUDP"udp", nil, &net.UDPAddr{ IP: net.IPv4127, 0, 0, 1), Port: 9090, }) if err != nil { fmt.Printf"connect failed, err: %v ", 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 ", err) return } //step 3 接收数据 result := make[]byte, 1024) n, remoteAddr, err := conn.ReadFromUDPresult) if err != nil { fmt.Printf"receive data failed, err: %v ", err) return } fmt.Printf"receive from addr: %v data: %v ", remoteAddr, stringresult[:n])) } }
D:gocode1.14.3gocodegateway_demodemoaseudp_clientmain.go
package main import "fmt" "net" ) func main) { //step 1 监听服务器 listen, err := net.ListenUDP"udp", &net.UDPAddr{ IP: net.IPv40, 0, 0, 0), Port: 9090, }) if err != nil { fmt.Printf"listen failed, err:%v ", err) return } //step 2 循环读取消息内容 for { var data [1024]byte n, addr, err := listen.ReadFromUDPdata[:]) if err != nil { fmt.Printf"read failed from addr: %v, err: %v ", addr, err) break } go func) { //todo sth //step 3 回复数据 fmt.Printf"addr: %v data: %v count: %v ", addr, stringdata[:n]), n) _, err = listen.WriteToUDP[]byte"received success!"), addr) if err != nil { fmt.Printf"write failed, err: %v ", err) } }) } }
D:gocode1.14.3gocodegateway_demodemoaseudp_servermain.go
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 ", err) return } //2.建立套接字连接 for { conn, err := listener.Accept) if err != nil { fmt.Printf"accept fail, err: %v ", err) continue } //3. 创建处理协程 go processconn) } } func processconn net.Conn) { defer conn.Close) //思考题:这里不填写会有啥问题? for { var buf [128]byte n, err := conn.Readbuf[:]) if err != nil { fmt.Printf"read from connect failed, err: %v ", err) break } str := stringbuf[:n]) fmt.Printf"receive from client, data: %v ", 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 ", err.Error)) return } //2、读取命令行输入 inputReader := bufio.NewReaderos.Stdin) for { // 3、一直读取直到读到 input, err := inputReader.ReadString' ') if err != nil { fmt.Printf"read from console failed, err: %v ", err) break } // 4、读取Q时停止 trimmedInput := strings.TrimSpaceinput) if trimmedInput == "Q" { break } // 5、回复服务器信息 _, err = conn.Write[]bytetrimmedInput)) if err != nil { fmt.Printf"write failed , err : %v ", 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 { panicerr) } defer resp.Body.Close) // 读取内容 bds, err := ioutil.ReadAllresp.Body) if err != nil { panicerr) } fmt.Printlnstringbds)) }
http客户端
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.Fatalserver.ListenAndServe)) } func sayByew http.ResponseWriter, r *http.Request) { time.Sleep1 * time.Second) w.Write[]byte"bye bye ,this is httpServer")) }
http服务端
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.Fatalserver.ListenAndServe)) } func sayByew http.ResponseWriter, r *http.Request) { time.Sleep1 * time.Second) w.Write[]byte"bye bye ,this is httpServer")) }
来看看HandleFunc是啥?
// HandleFunc registers the handler function for the given pattern. func mux *ServeMux) HandleFuncpattern string, handler funcResponseWriter, *Request)) { if handler == nil { panic"http: nil handler") } mux.Handlepattern, HandlerFunchandler)) }
HandlerFunchandler)
此处就是用到了type关键字 把一个函数转成HandlerFunc 类型,并且实现了ServeHTTP方法
type HandlerFunc funcResponseWriter, *Request) // ServeHTTP calls fw, r). func f HandlerFunc) ServeHTTPw ResponseWriter, r *Request) { fw, r) }
ServeHTTP方法又实现了Handler接口
type Handler interface { ServeHTTPResponseWriter, *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.Handlepattern, HandlerFunchandler))
开启服务:
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.Serveln) }
func srv *Server) Servel net.Listener) error
处理链接:
func mux *ServeMux) ServeHTTPw ResponseWriter, r *Request) { if r.RequestURI == "*" { if r.ProtoAtLeast1, 1) { w.Header).Set"Connection", "close") } w.WriteHeaderStatusBadRequest) return } h, _ := mux.Handlerr) h.ServeHTTPw, r) }
func mux *ServeMux) Handlerr *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 { panicerr) } defer resp.Body.Close) // 读取内容 bds, err := ioutil.ReadAllresp.Body) if err != nil { panicerr) } fmt.Printlnstringbds)) }
分析以后继续。。。。。。。。
下一篇 网络代理之HTTP代理