本文介绍了golang TCPConn.SetWriteDeadline似乎没有按预期工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 29岁程序员,3月因学历无情被辞! 我试图通过检查golang返回的错误来检测发送失败 TCPConn.Write ,但它是零。我也尝试过使用 TCPConn.SetWriteDeadline ,但没有成功。 这就是事情的发生: 服务器启动 客户端连接 服务器发送一条消息并且客户端收到它 客户端关闭 服务器再发送一条消息:无错误 服务器发送第三条消息:现在只有错误出现 问题:为什么只有发送给不存在客户端的第二封邮件会导致错误?如何正确处理案件? 代码如下: 包主要 进口(净osbufiofmt时间 $ b $ func AcceptConnections(监听器net.Listener,console< - chan string){ msg:= for { conn,err:= listener.Accept() if err!= nil { panic(err)} fmt.Printf(client connected \\\) for { if msg =={ msg =< - console fmt.Printf(从控制台读取:%s,msg)} err = conn.SetWriteDeadline(time.Now().Add(time.Second) ) if err!= nil { fmt.Printf(SetWriteDeadline failed:%v \ n,err)} _ ,err = conn.Write([] byte(msg)) if err!= nil { //发送消息后预期发生错误 //到一个不存在的客户端端点 fmt.Printf(发送消息失败到网络: %v \ n,err) break } else { fmt.Printf(msg sent:%s,msg) msg = $ b ReadConsole(网络chan< - 字符串){ console:= bufio.NewReader (os.Stdin) 用于{ $ b $ line,err:= console.ReadString('\ n') if err!=无{ 恐慌(错误) }其他{ 网络} } func main(){ 监听器,err:= net.Listen(tcp,localhost:6666) if err!= nil { panic(err)} println(listen on+ l istener.Addr()。String()) consoleToNetwork:= make(chan string) $ b去AcceptConnections(监听者,consoleToNetwork) ReadConsole (consoleToNetwork)} 服务器控制台如下所示: 听127.0.0.1:6666 客户端连接您好!从控制台读取:hi there! msg发送:你好! 这一个应该失败从控制台读取:这一个应该失败 msg发送:这个应该失败这一个实际上失败从控制台读取:这一个实际上失败无法发送消息到网络:write tcp 127.0.0.1:51194:损坏的管道 客户端看起来像这样: package main import(net osio //bufio //fmt) func cp(dst io .Writer,src io.Reader,errc chan< - error){ // - 从src读取并写入dst // -blocks,直到EOF // - EOF不是错误 _,err:= io.Copy(dst,src) //当io.Copy返回 errc时,将错误推送到频道} func StartCommunication(conn net.Conn){ //为错误创建一个通道 errc:= make(chan error) //读取连接并打印到控制台 go cp(os.Stdo ut,conn,errc) //读取用户输入并写入连接 go cp(conn,os.Stdin,errc) //等待无或错误到达 err:=< - errc if err!= nil { println(cp error:,err.Error()) func main(){ servAddr:=localhost:6666 tcpAddr,err:= net.ResolveTCPAddr(tcp,servAddr) if err!= nil { println(ResolveTCPAddr failed:,err.Error()) os.Exit 1)} conn,err:= net.DialTCP(tcp,nil,tcpAddr) 如果err!= nil { println(net.DialTCP失败:,err.Error()) os.Exit(1)} 推迟conn.Close() StartCommunication(conn) } 编辑:按照JimB的建议,我提出了一个工作示例。消息不会再丢失,并以新的连接重新发送。我不太清楚在不同的go例程之间使用共享变量(connWrap.IsFaulted)有多安全。 包主要 进口(净osbufiofmt) 类型连接struct { IsFaulted bool Conn net.Conn } func StartWritingToNetwork(connWrap * Connection,errChannel chan< - error,msgStack chan字符串){ 为{ msg:=< - msgStack 如果connWrap.IsFaulted { / /放回另一个连接 msgStack< - msg return } _,err:= connWrap.Conn.Write([ ]字节(msg)) if err!= nil { fmt.Printf(发送消息失败到网络:%v \ n,错误) connWrap.IsFaulted = true msgStack< - msg er rChannel< - err return } else { fmt.Printf(msg sent:%s,msg) func StartReadingFromNetwork(connWrap * Connection,errChannel chan< - error){ network:= bufio.NewReader( connWrap.Conn)$! $ b for(!connWrap.IsFaulted){ $ b $ line,err:= network.ReadString('\\\') if err!= nil { fmt.Printf(网络读取失败:%v \ n,错误) connWrap.IsFaulted = true errChannel< - err } else { fmt.Printf(%s,line)} } $ b $ func AcceptConnections(监听器net.Listener,控制台chan字符串){ errChannel:= make(chan error) for { conn,err:= listener.Accept() if err!= nil { panic(err) $ b $ fmt.Printf(client connected\\\) connWrap:= Connection {false,conn} 转到StartReadingFromNetwork(& connWrap,errChannel) 转到StartWritingToNetwork(& connWrap,errChannel,console) //阻塞,直到发生错误< - errChannel $ b func ReadConsole(网络chan< - 字符串){ console:= bufio.NewReader(os.Stdin)$ $ b $ 恐慌(错误) }其他{ 网络} } } func main(){ 监听器,err:= net.Listen(tcp,localhost:6666) 如果err!= nil { panic(err)} println(listen on+ listener.Addr()。String()) consoleToNetwo rk:= make(chan string) $ b $ go AcceptConnections(listener,consoleToNetwork) ReadConsole(consoleToNetwork)} 解决方案这不是专用的,它是底层TCP套接字的人工产物, p> 一个体面的TCP终止步骤图在这个页面的底部: http://www.tcpipguide.com/free/t_TCPConnectionTermination-2.htm 简单的版本是,当客户端关闭其套接字时,它发送一个FIN,并从服务器接收一个ACK。然后等待服务器执行相同的操作。不是发送FIN,而是发送更多的数据,这些数据被丢弃,而客户端套接字现在假设来自您的更多数据是无效的,因此下次您发送RST时,会出现什么情况进入你看到的错误。 回到你的程序,你需要以某种方式处理这个问题。通常你可以想到谁负责发起一个发送,也负责发起终止,因此你的服务器应该假设它可以继续发送,直到 it 关闭连接,或者遇到错误。如果您需要更可靠地检测客户端关闭,您需要在协议中有某种客户端响应。这样recv可以在套接字上调用并返回0,这会提醒你关闭连接。 在go中,这将从连接的Read方法(或从您的案例中的Copy中)返回一个EOF错误。 SetWriteDeadline不起作用,因为一个小的写入将会通过并被无声地丢弃,或者客户端最终会用RST响应,给你一个错误。 I'm trying to detect sending failures by inspecting the error returned by golang TCPConn.Write, but it's nil. I also tried using TCPConn.SetWriteDeadline without success.That's how things happen:the server startsa client connectsthe server sends a message and the client receives itthe client shuts downthe server sends one more message: no errorthe server sends the third message: only now the error appearsQuestion: why only the second message to a non-existing client results in an error? How should the case be handled properly?The code follows:package mainimport ( "net" "os" "bufio" "fmt" "time")func AcceptConnections(listener net.Listener, console <- chan string) { msg := "" for { conn, err := listener.Accept() if err != nil { panic(err) } fmt.Printf("client connected\n") for { if msg == "" { msg = <- console fmt.Printf("read from console: %s", msg) } err = conn.SetWriteDeadline(time.Now().Add(time.Second)) if err != nil { fmt.Printf("SetWriteDeadline failed: %v\n", err) } _, err = conn.Write([]byte(msg)) if err != nil { // expecting an error after sending a message // to a non-existing client endpoint fmt.Printf("failed sending a message to network: %v\n", err) break } else { fmt.Printf("msg sent: %s", msg) msg = "" } } }}func ReadConsole(network chan <- string) { console := bufio.NewReader(os.Stdin) for { line, err := console.ReadString('\n') if err != nil { panic(err) } else { network <- line } }}func main() { listener, err := net.Listen("tcp", "localhost:6666") if err != nil { panic(err) } println("listening on " + listener.Addr().String()) consoleToNetwork := make(chan string) go AcceptConnections(listener, consoleToNetwork) ReadConsole(consoleToNetwork)}The server console looks like this:listening on 127.0.0.1:6666client connectedhi there!read from console: hi there!msg sent: hi there!this one should failread from console: this one should failmsg sent: this one should failthis one actually failsread from console: this one actually failsfailed sending a message to network: write tcp 127.0.0.1:51194: broken pipeThe client looks like this:package mainimport ( "net" "os" "io" //"bufio" //"fmt")func cp(dst io.Writer, src io.Reader, errc chan<- error) { // -reads from src and writes to dst // -blocks until EOF // -EOF is not an error _, err := io.Copy(dst, src) // push err to the channel when io.Copy returns errc <- err}func StartCommunication(conn net.Conn) { //create a channel for errors errc := make(chan error) //read connection and print to console go cp(os.Stdout, conn, errc) //read user input and write to connection go cp(conn, os.Stdin, errc) //wait until nil or an error arrives err := <- errc if err != nil { println("cp error: ", err.Error()) }}func main() { servAddr := "localhost:6666" tcpAddr, err := net.ResolveTCPAddr("tcp", servAddr) if err != nil { println("ResolveTCPAddr failed:", err.Error()) os.Exit(1) } conn, err := net.DialTCP("tcp", nil, tcpAddr) if err != nil { println("net.DialTCP failed:", err.Error()) os.Exit(1) } defer conn.Close() StartCommunication(conn)}EDIT: Following JimB's suggestion I came up with a working example. Messages don't get lost any more and are re-sent in a new connection. I'm not quite sure though how safe is it to use a shared variable (connWrap.IsFaulted) between different go routines.package mainimport ( "net" "os" "bufio" "fmt")type Connection struct { IsFaulted bool Conn net.Conn}func StartWritingToNetwork(connWrap * Connection, errChannel chan <- error, msgStack chan string) { for { msg := <- msgStack if connWrap.IsFaulted { //put it back for another connection msgStack <- msg return } _, err := connWrap.Conn.Write([]byte(msg)) if err != nil { fmt.Printf("failed sending a message to network: %v\n", err) connWrap.IsFaulted = true msgStack <- msg errChannel <- err return } else { fmt.Printf("msg sent: %s", msg) } }}func StartReadingFromNetwork(connWrap * Connection, errChannel chan <- error){ network := bufio.NewReader(connWrap.Conn) for (!connWrap.IsFaulted) { line, err := network.ReadString('\n') if err != nil { fmt.Printf("failed reading from network: %v\n", err) connWrap.IsFaulted = true errChannel <- err } else { fmt.Printf("%s", line) } }}func AcceptConnections(listener net.Listener, console chan string) { errChannel := make(chan error) for { conn, err := listener.Accept() if err != nil { panic(err) } fmt.Printf("client connected\n") connWrap := Connection{false, conn} go StartReadingFromNetwork(&connWrap, errChannel) go StartWritingToNetwork(&connWrap, errChannel, console) //block until an error occurs <- errChannel }}func ReadConsole(network chan <- string) { console := bufio.NewReader(os.Stdin) for { line, err := console.ReadString('\n') if err != nil { panic(err) } else { network <- line } }}func main() { listener, err := net.Listen("tcp", "localhost:6666") if err != nil { panic(err) } println("listening on " + listener.Addr().String()) consoleToNetwork := make(chan string) go AcceptConnections(listener, consoleToNetwork) ReadConsole(consoleToNetwork)} 解决方案 This isn't Go specific, and is a artifact of the underlying TCP socket showing through.A decent diagram of the TCP termination steps is at the bottom of this page:http://www.tcpipguide.com/free/t_TCPConnectionTermination-2.htmThe simple version is that when the client closes its socket, it sends a FIN, and receives an ACK from the server. It then waits for the server to do the same. Instead of sending a FIN though, you're sending more data, which is discarded, and the client socket now assumes that any more data coming from you is invalid, so the next time you send you get an RST, which is what bubbles up into the error you see.Going back to your program, you need to handle this somehow. Generally you can think of whomever is in charge of initiating a send, is also in charge of initiating termination, hence your server should assume that it can continue to send until it closes the connection, or encounters an error. If you need to more reliably detect the client closing, you need to have some sort of client response in the protocol. That way recv can be called on the socket and return 0, which alerts you to the closed connection.In go, this will return an EOF error from the connection's Read method (or from within the Copy in your case). SetWriteDeadline doesn't work because a small write will go though and get dropped silently, or the client will eventually respond with an RST, giving you an error. 这篇关于golang TCPConn.SetWriteDeadline似乎没有按预期工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云!
08-22 14:11