当另一个客户端发送到服务器时,如何让 UDP 服务器推送到客户端

Posted

技术标签:

【中文标题】当另一个客户端发送到服务器时,如何让 UDP 服务器推送到客户端【英文标题】:How to get UDP Server to push to Client when another Client sends to server 【发布时间】:2014-11-28 00:47:11 【问题描述】:

我正在开发一个简单的 Java 聊天程序,主要是为了了解 UDP 以及计算机如何相互通信。

到目前为止,我已经能够设置服务器来监听客户端连接到它,我什至能够通过服务器将消息从一个客户端重定向到另一个客户端 - 也就是说:

客户端 A --> 服务器 --> 客户端 B

我已经到了服务器实际发送数据包 (sock.send(packet)) 的地步,但问题是客户端实际上并不“知道”要监听。他们只知道如何发送到服务器。

我尝试为客户端设置一个 run(),类似于我对服务器的设置,但是一旦我启动两个客户端,我的程序就会崩溃,因为我试图在同一个端口上监听。

服务器代码(请忽略所有分隔符,这只是我现在用来发送不同信息的一种方式):

package com.jona.chat.UDP;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.sql.SQLException;
import java.util.TreeMap;

public class UDPServer extends Thread

    final int PORT_NUMBER = 4447;
    private String separator = "~!~--//1337"; //IGNORE THIS
    protected DatagramSocket sock = null;
    private TreeMap<String,InetAddress> nameIPTree = new TreeMap<String,InetAddress>(); //IGNORE THIS


    public static void main(String args[]) throws IOException

        UDPServer SERVER = new UDPServer();

        //calls the run() method
        SERVER.start();
    

    public UDPServer() throws IOException

        sock = new DatagramSocket(PORT_NUMBER);
    

    public void run()
        System.out.println("Waiting for Client");
        while(true)

            try

                //========================================================================================================
                //Prepare the packet to receive data from client
                //========================================================================================================

                //Buffer (byte array) that will receive the client's data
                byte[] buffer = new byte[512];

                //Create a packet using the empty buffer and its length
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

                //========================================================================================================
                //Receive the packet from the client, execute the statement, and get the result
                //========================================================================================================

                //Receive the packet
                sock.receive(packet);
                System.out.println("Server: Received packet from Client");

                //Extract the data
                String fromUser = new String(packet.getData(), 0, packet.getLength());
                //Parse data
                String[] instructions = fromUser.split(separator); //IGNORE THIS

                //Add UserName and IP to tree
                if(instructions[0].equals("LOGIN"))
                    System.out.println("Logged in!");
                    nameIPTree.put(instructions[1], packet.getAddress());

                    run();
                
                //Send message to recipient and upload to DB
                else if(instructions[0].equals("MESSAGE"))

                    //Create a string composed of the sender and the message
                    String toUser = instructions[2] + separator + instructions[3];

                    //Store the string in the buffer
                    buffer = toUser.getBytes();

                    //Make a new packet with the buffer, its length, the RECEPIENT'S IP (retrieved from tree, hence receiving user HAS TO BE LOGGED IN)
                    //and the port number the server uses

                    packet = new DatagramPacket(buffer, buffer.length, nameIPTree.get(instructions[2]), PORT_NUMBER+1);

                    //Send the packet
                    sock.send(packet);
                    System.out.println("Server: Sent result to Client:  " + toUser);
                

                
                catch (IOException  e)
                    e.printStackTrace();
                    break;
                
        
        System.out.println("Closing the socket");
        sock.close();
    

客户端

public class UDPClient extends Thread

    final int PORT_NUMBER = 4447;
    private String separator = "~!~--//1337";

    public String TalkToServer(String message)

        try
            //========================================================================================================
            //Create a datagram socket
            //========================================================================================================
            DatagramSocket sock = new DatagramSocket();

            //========================================================================================================
            //Connect & Send to server
            //========================================================================================================

            //Create a byte array called buffer that will hold the instructions to be sent to the server
            byte[] buffer =  message.getBytes("UTF-8");

            //Get the IP address to which the packet will be sent 
            InetAddress ipAddress = InetAddress.getByName("123.45.67");

            //Create a datagram packet which is composed of the buffer (message), its length, the IP address, 
            //and the port (matches with server's listening port) to send the data on
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length, ipAddress, PORT_NUMBER);

//same thing in both ifs, I know, I just wanted to see what it was doing 
                if(message.substring(0, 5).equals("LOGIN"))

                    System.out.println("Client: Logging in");


                //Send the packet
                sock.send(packet);
                System.out.println("Client: Sent packet to Server\nSent: " + message);

                sock.close();
                return null;
            
            if(message.substring(0, 7).equals("MESSAGE"))

                System.out.println("Client: Sending message to server");

                //Send the packet
                sock.send(packet);
                System.out.println("Client: Sent packet to Server\nSent: " + message);

                sock.close();
                return null;
            
        
        catch(IOException e)System.out.print(e);
        return null;
    

最后,这是我试图让客户倾听的方式(这是在我的主课中):

public static void main(String[] args) throws SocketException

    MainGUI listener = new MainGUI();
    listener.start();

...

public MainGUI() throws SocketException


    sock = new DatagramSocket(PORT_NUMBER+1);

public void run()


    byte[] buffer =  new byte[512];
    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

    try 
        sock.receive(packet);
        String fromUser = new String(packet.getData(), 0, packet.getLength());
        //Parse data
        String[] instructions = fromUser.split(separator);
        System.out.println("Received message: " + instructions[1] + " from " + instructions[0]);


     catch (IOException e)    e.printStackTrace();    


这是我尝试同时运行两个 Mains 时遇到的错误(这个错误在很大程度上对我来说是有意义的,我只是不知道要做什么才能让客户端听):

Exception in thread "main" java.net.BindException: Address already in use: Cannot bind
    at java.net.DualStackPlainDatagramSocketImpl.socketBind(Native Method)
    at java.net.DualStackPlainDatagramSocketImpl.bind0(Unknown Source)
    at java.net.AbstractPlainDatagramSocketImpl.bind(Unknown Source)
    at java.net.DatagramSocket.bind(Unknown Source)
    at java.net.DatagramSocket.<init>(Unknown Source)
    at java.net.DatagramSocket.<init>(Unknown Source)
    at java.net.DatagramSocket.<init>(Unknown Source)
    at com.jona.chat.UDP.UDPServer.<init>(UDPServer.java:28)
    at com.jona.chat.UDP.UDPServer.main(UDPServer.java:20)

提前感谢您的帮助!

【问题讨论】:

【参考方案1】:

您不能在单个机器/网络接口上的同一端口上运行两个侦听器。我提供了这个解决方案,它应该足以在同一台机器上运行一个服务器和多个客户端。

    在您的服务器上,维护可用客户端侦听端口的列表 在您的服务器上维护客户端及其侦听端口的地图 在启动客户端侦听器之前,与服务器建立客户端连接并请求下一个可用客户端端口 服务器更新客户端/端口映射 在客户端上启动监听器/serversocket,监听上一步从服务器接收到的端口号 现在假设您的服务器在唯一端口上运行,客户端在唯一端口上运行 当客户端向服务器发送消息时,该消息打算发送给另一个客户端。服务器只需检索接收客户端端口并与接收者建立客户端连接以发送消息。

【讨论】:

你至少可以监听 UDP 套接字。 - 有SO_REUSE_ADDR 选项,freecodetrips.blogspot.de/2009/06/… 或 (ab) 使用 MultiCastSocket 可以使用该选项 这很简单,也很棒。我会尝试一下,如果我让它工作,请告诉你。一般来说,这是一种好的做法——为每个客户端-服务器关系设置一个唯一的端口吗?再次感谢! @Jona 取决于客户端是在同一台机器上还是在其他机器上运行。如果客户端在不同的机器上运行。您可以为客户端和服务器套接字使用相同的端口。 @Jona 一旦你有基本的逻辑工作。您始终可以优化服务器上​​与客户端的连接。也许您想保持与活动客户端的连接。我会把它留给你的想象力和创造力来进一步优化它。 @JunedAhsan 你说的关于不同机器的最后一件事就是对我有用!我不知道你不能让两个客户端在同一个端口上监听。我打算让它在两台机器上运行,所以我像这样测试它并且它有效!再次感谢互联网英雄【参考方案2】:

您不应该为“接收者”线程重新绑定套接字。删除此行sock = new DatagramSocket(PORT_NUMBER+1);。你只需要一个套接字来发送和接收数据包,所以你不应该重新绑定套接字。虽然,如果你正在等待接收一个数据包,这将停止你的线程并且你不能发送一个数据包,所以如果你需要同时做这两个,你应该使用两个套接字。这能解决您的问题吗?

【讨论】:

谢谢大卫,我会先试试 Juned 的策略,然后再看看你的

以上是关于当另一个客户端发送到服务器时,如何让 UDP 服务器推送到客户端的主要内容,如果未能解决你的问题,请参考以下文章

如何准备 UDP 数据报以发送到服务器

聊天UDP客户端/服务器程序

C# 如何确保所需的 UDP 数据到达客户端/服务器?

发送 UDP 到本地服务器,但不通过环回接口

UDP打孔:无法从服务器发送到客户端

Python UDP服务器无法接受客户端连接