gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 19:04:15 25 4
gpt4 key购买 nike

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

运行命令
go run server.go
---
go run client.go

服务器运行截图

enter image description here

客户端同时向服务器发起 20,000 个连接,服务器接受了两个完全相同的 remotePort 连接(在极短的时间内)。

我尝试使用 tcpconn.py来自 bcc(从 tcpconnect.py 通过添加 skc_num(aka: local_port) 修补)
enter image description here

tcpaccept.py
enter image description here
跟踪连接,在客户端没有重复的情况下,也发现远程端口在服务器端重复

以我的理解,socket的5元组不会重复,为什么服务器接受两个远程端口完全相同的socket?

我的测试环境:

Fedora 31,内核版本 5.3.15 x86_64

Ubuntu 18.04.3 LTS,内核版本 4.19.1 x86_64

转到版本 go1.13.5 linux/amd64

Wireshark :
服务器 TCP 保持连接到 ACK 和 PSH+ACK
dup flow

服务器 TCP Keep-Alive 仅对 PSH+ACK
no dup

最佳答案

连接已添加到 map data map[int]net.Conn当它建立时,但当连接关闭时,它不会从 map 中删除。因此,如果连接关闭,其端口将变为空闲状态,并且可以由操作系统重新使用以进行下一次连接。这就是您可以看到重复端口的原因。
尝试在关闭时从 map 中删除端口。

关于sockets - 为什么在并发连接到服务器时接受两个相同的 5 元组套接字?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59502881/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com