当TCP Client异常结束时,大部分TCP server都无法正确判断和处理这个问题。
常见的解决思路:
- NoDelay: 解决不了,只解决了发送的问题
- KeepAlive:解决不了,虽然这个机制会最终导致socket error然后退出,但是时间太长,没有实际意义
- epoll error: 这个机制不是所有的语言都支持
- Timeout: 这个机制对golang内置,其他语言不一定有原生实现
- 自定义心跳:可以,需要双端支持,不建议
这里给出一个golang 解决这个问题的代码:
server: (核心在connLoop
函数)
package main
import (
"fmt"
"net"
"time"
)
// var tcpServe *net.TCPListener
func main() {
fmt.Println("Hello world")
addr, er := net.ResolveTCPAddr("tcp", "0.0.0.0:19000")
if er != nil {
fmt.Println("parse addr error on ", addr.String())
return
}
tcpServe, er := net.ListenTCP("tcp", addr)
if er != nil {
fmt.Println("Listen error on ", tcpServe.Addr().String())
return
}
defer tcpServe.Close()
fmt.Println("start listen for client...", tcpServe.Addr().String())
for {
conn, er := tcpServe.AcceptTCP()
if er != nil {
fmt.Println("tcp server accept error ", er)
break
}
fmt.Println("Conn come in: ", conn.RemoteAddr().String())
conn.SetNoDelay(true)
conn.SetKeepAlive(true)
go connLoop(conn)
}
}
func connLoop(c *net.TCPConn) {
defer c.Close()
//fmt.Println("Conn come in: ", c.RemoteAddr().String())
var buff = make([]byte, 1024)
for {
c.SetReadDeadline(time.Now().Add(time.Second * 10))
n, er := c.Read(buff)
if er != nil {
switch t_er := er.(type) {
case net.Error:
if t_er.Timeout() {
// 只有这个可以判断 read timeout 错误.
fmt.Println("read timeout, retry...")
continue
}
// 对于 io.EOF 之类,正常处理
break
default:
break
}
fmt.Println("Conn error: ", er)
break
}
fmt.Println("Read inf: ", buff[:n])
}
fmt.Println("Conn leave... ", c.RemoteAddr().String())
}
Client:
package main
import (
"bufio"
"fmt"
"net"
"os"
)
// var client *net.TCPConn
func main() {
fmt.Println("Hello world client")
client, er := net.Dial("tcp", "127.0.0.1:19000")
if er != nil {
fmt.Println("Dial error: ", er)
return
}
defer client.Close()
fmt.Println("Local: ", client.LocalAddr().String(), "Remote: ", client.RemoteAddr().String())
reader := bufio.NewReader(os.Stdin)
for {
dt, er := reader.ReadString('\n')
if er != nil {
fmt.Println("read error: ", er)
break
}
client.Write([]byte(dt))
}
fmt.Println("client disconnect...")
}