Golang gorilla websocket server refusing further connections after accepting approximately 16,300 concurrent connections

106 Views Asked by At

I am running Ubuntu Server 22.04 on a virtual machine with a configuration of 16 CPUs and 32GB of RAM. On this virtual machine, I have deployed a Golang server using Gorilla WebSocket with the goal of establishing 100,000 WebSocket connections. To achieve this, I am using Docker on another PC within the same network; I have configured multiple containers, each attempting to establish 10,000 WebSocket connections. However, I am encountering an issue whereby the server starts refusing connections after reaching approximately 16,300 concurrent connections.

Interestingly, I replicated the same setup on my Mac, running both the server and client on the my mac locally no virtual machine, and encountered a similar connection refusal scenario after approximately 16,300 connections, also on my mac i was able to get it to accept up to about approximately 32,700 websocket connections when i remove localhost in http.ListenAndServe("localhost:8000", nil) and only use http.ListenAndServe(":8000", nil). The exact error i get on the client is "dial tcp [::1]:9000: connect: resource temporarily unavailable". client error image

Server code

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/websocket"
)

var connCount int = 0

func ws(w http.ResponseWriter, r *http.Request) {
    // Upgrade connection
    connCount++
    if connCount%100 == 0 {
        fmt.Println("Client Connected: ", connCount)
    }

    upgrader := websocket.Upgrader{}
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer conn.Close()

    // Read messages from socket
    for {
        _, msg, err := conn.ReadMessage()
        if err != nil {
            fmt.Println(err)
            fmt.Println("Client Disconnected: ", connCount)

            return
        }
        log.Printf("msg: %s", string(msg))
    }
}

func main() {
    http.HandleFunc("/", ws)
    if err := http.ListenAndServe(":9000", nil); err != nil {
        log.Fatal(err)
    }
}

Client code

package main

import (
    "flag"
    "fmt"
    "io"
    "log"
    "net/url"
    "os"
    "time"

    "github.com/gorilla/websocket"
)

var (
    ip          = flag.String("ip", "localhost", "server IP")
    connections = flag.Int("conn", 1, "number of websocket connections")
)

func main() {
    flag.Usage = func() {
        io.WriteString(os.Stderr, `Websockets client generator
Example usage: ./client -ip=172.17.0.1 -conn=10
`)
        flag.PrintDefaults()
    }
    flag.Parse()

    u := url.URL{Scheme: "ws", Host: *ip + ":9000", Path: "/"}
    log.Printf("Connecting to %s", u.String())

    var conns []*websocket.Conn
    for i := 0; i < *connections; i++ {
        c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
        if err != nil {
            fmt.Println("Failed to connect", i, err)
            break
        }
        conns = append(conns, c)
        defer func() {
            c.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second))
            time.Sleep(time.Second)
            c.Close()
        }()
    }

    log.Printf("Finished initializing %d connections", len(conns))
    tts := time.Second
    if *connections > 100 {
        tts = time.Millisecond * 5
    }
    for {
        for i := 0; i < len(conns); i++ {
            time.Sleep(tts)
            conn := conns[i]
            log.Printf("Conn %d sending message", i)
            if err := conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(time.Second*5)); err != nil {
                fmt.Printf("Failed to receive pong: %v", err)
            }
            conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("Hello from conn %v", i)))
        }
    }
}

0

There are 0 best solutions below