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

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

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


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

 golang微服务网关一:网络基础知识扫盲(温故而知新)_客户端_02


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


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


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


 golang微服务网关一:网络基础知识扫盲(温故而知新)_客户端_06


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


 golang微服务网关一:网络基础知识扫盲(温故而知新)_客户端_08


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


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



为啥time_wait需要等待2MSL?

1,MSL:Maximum Segment Lifetime,30秒-1分钟

2,保证TCP协议的全双工连接能够可靠关闭

3,保证这次连接的重复数据段从网络中消失


为啥会出现大量的close_wait?

1,首先close_wait一般书现在被动方关闭

2,并发请求太多导致

3,被动关闭方未及时释放端口资源导致


golang微服务网关一:网络基础知识扫盲(温故而知新)_客户端_11


 CLOSE_WAIT产生原因

  close_wait是被动关闭连接是形成的,根据TCP状态机,服务器端收到客户端发送的FIN,TCP协议栈会自动发送ACK,链接进入close_wait状态。但如果服务器端不执行socket的close()操作,状态就不能由close_wait迁移到last_ack,则系统中会存在很多close_wait状态的连接;

说白的就是并发可能有点大,io不能及时切换过去,I/O线程被意外阻塞,I/O操作处理不及时,链路不能被及时释放

TCP为啥需要流量控制?

golang微服务网关一:网络基础知识扫盲(温故而知新)_客户端_12


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



如何调整网络负载,tcp为啥需要拥塞控制?

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



慢开始和拥塞避免

快速重传和快速恢复

golang微服务网关一:网络基础知识扫盲(温故而知新)_客户端_15


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

golang微服务网关一:网络基础知识扫盲(温故而知新)_客户端_16


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

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


golang微服务网关一:网络基础知识扫盲(温故而知新)_客户端_17



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

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

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

然后进入拥塞避免阶段


为什么出现粘包/拆包?

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


上图:

发送方由应用程序发送应用的报文,根据应用数据报文大小的不同,它会占用2个或者1个,应用的数据实际会发送到tcp的缓冲区里面(发送缓冲区)。真正发送是由linux内核走tcp连接发送;

tcp根据缓冲区大小来决定是否要粘包,粘包:多次请求合并到一个tcp报文中,拆包:一次请求拆到多个tcp报文里面,至于数据如何被包装都是由tcp底层去完成的。

因为我运用的其实是应用层,不需要关心它的细节,数据会流入接收方的接收缓冲区,接收方通过socket的reverve方法去获取到数据。

我们是在应用层通过socket 直接从bufer缓冲区拿取数据

golang微服务网关一:网络基础知识扫盲(温故而知新)_客户端_19



如何获取完整应用的数据报文?

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



 如何获取完整的数据报文?

golang微服务网关一:网络基础知识扫盲(温故而知新)_客户端_21


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

 实例代码:

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

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.3\gocode\gateway_demo\demo\base\unpack\unpack\codec.go


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

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!!!")
}

D:\gocode1.14.3\gocode\gateway_demo\demo\base\unpack\tcp_client\main.go


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

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)
}
}

D:\gocode1.14.3\gocode\gateway_demo\demo\base\unpack\tcp_server\main.go


golang创建udp服务和客户端

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




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

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]))
}
}

D:\gocode1.14.3\gocode\gateway_demo\demo\base\udp_client\main.go



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

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)
}
}()
}
}

D:\gocode1.14.3\gocode\gateway_demo\demo\base\udp_server\main.go


golang创建tcp服务器和客户端

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

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

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)
}
}

服务端

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

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连接探测后才会关闭)

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

服务端:defer conn.Close()  //思考题:这里不填写会有啥问题?
客户端发起了关闭,服务端没有关闭,此时按照四次挥手图分析:
客户端是主动关闭方,客户端此时处于FIN-WAIT-2;
服务端属于被动关闭方,服务端处于CLOSE-WAIT状态;

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


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


golang创建http服务

服务端:

  • 创建路由器;
  • 设置路由规则;
  • 创建服务器;
  • 监听端口并提供服务;


客户端:

  • 创建连接池:
  • 创建客户端;
  • 请求数据;
  • 读取内容;


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

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客户端


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

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()


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



 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))



开启服务:

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



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


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


 处理链接:

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