对gin应用基于TCP窗口的流量控制
Tag gin, tcp, 窗口, 流量控制, on by view 166

TCP可以基于滑动窗口进行流量控制,使用setsockopt系统调用实现,可以限定客户端或者服务端连接的入网或出网流量,http是基于TCP协议的,因此http也可以基于TCP滑动窗口实现流量控制。golang自有的net包不支持server端TCP窗口设置,因此无法直接实现基于TCP窗口的流量控制。今天我们要对一个基于gin实现的微服务进行流量限制。

首先,gin自带的r.Run()启动的http肯定是不行的,然后http包中的http.ListenAndServer()也是不行的,那么我们就基于TCP来实现,但是golang得net包中的net.Listen()也是不行的。这时候我们只有调用底层的系统调用了(不是cgo),我们可以使用syscall包来实现系统调用。我们分为五步:创建socket,设置socket选项,绑定端口地址,转换为golang的listener,listen。

  • 创建原生的socket

s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
if err != nil {
    log.Println("create socket failed, err:", err.Error())
    return
}
  • 设置socket选项

// set receive buffer here
err = syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_RCVBUF, 2350)
if err != nil {
    log.Println("set socket option receive buffer failed, err:", err.Error())
    return
}

// set send buffer here
err = syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_SNDBUF, 2450)
if err != nil {
    log.Println("set socket option send buffer failed, err:", err.Error())
    return
}
  • 绑定端口地址

err = syscall.Bind(s, &syscall.SockaddrInet4{Port: 8099, Addr: inetAddr("192.168.31.11")})
if err != nil {
    log.Println("bind socket failed, err:", err.Error())
    return
}
  • 转换为golang的listener

f := os.NewFile(uintptr(s), "")
ln, err := net.FileListener(f)
if err != nil {
    log.Println("create listener failed, err:", err.Error())
    return
}
  • listen

err = syscall.Listen(s, 0)
if err != nil {
    log.Println("listen failed, err:", err.Error())
    return
}

最后我们把我们自定义的支持限流的listener应用于gin上

r := gin.Default()

r.GET("/", func(context *gin.Context) {
    context.File("socket")
})

err = http.Serve(ln, r)
if err != nil {
    log.Println("create http server failed, err:", err.Error())
    return
}

一个支持限流的http server就此实现。