• golang微服务网关一:网络基础知识扫盲(温故而知新)


    golang微服务网关之网络基础知识

    面试中被面试官经常问到的一些关于tcp网络知识问题 今天依依给大家分析下(毫无保留分享:)

    • 三次握手
    • 四次挥手
    • 为啥time_wait需要等待2MSL?
    • 为啥会出现大量的close_wait?
    • 什么时候会出现FIN-WAIT?
    • TCP为啥需要流量控制?
    • 如何调整网络负载?
    • tcp为啥需要拥塞控制?
    • 慢开始和拥塞避免?
    • 快速重传和快速恢复?
    • 为什么出现粘包/拆包?

    osi七层网络协议

     经典协议与数据包

     

     

     

     

     

     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为啥需要拥塞控制?

    • 慢开始和拥塞避免

    • 快速重传和快速恢复

    tcp拥塞控制  慢开始和拥塞避免

     所谓慢开始,tcp刚开始一点点传递试探一下网络的承受能力,以免扰乱网络通道的秩序

    tcp拥塞控制  慢开始和拥塞避免

     上图中,图标3处遇到网络拥塞,就把拥塞的窗口直接降为1了,然后重新开始慢开始,一点点递增

    为了优化慢开始所以对算法进行了优化:快重传和快恢复

    tcp拥塞控制 快重传和快恢复

    快速重传;当收到3个重复ACK 执行快重传

    会把当前拥塞窗口降为原来的一般。然后把拥塞避免的预值降为原来的一半,进入一个快速恢复的阶段

    快速恢复:因为受到3次重复ack,丢包,只要是在这个阶段丢的包,会重复发送一遍,直到把所有丢失的包重新发送完毕后就会退出快速恢复阶段,

    然后进入拥塞避免阶段

    为什么出现粘包/拆包

    tcp粘包和拆包

    上图:

    发送方由应用程序发送应用的报文,根据应用数据报文大小的不同,它会占用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
    }
    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.Encode(conn, "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 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
    ", err)
                break
            }
            str := string(bt)
            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.IPv4(127, 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.ReadFromUDP(result)
            if err != nil {
                fmt.Printf("receive data failed, err: %v
    ", err)
                return
            }
            fmt.Printf("receive from addr: %v  data: %v
    ", remoteAddr, string(result[: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.IPv4(0, 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.ReadFromUDP(data[:])
            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, string(data[: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 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
    ", err)
                break
            }
            str := string(buf[: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.NewReader(os.Stdin)
        for {
            // 3、一直读取直到读到
    
            input, err := inputReader.ReadString('
    ')
            if err != nil {
                fmt.Printf("read from console failed, err: %v
    ", 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
    ", 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))
    }
    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.Fatal(server.ListenAndServe())
    }
    
    func sayBye(w http.ResponseWriter, r *http.Request) {
        time.Sleep(1 * time.Second)
        w.Write([]byte("bye bye ,this is httpServer"))
    }
    http服务端

    golang http服务器源码分析:

      在分析httpserver源码之前,请看看此文章 ,了解下type func的用法,函数式一等公民概念,不然下面代码可能难以理解。

    type关键字的用法

     从最简单的例子开始:

    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代理

  • 相关阅读:
    Java 添加条码、二维码到Word文档
    我的博客园博客开通了
    新浪博客发博文老是提示系统繁忙、请稍后再试解决方法
    阿里云服务器https改造全过程
    网站从HTTP移动到HTTPS
    Apache ab性能测试结果分析
    Memcached与Redis的区别和选择
    binlog2sql使用介绍
    Mysql查看连接数(连接总数、活跃数、最大并发数)
    测试流程规范--测试准入、准出、停止标准、bug优先级定义
  • 原文地址:https://www.cnblogs.com/sunlong88/p/13418803.html
Copyright © 2020-2023  润新知