gpt4 book ai didi

tcp - golang TCPConn.SetWriteDeadline 似乎没有按预期工作

转载 作者:IT王子 更新时间:2023-10-29 01:23:54 27 4
gpt4 key购买 nike

我试图通过检查 golang TCPConn.Write 返回的错误来检测发送失败,但它是零。我也尝试使用 TCPConn.SetWriteDeadline没有成功。

事情就是这样发生的:

  1. 服务器启动
  2. 客户端连接
  3. 服务器发送消息,客户端接收
  4. 客户端关闭
  5. 服务器再发送一条消息:没有错误
  6. 服务器发送第三条消息:现在才出现错误

问题:为什么只有发送给不存在的客户端的第二条消息会导致错误?案件该如何妥善处理?

代码如下:

package main

import (
"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)
}

服务器控制台看起来像这样:

listening on 127.0.0.1:6666
client connected
hi there!
read from console: hi there!
msg sent: hi there!
this one should fail
read from console: this one should fail
msg sent: this one should fail
this one actually fails
read from console: this one actually fails
failed sending a message to network: write tcp 127.0.0.1:51194: broken pipe

客户端看起来像这样:

package main

import (
"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)

}

编辑:根据 JimB 的建议,我提出了一个工作示例。消息不再丢失,并在新连接中重新发送。我不太确定在不同的 go 例程之间使用共享变量 (connWrap.IsFaulted) 有多安全。

package main

import (
"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)
}

最佳答案

这不是 Go 特定的,而是底层 TCP 套接字显示的产物。

TCP 终止步骤的图解位于本页底部: http://www.tcpipguide.com/free/t_TCPConnectionTermination-2.htm

简单的版本是,当客户端关闭它的套接字时,它发送一个 FIN,并从服务器接收一个 ACK​​。然后它等待服务器做同样的事情。不过,您没有发送 FIN,而是发送了更多数据,这些数据被丢弃了,客户端套接字现在假定来自您的任何更多数据都是无效的,因此下次您发送时会收到 RST,这就是冒泡的原因进入您看到的错误。

回到您的程序,您需要以某种方式处理它。通常你可以想到谁负责发起发送,谁也负责发起终止,因此你的服务器应该假设它可以继续发送直到关闭连接,或遇到错误.如果您需要更可靠地检测客户端关闭,则需要在协议(protocol)中包含某种客户端响应。这样可以在套接字上调用 recv 并返回 0,这会提醒您连接已关闭。

在 go 中,这将从连接的 Read 方法(或在您的情况下从 Copy 中)返回 EOF 错误。 SetWriteDeadline 不起作用,因为小的写入会通过并被静默丢弃,或者客户端最终会响应 RST,给你一个错误。

关于tcp - golang TCPConn.SetWriteDeadline 似乎没有按预期工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15067286/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com