Go语言 如何解决写:WebSocket上的管道损坏?

2guxujil  于 2023-09-28  发布在  Go
关注(0)|答案(1)|浏览(329)

我正在为我的程序做一个进度条,并把它发送给客户端。我使用的IPC是WebSocket。但是我在向客户端更新进度条时遇到了write: broken pipe。此错误在第一次请求后发生。例如,在我的应用程序的初始运行,当我perfrom请求到服务器,没有错误。但是,当我执行另一个请求时,就会发生此错误。
即使有这个错误,但进度条实际上是工作尽管错误(进度条成功发送到客户端,直到100%)
我使用go fiber WebSocket作为我的服务器

// middleware
app.Use("/ws", func(c *fiber.Ctx) error {
  if websocket.IsWebSocketUpgrade(c) {
    c.Locals("allowed", true)
      return c.Next()
    }

    return fiber.ErrUpgradeRequired
})

// handler
func (s *downloaderService) progressBar(c *websocket.Conn) {
    channel := api.CreateChannel(c.Params("client"))
    done := make(chan bool)

    go func() {
        for {
            t, _, err := c.ReadMessage()
            if err != nil {
                log.Println("Error reading message:", err)
                return
            }

            if t == websocket.CloseMessage {
                done <- true
            }
        }
    }()

    for {
        select {
        case <-done:
            return
        case data, ok := <-channel.Subscribe():
            if !ok {
                return
            }

            progressBar := data.(downloader.Progressbar)

            payload, err := json.Marshal(progressBar)
            if err != nil {
                log.Println("Error marshalling data:", err)
                break
            }

            c.SetWriteDeadline(time.Now().Add(10 * time.Second))
            if err := c.WriteMessage(websocket.TextMessage, payload); err != nil {
                log.Println("Error sending progress data:", err)
                done <- true
            }
        }
    }
}

在客户端,我使用的是gorilla WebSocket

func main() {
    interrupt := make(chan os.Signal, 1)
    signal.Notify(interrupt, []os.Signal{syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGSTOP, os.Interrupt}...)

    ctx, cancel := context.WithCancel(context.Background())

    conn, res, err := websocket.DefaultDialer.DialContext(ctx, ws, nil)
    if err != nil {
        log.Fatalf("Error dialing websocket: %v. Status courlde %d", err, res.StatusCode)
        return
    }

    executeCommand(ctx)

    progressBar := progressbar()

    go func() {
        for {
            _, message, err := conn.ReadMessage()
            if err != nil {
                log.Println("Error reading message:", err)
                break
            }

            var progress progress
            if err := json.Unmarshal(message, &progress); err != nil {
                log.Println("Error unmarshalling message:", err)
                break
            }

            if progress.Done {
                truncateStore()
                cancel()
                break
            }

            progressBar.update(progress.Index, progress.Downloaded, progress.Size)
        }
    }()

    for {
        select {
        case <-ctx.Done():
            closeConn(ctx, conn)
            return
        case <-interrupt:
            stopDownload()
            closeConn(ctx, conn)
            return
        }
    }
}

func closeConn(ctx context.Context, conn *websocket.Conn) {
    if err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil {
        log.Println("Error sending close signal to server:", err)
        return
    }

    select {
    case <-ctx.Done():
    case <-time.After(time.Second):
    }
}

客户端是一个CLI,它将创建对服务器的请求。当进度条完成时,它将被终止。
下面是它看起来像

的样子

更新

在阅读了客户端的关闭消息后,我设法摆脱了管道破裂的错误。我得到了write: close sent

brjng4g3

brjng4g31#

错误write: broken pipe通常表示接收方(在本例中为客户端)在服务器可以写入之前关闭了连接。即使您已经注意到进度条在初始运行时工作,后续请求也可能遇到在服务器端写入消息和在客户端阅读/关闭连接之间存在竞争条件的情况。
以下是您可以采取的一些步骤,以诊断并解决问题:
1.确保持久连接:检查客户端是否在多个请求之间保持持久的WebSocket连接。如果客户端关闭WebSocket连接,然后尝试使用相同的关闭连接进行后续请求,您将遇到此类错误。
1.正常关机:确保服务器和客户端都正常关闭连接。您使用websocket.CloseMessage来指示客户端的关闭,但您还应该确保服务器确认这一点并从其端优雅地关闭连接。
1.错误处理:当前progressBar函数中的错误处理在出现错误时立即退出循环。如果您希望确保持续的通信,即使遇到临时错误,这也可能会有问题。考虑添加更精细的错误处理。
1.上下文处理:确保您与websocket.DefaultDialer.DialContext(ctx, ws, nil)一起使用的ctx不会过早取消,从而导致连接终止。您的代码确实建议您在progress.Donetruecancel上下文,但只需仔细检查应用程序的其余部分,以确保没有无意中取消上下文。
1.日志与调试:在服务器端和客户端都添加更多的日志,特别是在连接初始化、消息发送/接收和连接终止方面。这将给予您更清楚地了解操作的顺序,并可能有助于查明哪里出错。
1.多重连接:确保您没有无意中从客户端创建多个WebSocket连接,这可能会导致意外行为。
1.手柄连接闭合:确保您在客户端和服务器端都处理WebSocket的CloseHandler,以正常管理连接终止。
1.检查资源限制:确保您的服务器没有达到任何打开文件描述符限制或类似的资源限制,这可能会导致连接中断。
1.检查中间件:仔细检查您使用Go Fiber的任何中间件。确保它们不会干扰WebSocket连接。
如果您使用CLI客户端的方式是快速发出连续请求(例如快速重新运行CLI命令),则可能需要添加一个轻微的延迟,或者确保在启动新的WebSocket连接之前,之前的WebSocket连接已完全关闭。

相关问题