Sockets 为什么在并发连接到服务器时接受两个相同的5元组套接字?

Sockets 为什么在并发连接到服务器时接受两个相同的5元组套接字?,sockets,go,networking,tcp,Sockets,Go,Networking,Tcp,server.go package main import ( "fmt" "io" "io/ioutil" "log" "net" "net/http" _ "net/http/pprof" "sync" "syscall" ) type ConnSet struct { data map[int]net.Conn mutex sync.Mutex } func (m *ConnSet) Updat

server.go

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net"
    "net/http"
    _ "net/http/pprof"
    "sync"
    "syscall"
)

type ConnSet struct {
    data  map[int]net.Conn
    mutex sync.Mutex
}

func (m *ConnSet) Update(id int, conn net.Conn) error {
    m.mutex.Lock()
    defer m.mutex.Unlock()
    if _, ok := m.data[id]; ok {
        fmt.Printf("add: key %d existed \n", id)
        return fmt.Errorf("add: key %d existed \n", id)
    }
    m.data[id] = conn
    return nil
}

var connSet = &ConnSet{
    data: make(map[int]net.Conn),
}

func main() {
    setLimit()

    ln, err := net.Listen("tcp", ":12345")
    if err != nil {
        panic(err)
    }

    go func() {
        if err := http.ListenAndServe(":6060", nil); err != nil {
            log.Fatalf("pprof failed: %v", err)
        }
    }()

    var connections []net.Conn
    defer func() {
        for _, conn := range connections {
            conn.Close()
        }
    }()

    for {
        conn, e := ln.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                log.Printf("accept temp err: %v", ne)
                continue
            }

            log.Printf("accept err: %v", e)
            return
        }
        port := conn.RemoteAddr().(*net.TCPAddr).Port
        connSet.Update(port, conn)
        go handleConn(conn)
        connections = append(connections, conn)
        if len(connections)%100 == 0 {
            log.Printf("total number of connections: %v", len(connections))
        }
    }
}

func handleConn(conn net.Conn) {
    io.Copy(ioutil.Discard, conn)
}

func setLimit() {
    var rLimit syscall.Rlimit
    if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
        panic(err)
    }
    rLimit.Cur = rLimit.Max
    if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
        panic(err)
    }

    log.Printf("set cur limit: %d", rLimit.Cur)
}
客户机,开始

package main

import (
    "bytes"
    "flag"
    "fmt"
    "io"
    "log"
    "net"
    "os"
    "strconv"
    "sync"
    "syscall"
    "time"
)

var portFlag = flag.Int("port", 12345, "port")

type ConnSet struct {
    data  map[int]net.Conn
    mutex sync.Mutex
}

func (m *ConnSet) Update(id int, conn net.Conn) error {
    m.mutex.Lock()
    defer m.mutex.Unlock()
    if _, ok := m.data[id]; ok {
        fmt.Printf("add: key %d existed \n", id)
        return fmt.Errorf("add: key %d existed \n", id)
    }
    m.data[id] = conn
    return nil
}

var connSet = &ConnSet{
    data: make(map[int]net.Conn),
}

func echoClient() {
    addr := fmt.Sprintf("127.0.0.1:%d", *portFlag)
    dialer := net.Dialer{}
    conn, err := dialer.Dial("tcp", addr)
    if err != nil {
        fmt.Println("ERROR", err)
        os.Exit(1)
    }
    port := conn.LocalAddr().(*net.TCPAddr).Port
    connSet.Update(port, conn)
    defer conn.Close()

    for i := 0; i < 10; i++ {
        s := fmt.Sprintf("%s", strconv.Itoa(i))
        _, err := conn.Write([]byte(s))
        if err != nil {
            log.Println("write error: ", err)
        }
        b := make([]byte, 1024)
        _, err = conn.Read(b)
        switch err {
        case nil:
            if string(bytes.Trim(b, "\x00")) != s {
                log.Printf("resp req not equal, req: %d, res: %s", i, string(bytes.Trim(b, "\x00")))
            }
        case io.EOF:
            fmt.Println("eof")
            break
        default:
            fmt.Println("ERROR", err)
            break
        }
    }
    time.Sleep(time.Hour)
    if err := conn.Close(); err != nil {
        log.Printf("client conn close err: %s", err)
    }
}

func main() {
    flag.Parse()
    setLimit()
    before := time.Now()
    var wg sync.WaitGroup
    for i := 0; i < 20000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            echoClient()
        }()
    }
    wg.Wait()
    fmt.Println(time.Now().Sub(before))
}

func setLimit() {
    var rLimit syscall.Rlimit
    if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
        panic(err)
    }
    rLimit.Cur = rLimit.Max
    if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
        panic(err)
    }

    log.Printf("set cur limit: %d", rLimit.Cur)
}
服务器运行屏幕截图

客户端同时启动到服务器的20000个连接,服务器接受两个完全相同的remotePort连接(在极短的时间内)

我尝试使用from bcc(通过add skc_num(aka:local_port)从中修补)

跟踪连接,并发现当客户端没有重复端口时,远程端口在服务器端重复

据我所知,套接字的5元组不会重复,为什么服务器接受两个具有完全相同远程端口的套接字

我的测试环境:

Fedora 31,内核版本5.3.15 x86_64 和 Ubuntu 18.04.3 LTS,内核版本4.19.1 x86_64

go版本go1.13.5 linux/amd64

wireshark: 服务器TCP对ACK和PSH+ACK保持活动状态

服务器TCP仅对PSH+ACK保持活动状态

建立连接时,连接会添加到地图
数据地图[int]net.Conn
,但当连接关闭时,连接不会从地图中删除。所以,若连接被关闭,其端口将变为空闲端口,并且可以被操作系统重新用于下一个连接。这就是为什么可以看到重复端口的原因


当端口关闭时,尝试从映射中删除端口。

奇怪。两个连接都工作吗?i、 你能通过这两种方式正确发送和接收数据吗?这真的是你正在运行的代码吗?服务器丢弃所有读取的数据,而不回写任何内容,但客户端希望得到响应。如果客户端关闭一个连接,然后重新使用以前使用过的端口,服务器端可能会发生重复错误,但从代码上看这是不可能的。@BurakSerdar代码是可运行的。服务器块接受新连接,客户端块从连接读取,客户端和服务器都不会关闭连接,我通过验证这一点。我通过wireshark捕获本地主机网络流量,561865737647892存在重复问题。其他端口(如42002)没有此问题。你能帮我看看出了什么问题吗@BurakSerdar根据您的建议,我尝试了只读取一个连接,但另一个连接在“连接重置”错误显示套接字已关闭时未关闭。“连接超时”表示一方正在等待从未到达的ack。也许它错过了重置?无论是哪种情况,插座的另一端都是闭合的,不知何故,一端漏了。我猜消息真的丢失了,或者这里有一个与tcp堆栈相关的东西。这不太可能与go实施有关。
go run server.go
---
go run client.go