【问题标题】:How to ignore messages from disconnected channel如何忽略来自断开连接的频道的消息
【发布时间】:2013-01-15 16:07:10
【问题描述】:

我正在为多人游戏实现简单的网络服务器。我只是想弄清楚Netty。

我通过 telnet 测试服务器。我所做的是将消息广播到所有频道。它工作顺利。我还可以在关闭事件时从地图中删除频道,这很好。

但问题是,如果其中一个客户端意外断开,在关闭回调之前,调用发送方断开通道的 messageReceived 回调。

如何正确忽略来自断开连接的客户端的消息? 我在messagedReceived 中使用StringBuffer,但对于这种情况StringBuffer.toString 也不是正确的字符串。最后断开的通道向其他通道和自身广播无意义的消息,当接收通道本身时抛出异常Connection reset by peer 这是正常的,因为频道本身暂时不可用。

这里是代码;

     @Override
     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {

            System.out.println();
            System.out.println("------------------");


            Channel current = e.getChannel();
            System.out.println("SenderChannel:"+current.getId());

            if(!current.isOpen())
                System.out.println("Not Open");

            ChannelBuffer buf = (ChannelBuffer) e.getMessage();

            StringBuffer sbs = new StringBuffer();

            while(buf.readable()) {
                sbs.append((char) buf.readByte());
            }

            String s = sbs.toString();


            System.out.println(s);

            String you = "You:" + s;
            String other = "Other:" + s;



            byte [] uResponse = you.getBytes();
            byte [] otherResponse = other.getBytes();


            Iterator iterator = channelList.entrySet().iterator();
            while(iterator.hasNext()){
                  Map.Entry pairs = (Map.Entry)iterator.next();

                  Integer key = (Integer)pairs.getKey();
                  Channel c = (Channel)pairs.getValue();

                  System.out.println("ReceiverChannel:"+c.getId());

                  if(key != current.getId())
                      c.write(ChannelBuffers.wrappedBuffer(otherResponse));
                  else
                      c.write(ChannelBuffers.wrappedBuffer(uResponse));

            }

     }


    @Override
    public void channelDisconnected(ChannelHandlerContext ctx,
            ChannelStateEvent e){

        Channel ch = e.getChannel();


            channelList.remove(ch.getId());
            System.out.println();
            System.out.println("*****************");
            System.out.println("DisconnectEvent:"+ch.getId());
            System.out.println("*****************");
            System.out.println();
            ch.close();

    }

【问题讨论】:

  • 与问题和答案无关:您可以使用 StringBuilder 而不是 StringBuffer。在使用的范围内不需要同步。
  • StringBuilder 是有道理的,但是对于您的其余评论,我没明白吗?
  • :) 同步需要资源。因此,使用不需要同步的非线程安全类将为您节省一些 cpu%
  • 好的,我明白了,但是@Fildor 问题的答案呢
  • 恐怕我没有。除了采用 Soroush 的概念。如果连接本身不是用户登录或退出的“状态”,则必须保持上下文观察该状态,并且必须在写入时检查该状态并相应地过滤消息。

标签: java sockets netty


【解决方案1】:

您无法以您希望的方式解决问题。如果出现网络问题,那么从技术上讲,发件人可以随时断开连接,例如

  • 只要线程进入 messageReceived
  • 在遍历 channelList 时
  • 在您遍历 channelList 时,但在您回显给发送者之后
  • 广播消息后

当 messageReceived 正在处理时,Netty 无法引发断开连接的事件,因为您正在运行将引发事件的线程(除非您的管道中有一个无序执行处理程序)。正确的解决方案实际上取决于您的应用程序。如果广播导致所有其他接收者响应,那么让服务器抑制任何发往不再连接的客户端的消息可能会更好/更容易。

此外,如果您真的要使用字符串,请查看 StringEncoder / StringDecoder。您的代码不能保证消息事件缓冲区包含完整的字符串。

【讨论】:

  • 谢谢,解决我的问题是在管道中使用 StringEncoder / StringDecoder。由于解码阶段,现在在 messageReviced 中没有看到断开连接。可能因为解码而被忽略了。
【解决方案2】:

如果这是用于多人游戏服务器,最好使用现有的 Netty 游戏服务器解决方案,例如 java game server。断开连接成为发送到session 的事件,由于它是事件驱动的,您可以编写自己的handler 来决定是否在同一会话中接收更多事件。由于事件按 FIFO 顺序排队,如果发生断开连接,则无需继续进行后续广播。

【讨论】:

  • 看起来很有趣,我一定会去看的。
  • 对了,这个问题怎么样?
【解决方案3】:

只需在每次发送周围放置一个 try/catch。如果其中一个失败,关闭相应的通道。

【讨论】:

  • 我不需要,因为已经覆盖了异常捕获。但问题是断开通道在发送其他通道期间无法抛出异常。其他人可以得到无意义的消息。只有当它尝试自己发送消息时抛出异常(对等方断开连接)。如果我没有回显给自己,我无法捕捉到异常。
  • @tylerdurden 我无法确定其中的任何一个,但是如果在套接字上执行 I/O 时遇到超时以外的任何异常,它就像门钉一样死了,你必须关闭它。
  • 是的,没错,这就是我在 exceptionCaught 中所做的。
【解决方案4】:

我不是 Java 开发人员。但从套接字的角度来看,这些数据在缓冲区中或在用户断开连接之前发送。因此,当您处于接收阶段时,用户仍处于连接状态,并且恰好在接收用户完成时已断开连接。所以我认为防止这种事情的最好方法是在每次接收数据后检查用户是否仍然连接。

在 C# 中,我个人使用此代码来检查用户是否仍处于连接状态:

if (client.Poll(0, SelectMode.SelectRead))
{
    byte[] checkConn = new byte[1];
    if (client.Receive(checkConn, SocketFlags.Peek) == 0)
        return false;
}
return true;

我不确定 Java 和 Netty(如果您的连接是 TCP),但这是我使用的,可以轻松将其转换为 Java。

【讨论】:

  • 那只判断客户端是否干净地关闭了连接,而且比较昂贵。最好只处理发生的发送错误:这样您就可以找到所有断开连接,而不仅仅是正常的断开连接,而且您不需要两个额外的系统调用来完成它。
猜你喜欢
  • 2012-04-16
  • 1970-01-01
  • 2018-11-23
  • 2011-01-19
  • 1970-01-01
  • 2021-09-09
  • 1970-01-01
  • 2015-10-28
  • 2012-11-30
相关资源
最近更新 更多