环境及现象
- 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
类型,它实现了Packager
和Transporter
接口。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