Sockets 为什么在并发连接到服务器时接受两个相同的5元组套接字?
server.goSockets 为什么在并发连接到服务器时接受两个相同的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
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