go程序使用tcp短连接报:only one usage of each socket address

环境及现象

  • Win10
  • 上位机(C#,WPF)
  • 后台使用go作为服务。

连接情况

C#连接大概60个TCP长连接(设备)。
后台go服务连接60个UDP短连接(设备附属硬件),
10个TCP短连接(PLC,modbus通讯),
经常每隔两三天,电脑就什么都连接不上,手动使用modbus 助手调试,报如下错误,需要重启才能解决。
如下图所示:
在这里插入图片描述

解决方法:

参考文章一:https://blog.csdn.net/MBuger/article/details/83088894
参考文章二:http://blog.chinaunix.net/uid-25472509-id-4988567.html
在这里插入图片描述
待观察是否还会出现连接不上的情况。

其他方案

参考:https://www.jianshu.com/p/69d221007d43

  • 查看端口是否耗尽:
netsh interface ipv4 show tcpstats
netsh int ipv4 show dynamicport tcp

  • 增加动态端口数:
# start是起始端口号,num为数量,该命令意思为从1025开始到61025结束,共60000个端口
netsh int ipv4 set dynamicport tcp start=1025 num=60000
  • 修改注册表,设置Time Wait时间(最小为30s),然后重启服务器
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters

新增值 TcpTimedWaitDelay,类型REG_DWORD , 设置为十进制30

大概的最终原因

先说结论:项目使用短连接通讯,由于没有仔细看一个开源库的使用注意事项,没有设置一个tcp的存活时间,导致这个连接永远不会关闭,而新的连接又不断创建,从而导致端口被用完。
项目用到的开源项目:https://github.com/goburrow/modbus
项目中创建连接代码如下:

func createHandlerAndClient(ip string, port int) (modbus.Client, error) {
    
    

	handler := modbus.NewTCPClientHandler(fmt.Sprintf("%s:%d", ip, port))
	handler.Timeout = TIMEOUT
	handler.IdleTimeout = time.Second * 5

	return modbus.NewClient(handler), nil
}

其中有一句非常关键:handler.IdleTimeout = time.Second * 5

为什么很关键?modbus.NewTCPClientHandler这个函数返回的是*TCPClientHandler类型,它实现了PackagerTransporter接口。Transporter接口如下:

type Transporter interface {
    
    
	Send(aduRequest []byte) (aduResponse []byte, err error)
}

看看TCPClientHandler是如何实现的:

// TCPClientHandler implements Packager and Transporter interface.
type TCPClientHandler struct {
    
    
	tcpPackager
	tcpTransporter
}

tcpTransporter实现了Transporter接口:

// Send sends data to server and ensures response length is greater than header length.
func (mb *tcpTransporter) Send(aduRequest []byte) (aduResponse []byte, err error) {
    
    
	mb.mu.Lock()
	defer mb.mu.Unlock()

	// Establish a new connection if not connected
	if err = mb.connect(); err != nil {
    
    
		return
	}
	// Set timer to close when idle
	mb.lastActivity = time.Now()
	mb.startCloseTimer()
	// Set write and read timeout
	var timeout time.Time
	if mb.Timeout > 0 {
    
    
		timeout = mb.lastActivity.Add(mb.Timeout)
	}
	if err = mb.conn.SetDeadline(timeout); err != nil {
    
    
		return
	}
	// Send data
	mb.logf("modbus: sending % x", aduRequest)
	if _, err = mb.conn.Write(aduRequest); err != nil {
    
    
		return
	}
	// Read header first
	var data [tcpMaxLength]byte
	if _, err = io.ReadFull(mb.conn, data[:tcpHeaderSize]); err != nil {
    
    
		return
	}
	// Read length, ignore transaction & protocol id (4 bytes)
	length := int(binary.BigEndian.Uint16(data[4:]))
	if length <= 0 {
    
    
		mb.flush(data[:])
		err = fmt.Errorf("modbus: length in response header '%v' must not be zero", length)
		return
	}
	if length > (tcpMaxLength - (tcpHeaderSize - 1)) {
    
    
		mb.flush(data[:])
		err = fmt.Errorf("modbus: length in response header '%v' must not greater than '%v'", length, tcpMaxLength-tcpHeaderSize+1)
		return
	}
	// Skip unit id
	length += tcpHeaderSize - 1
	if _, err = io.ReadFull(mb.conn, data[tcpHeaderSize:length]); err != nil {
    
    
		return
	}
	aduResponse = data[:length]
	mb.logf("modbus: received % x\n", aduResponse)
	return
}

其中有一句:mb.startCloseTimer()便是定时关闭连接,而这个定时时间,就是上面的IdleTimeout

猜你喜欢

转载自blog.csdn.net/lishuangquan1987/article/details/132051435