在同一个端口上监听TCP和UDP请求

6
我正在编写一套客户端/服务器程序。根据客户端请求的操作,我使用TCP或UDP请求。实现客户端很简单,因为我可以轻松地打开任何协议的连接并将请求发送到服务器端。另一方面,在服务器端,我希望在同一端口上同时监听UDP和TCP连接。此外,我希望服务器为每个连接请求打开新线程。我采用了链接文本中解释的方法。我通过为每个TCP/UDP请求创建新线程来扩展此代码示例。如果仅使用TCP,则可以正常工作,但尝试进行UDP绑定时会失败。请给我任何建议,如何纠正这个问题。谢谢。以下是服务器代码:
public class Server {
public static void main(String args[]) {
    try {
        int port = 4444;
        if (args.length > 0)
            port = Integer.parseInt(args[0]);

        SocketAddress localport = new InetSocketAddress(port);

        // Create and bind a tcp channel to listen for connections on.
        ServerSocketChannel tcpserver = ServerSocketChannel.open();
        tcpserver.socket().bind(localport);

        // Also create and bind a DatagramChannel to listen on.
        DatagramChannel udpserver = DatagramChannel.open();
        udpserver.socket().bind(localport);

        // Specify non-blocking mode for both channels, since our
        // Selector object will be doing the blocking for us.
        tcpserver.configureBlocking(false);
        udpserver.configureBlocking(false);

        // The Selector object is what allows us to block while waiting
        // for activity on either of the two channels.
        Selector selector = Selector.open();

        tcpserver.register(selector, SelectionKey.OP_ACCEPT);
        udpserver.register(selector, SelectionKey.OP_READ);
        
        System.out.println("Server Sterted on port: " + port + "!");
        
        //Load Map
        Utils.LoadMap("mapa");
        System.out.println("Server map ... LOADED!");
        
        // Now loop forever, processing client connections
        while(true) {
            try { 
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                
                // Iterate through the Set of keys.
                for (Iterator<SelectionKey> i = keys.iterator(); i.hasNext();) {
                    SelectionKey key = i.next();
                    i.remove();

                    Channel c = key.channel();

                    if (key.isAcceptable() && c == tcpserver) {
                        new TCPThread(tcpserver.accept().socket()).start();
                    } else if (key.isReadable() && c == udpserver) {
                        new UDPThread(udpserver.socket()).start();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        System.err.println(e);
        System.exit(1);
    }
}

UDPThread 代码:

public class UDPThread extends Thread {
private DatagramSocket socket = null;

public UDPThread(DatagramSocket socket) {
    super("UDPThread");
    this.socket = socket;
}

@Override
public void run() {
    byte[] buffer = new byte[2048];
    try {           
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        socket.receive(packet);
        
        String inputLine = new String(buffer);
        String outputLine = Utils.processCommand(inputLine.trim());
        
        DatagramPacket reply = new DatagramPacket(outputLine.getBytes(), outputLine.getBytes().length,
                                                  packet.getAddress(), packet.getPort());
        socket.send(reply);
        
    } catch (IOException e) {
        e.printStackTrace();
    }
    socket.close(); 
}

我收到:

Exception in thread "UDPThread" java.nio.channels.IllegalBlockingModeException
at sun.nio.ch.DatagramSocketAdaptor.receive(Unknown Source)
at server.UDPThread.run(UDPThread.java:25)

10倍


失败原因是什么?你有更多的信息吗,比如错误信息或示例代码? - Marcus Adams
1
请详细说明“它失败”的情况。 - BalusC
3个回答

3
它应该可以工作。这段代码的一个问题似乎是ByteBuffer大小设置为0,这意味着数据报被丢弃(正如注释中所提到的)。如果您需要通过UDP接收任何信息,并且您在可靠的网络上,则可以将大小设置得很大,并接收由多个数据包组成的大型数据报。否则,在不可靠的网络上,将其设置为MTU大小。确保在接收其中任何内容后翻转ByteBuffer。
此外,为每个请求创建新线程是一个坏主意,为每个在HashMap中接收到的不同IP创建一个“会话”线程,然后在会话对象上执行guarded block。传入新信息后,唤醒在该对象上休眠的线程。您拥有的选择器代码旨在以这种方式避免创建线程。
编辑:根据上面的代码,您创建了一个数据报通道,然后使用套接字直接接收数据报?这是没有意义的。只有在绑定通道之后才使用通道方法。而且,不要在单独的线程中执行此操作。您的代码不是线程安全的,会出现问题。将接收到的信息传递给前面提到的单独的“会话”线程。选择器的设计目的是告诉您哪些通道可以无阻塞读取(尽管阻塞被禁用,所以它将告诉您哪些通道具有要读取的数据)。

我理解你使用会话线程的观点,我同意你的看法,但是为了我的目的,每个连接最好使用一个线程。至于ByteBuffer,我没有使用它,看起来你没有看到我上传的代码,只有链接。谢谢。 - msaveski
UDP套接字没有连接,只有TCP套接字才有。线程只是将处理过程与主选择线程分开。不管怎样,这就是Apache MINA的工作方式 :) - Chris Dennett
如果我理解正确的话,您是说制作新的UDP线程没有意义,而是在服务器类中接收信息,并在新线程中进一步处理它?顺便问一下,有没有更优雅的方法来监听TCP和UDP请求? - msaveski
线程不是必需的,但它们可以增加可扩展性(如果您发现无法处理那么多数据,则允许选择线程执行更多工作)。至于同时监听UDP和TCP请求,请使用两个选择器线程,一个用于TCP,另一个用于UDP,并且您可以分离逻辑。 - Chris Dennett
读取数据报,通过setNextDatagramMessage(..)方法将其传递给线程并存储为字段,然后唤醒该线程 :) 它将存储数据报,将字段设置为null,然后消耗该数据报。您可以对TCP执行相同的操作。无论您得到什么对象(ByteBuffer或其他),只需通过即可。 - Chris Dennett
显示剩余2条评论

0
据我所知,您应该能够在同一端口上同时侦听TCP连接和UDP消息。如果您发布了UDP代码以及您看到的异常+堆栈跟踪,那将会很有帮助。

-1

在非阻塞模式下,您不能使用DatagramSocket.receive()。您必须直接使用DatagramChannelread()receive()方法。

实际上,由于您正在使用非阻塞模式和Selector,因此很难理解为什么您还要使用UDPThread。只需调用udpserver.receive()而不是启动线程即可。


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