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