Search code examples
javac#androidudpdatagram

"An existing connection was forcibly closed by the remote host" in UDP Server on receiving


So after a struggle with threads, I succeeded to establish a stream between a client and a server. My client is a Java Android Studio Application and the server is a C# Console.

I want to be able to start the stream and stop it on button click from the client side. Then restart it anytime, stop, restart, stop.. etc.

I am good to go with start, however sometimes after the first Stop, or sometimes on the first restart, or sometimes on the second restart,, It pops An existing connection was forcibly closed by the remote host

on the PacketReceived = serverSocket.Receive(ref client);

I have no idea why and I am trying hard to debug it with no productivity. What is wrong with my logic or maybe syntax?

C# Server Code:

class UdpServer
{
    static void Main(string[] args)
    {
        // variables declaration block
        byte[] PacketReceived = new byte[1024];
        var MyToken = new CancellationTokenSource(); //create token for the thread cancel
        UdpClient serverSocket = new UdpClient(15000);
        string PacketMessage = "";

        int i = 0;
        while (true) // this while for keeping the server "listening"
        {
            Console.WriteLine("Waiting for a UDP client...");                                   // display stuff
            IPEndPoint client = new IPEndPoint(IPAddress.Any, 0);                               // prepare
            PacketReceived = serverSocket.Receive(ref client);                                  // receive packet
            PacketMessage = Encoding.ASCII.GetString(PacketReceived, 0, PacketReceived.Length); // get string from packet
            Console.WriteLine("Response from " + client.Address);                               // display stuff
            Console.WriteLine("Message " + i++ + ": " + PacketMessage + "\n");                  // display received string

            if (PacketMessage == "Start")
            {
                MyToken = new CancellationTokenSource(); // for the restart, need a new token
                Task.Run(() => Start(ref serverSocket, ref client), MyToken.Token); //start method on another thread
            }

            if (PacketMessage == "Stop")
            {
                MyToken.Cancel();
            }
        }
    }

    static public void Start(ref UdpClient serverSocket, ref IPEndPoint client)
    {
        int i = 0;
        byte[] dataToSend;
        while (true)
        {
            try
            {
                dataToSend = Encoding.ASCII.GetBytes(i.ToString());
                serverSocket.Send(dataToSend, dataToSend.Length, client);
                i++;
            }
            catch (Exception e)
            { }
        }
    }
}

Java Client Code:

public class MainActivity extends AppCompatActivity {

    String message;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button StrtBtn = (Button) findViewById(R.id.StartButton);
        Button StpBtn = (Button) findViewById(R.id.StopButton);

        // Start Button Click
        StrtBtn.setOnClickListener(
                new Button.OnClickListener() {
                    public void onClick(View v) {
                        message = "Start";
                        SendUdpMsg();
                    }
                }
        );

        // Stop Button Click
        StpBtn.setOnClickListener(
                new Button.OnClickListener() {
                    public void onClick(View v) {
                        message = "Stop";

                    }
                }
        );
    }

    public void SendUdpMsg()
    {
        Thread networkThread = new Thread() {

            // No local Host 127.0.0.1 in Android
            String host = "192.168.200.3"; // Server's IP
            int port = 15000;
            DatagramSocket dsocket = null;

            public void run() {
                try {
                    // Get the Internet address of the specified host
                    InetAddress address = InetAddress.getByName(host);

                    // wrap a packet
                    DatagramPacket packetToSend = new DatagramPacket(
                            message.getBytes(),
                            message.length(),
                            address, port);

                    // Create a datagram socket, send the packet through it.
                    dsocket = new DatagramSocket();
                    dsocket.send(packetToSend);

                    // Here, I am receiving the response
                    byte[] buffer = new byte[65535]; // prepare
                    DatagramPacket packetReceived = new DatagramPacket(buffer, buffer.length); // prepare

                    while (true)
                    {
                        if(message == "Start")
                        {
                            dsocket.receive(packetReceived); // receive packet
                            byte[] buff = packetReceived.getData(); // convert packet to byte[]
                            final String Response = new String(buffer, 0, packetReceived.getLength());

                            runOnUiThread(new Runnable()
                            {
                                @Override
                                public void run()
                                {
                                    // this is executed on the main (UI) thread
                                    final TextView TextOne = (TextView)findViewById(R.id.StatusText);
                                    TextOne.setText(Response);
                                }
                            });
                        }
                        else
                        {
                            // wrap a packet to send the Stop
                            packetToSend = new DatagramPacket(
                                    message.getBytes(),
                                    message.length(),
                                    address, port);

                            // Create a datagram socket, send the packet through it.
                            dsocket = new DatagramSocket();
                            dsocket.send(packetToSend);
                            break; // break the whole thread
                        }
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        networkThread.start();
    }
}

Solution

  • It's hard to diagnose a spurious error like that without a complete code example (in particular, a real client to test with). However, there are at least two clear problems with your code, either of which might cause an error like that if they hit just the wrong thing:

    1. You are passing serverSocket and client by-reference to the Start() method. There is no need to do so, and passing client by-reference introduces the possibility of modifying the client variable at the wrong time, causing either an attempt to send to Any, or causing an illegal reference (if running on x64 architecture). This is on top of the basic issue that sharing variables between threads without some kind of synchronization — using volatile at a minimum — comes with its own collection of problems (and you have the same thread safety issue in the client code, because of the way you use the message field as a signal between threads).
    2. You are never using the cancellation token to stop the thread. Passing the token to the Task.Run() method simply allows the Task class to correctly handle any cancellation exception that might occur. But it's up to your own code to throw that exception at the appropriate time. You never do, which means that after multiple messages from the client, you will wind up with multiple threads trying to send to the client.

    My bet is on the second of the above. Your client appears to leave port selection up to the host, so each time you send the "Start" method, the client port is likely to change. Once a previously used socket is closed, that port is invalid and will cause an error on the remote socket if it tries to send to that port. Of course your server (the remote socket) will try to send to that port, causing an error on the socket, which can be thrown by the Receive() method (instead of the Send() method as you might expect).


    By the way, and for what it's worth, you should avoid using the thread pool for long-running tasks. At the very least, if you are going to use Task.Run() to start your thread, indicate to .NET that your task is long-running by passing the appropriate TaskCreationOptions.LongRunning value: