How to get UDP Server to push to Client when another Client sends to server

3.2k views Asked by At

I'm working on a simple Java chat program, mostly to learn about UDP and how computers talk with one another.

So far I have been able to set up the server to listen for the clients to connect to it, and I'm even able to redirect the messages from one client to another though the server - that is to say:

Client A --> Server --> Client B

I've gotten to the point where the server actually sends the packet (sock.send(packet)), but the catch is that the clients don't actually "know" to listen. They just know how to send to the server.

I tried setting up a run() for the client, similar to what I have with the server, but as soon as I pull up two clients my program crashes because I'm trying to listen on the same port.

Server code (Please ignore all the separator stuff, it's just a way I'm using to send different pieces of information for now):

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();
    }
}

Client Side

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;
    }
}

And finally, here is the way I tried to get the client to listen (this is in my main class):

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();    }

}

Here is the error I get when I try to run two Mains simultaneously (this error makes sense to me for the most part, I just don't know what else to do to get the client to listen):

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)

Thanks in advance for any help!

2

There are 2 answers

7
Juned Ahsan On BEST ANSWER

You can't run two listeners on the same port on a single machine/network interface. I offer this solution, which should be good enough to run a server and multiple clients on the same machine.

  1. On your server, maintain a list of available clients listening ports
  2. On your server maintain a map of clients and their listening ports
  3. Before starting a client listener, make a client connection to server and ask for the next available client port
  4. Server updates the client/port mapping
  5. Start a listener/serversocket on client listening on the port number received from server in last step
  6. Now suppose you have a server running on unique port, clients running on unique ports
  7. When a client sends a message to server intended for another client. Server simply retrieves the receiving client port and make a client connection to it the receiver to send the message.
1
David.Jones On

You shouldn't rebind the socket for the 'receiver' thread. Remove this line sock = new DatagramSocket(PORT_NUMBER+1);. You only need one socket to send and receive packets, so you shouldn't rebind the socket. Although, if you are waiting to receive a packet, this will stall your thread and you can't send out a packet, so if you need to do both concurrently, you should use two sockets. Does this fix your issue?