Spring WebSockets中的Broken pipe和客户端未收到消息

14

我在使用websocket时遇到了一些问题:

  • java.io.IOException: Broken Pipe
  • 客户端无法接收消息
    简短总结

我需要知道的主要内容:

  • 请列举所有可能导致客户端关闭连接的情况(除了刷新或关闭标签页)。
  • 除了服务器向已经断开的连接发送消息以外,是否还会出现“Broken Pipe”异常?如果是,那么如何处理?
  • 服务器为什么有时不会发送消息,即使它确实发送了心跳包?(当发生这种情况时,我需要重新启动应用程序才能让它再次工作。这是一个可怕的解决方法,因为它已经在生产中使用了。)


我有一个使用websocketsSpringMVC项目;客户端使用SockJS,服务端使用org.springframework.web.socket.handler.TextWebSocketHandler

一个JSON在服务器端生成并发送到客户端。有时候,我会遇到java.io.IOException: Broken Pipe异常。我在谷歌和StackOverflow上搜索了很多东西,但是找到了很多我不理解的东西,但原因可能是连接已经在客户端关闭,但服务器仍在发送消息(例如心跳包)。这听起来合理吗?此异常出现的其他原因是什么?除了刷新或关闭标签页,导致客户端关闭连接的原因有哪些?

还有,有时候客户端无法从服务器接收任何消息,尽管服务器应该发送它们。我在发送消息之前和之后进行记录,两个记录语句都被打印出来。有人知道为什么会发生这种情况吗?Chrome的控制台日志中没有错误。刷新页面不起作用,我需要重新启动Spring项目...

如果您需要更多信息,请留言。


客户端

function connect() {
    var socket = new SockJS('/ws/foo');

    socket.onopen = function () {
        socket.send(fooId); // ask server for Foo with id fooId.
    };

    socket.onmessage = function (e) {
        var foo = JSON.parse(e.data);
        // Do something with foo.
    };
}

服务器端
服务

@Service
public class FooService implements InitializingBean {
    public void updateFoo(...) {
        // Update some fields of Foo.
        ...
        // Send foo to clients.
        FooUpdatesHandler.sendFooToSubscribers(foo);
    }
}

WebSocket处理程序

public class FooUpdatesHandler extends ConcurrentTextWebSocketHandler {
// ConcurrentTextWebSocketHandler taken from https://github.com/RWTH-i5-IDSG/BikeMan (Apache License version 2.0)

    private static final Logger logger = LoggerFactory.getLogger(FooUpdatesHandler.class);
    private static final ConcurrentHashMap<String, ConcurrentHashMap<String, WebSocketSession>> fooSubscriptions =
            new ConcurrentHashMap<>();

    public static void sendFooToSubscribers(Foo foo) {
        Map<String, WebSocketSession> sessionMap = fooSubscriptions.get(foo.getId());

        if (sessionMap != null) {
            String fooJson = null;
            try {
                fooJson = new ObjectMapper().writeValueAsString(foo);
            } catch (JsonProcessingException ignored) {
                return;
            }

            for (WebSocketSession subscription : sessionMap.values()) {
                try {
                    logger.info("[fooId={} sessionId={}] Sending foo...", foo.getId(), subscription.getId());
                    subscription.sendMessage(new TextMessage(fooJson));
                    logger.info("[fooId={} sessionId={}] Foo send.", foo.getId(), subscription.getId());
                } catch (IOException e) {
                    logger.error("Socket sendFooToSubscribers [fooId={}], exception:  ", foo.getId(), e);
                }
            }
        }
    }
}
2个回答

1

只是一种有根据的猜测:检查你的网络设备。也许存在配置错误的防火墙终止这些连接; 或者更糟糕的是,损坏的网络设备导致连接终止。如果您的服务器有多个NIC(很可能是这种情况),那么使用这些NIC存在一些配置错误,或者通过不同的NIC连接到服务器存在问题。


奇怪的是,通常一切都很顺利。多个设备(移动设备和笔记本电脑)都有一个websocket连接,并且每个设备都会收到Foo的JSON。但是有时候(大约每两周一次),会发生“重”断开管道(请参见@Pavel Uvarov答案中的我的评论)。websocket连接仍然存在,服务器仍然发送心跳,只是不再发送Foo的JSON。我不认为这是防火墙问题。还有其他想法吗? - J. Kamans
这会影响到“重型”断管异常后的任何客户端,还是只影响引起异常的特定客户端? - Mr.Radar
它会影响所有客户端。当它出现问题(无法使用JSON),客户端会联系我。我会通过在多个选项卡中打开网站来测试是否真的出现了问题。当“重型”断开管道发生时,所有选项卡都会出现相同的问题。那时唯一的解决方案就是重新启动生产服务器上的Java程序。 - J. Kamans

1
如果此问题是意外发生的,那么可能存在某些缓存问题-请检查Spring或SocksJS是否具有用于套接字交互的自己的缓存。
这是否发生在您的设备上(或您控制的设备上)?
此外,我建议您使用一些网络数据包分析器,如wireshark。使用这样的工具,您将在线查看当前的网络活动。
一些外部原因可能会在不正确停止连接的情况下破坏连接(您无法在没有连接检查的情况下进行管理):
- 设备暂停/关闭电源 - 网络故障 - 浏览器在某些错误时关闭
我认为这只是可能导致连接断开的完整列表的一小部分。

它发生在手机和笔记本电脑上。 - J. Kamans
有不同“类型”的Broken Pipe异常。我可以通过频繁刷新浏览器来生成一个,但在最后一次刷新之后,一切仍然正常工作(服务器仍将Foo的JSON发送到客户端)。但有时我会遇到一个Broken Pipe异常,它非常“严重”,服务器不再发送JSON(即使我刷新页面)。奇怪的是,服务器仍然发送心跳,并且服务器仍然记录“发送消息”和“消息已发送”(Session.sendMessage方法之前和之后的日志)。我需要重新启动整个Java程序才能解决这个问题... - J. Kamans
有任何想法为什么会发生“沉重的”破管错误吗?不幸的是,这并不是由你列出的原因造成的。 - J. Kamans
@J.Kamans,你有用过任何网络数据包分析器来检查网络交互吗? - Pavel Uvarov
我使用Chrome,按F12,进入网络选项卡。在那里我可以看到“websocket”,并且可以查看所有的websocket消息。当一切正常时,我可以看到“o”(打开),“h”(心跳)以及JSON。Websocket frames Chrome.png - J. Kamans

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接