• After 15+ years, we've made a big change: Android Forums is now Early Bird Club. Learn more here.

Apps My UDP application host lags very badly

Canvas

Newbie
I've been working on a UDP android server for some time, and at last, it works, it allows multiple clients to connect, they can move their block around the screen, and everyone connected can see, but my issue is, when a client moves it is instant on their screen, but on another clients screen, it doesn't happen until about 3seconds later, now I know this is a server issue, I think I know where the issue is but I'm not sure how I can try and fix it, my server uses 3 different ports, one to receive a message so a client wants to join, one port to allow clients to send their position, and one more port to send out the position of the clients connected blocks, so clients can update their game,

Here is my server code

[HIGH]
while( run )
{
/*addresses = gsID.addresses;
gsPos.addresses = addresses;
gsSendPos.addresses = addresses;
gsSendPos.positions = gsPos.positions;*/
//GameServerID
try
{
boolean dataGot = false;
if(gameServerID == null)
{
gameServerID = new DatagramSocket( portID );
}
//try to receive data
byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket( buf, buf.length );
try
{
//Log.d(TAG, "Wait for a client to connect");
gameServerID.setSoTimeout( 1 );
gameServerID.receive( packet );
dataGot = true;
}
catch (IOException e)
{
//Log.d(TAG, "Error with receiving data");
e.printStackTrace();
}
String data = new String( buf, 0, packet.getLength() );
if( dataGot == true )
{
//Log.d(TAG, data);
//Send out the ID to the client
byte[] bufer = new byte[256];
//Send a message "connect" to the host
String msg = Integer.toString( players );
players = players + 1;
bufer = msg.getBytes();
InetAddress address;
//Default ip address of the host
address = packet.getAddress();
DatagramPacket p = new DatagramPacket( bufer, bufer.length , address, portID );
//Send packet
gameServerID.send( p );
addresses.add( address );
}
}
catch (SocketException e)
{
Log.d(TAG, "Error with socket");
e.printStackTrace();
}
//Listen for a client to connect
catch (IOException e)
{
Log.d(TAG, "Error with I/O");
e.printStackTrace();
}

//GameServerPositions
try
{
boolean dataGot2 = false;
//Log.d(TAG, "Run the gamePositions code");
if(gamePositions == null)
{
gamePositions = new DatagramSocket( portPos );
}
//Receive position
//try to receive data
byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket( buf, buf.length );
try
{
gamePositions.setSoTimeout( 1 );
gamePositions.receive( packet );
dataGot2 = true;
}
catch (IOException e)
{
Log.d(TAG, "Error with receiving data");
e.printStackTrace();
}
String data = new String( buf, 0, packet.getLength() );

if( dataGot2 == true )
{
//Log.d(TAG, data);
String[] pos = data.split(":");
for(int i = 0;i<pos.length;i++)
{
//Log.d(TAG, pos);
}
xValues[ Integer.parseInt( pos[0] ) ] = Integer.parseInt( pos[1] );
yValues[ Integer.parseInt( pos[0] ) ] = Integer.parseInt( pos[2] );
}
}
catch (SocketException e)
{
Log.d(TAG, "Error with socket");
e.printStackTrace();
}
//Listen for a client to connect
catch (IOException e)
{
Log.d(TAG, "Error with I/O");
e.printStackTrace();
}

//GameServerSendPos
try
{
//Log.d(TAG, "Run the gamePositionsSend code");
String data = "";
if( gameSendPos == null )
{
gameSendPos = new DatagramSocket( portSend );
}

//create the string ready to be sent out
for(int i = 0; i < 8; i++)
{
if(xValues >= 0)
{
data += i + ":" + xValues + ":" + yValues + ":";
//Log.d(TAG, "data to be sent out : " + data);
}
}
//Log.d(TAG, "data finished");

byte[] bufer = new byte[256];
bufer = data.getBytes();
DatagramPacket p = null;

for(int i = 0;i < addresses.size(); i++)
{
if( addresses.get(i) != null )
{
p = new DatagramPacket( bufer, bufer.length , addresses.get(i), portSend );
gameSendPos.send( p );
}
}

}
catch (SocketException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}

} //End of while statement
[/HIGH]

gameServerID has a 1 millisecond timeout to stop the server from just stopping, I tried to implement it in a class using AsyncTask but the method just kept in its loop and didn't allow the other methods to run, I tried a Thread but android 4.0 and above doesn't like this because of UI and Threads. What it seems is happening is, gameServerID and gamePositions both have a setTimeOut(1), so both of them will listen for 1 millisecond, this allows for clients to join, and send their position to the server, but doing so SLAMS the server into a slug mode, and positions are sent out to the clients very, very slowly, here is a piece of the logcat output screen from the host

[HIGH]
04-05 02:49:42.093: D/dalvikvm(16836): JIT unchain all for threadid=11
04-05 02:49:42.140: D/dalvikvm(16836): GC_CONCURRENT freed 511K, 59% free 2756K/6663K, external 1657K/2137K, paused 2ms+2ms
04-05 02:49:42.148: W/System.err(16836): java.net.SocketTimeoutException: Try again
04-05 02:49:42.148: W/System.err(16836): at org.apache.harmony.luni.platform.OSNetworkSystem.recv(Native Method)
04-05 02:49:42.152: W/System.err(16836): at dalvik.system.BlockGuard$WrappedNetworkSystem.recv(BlockGuard.java:321)
04-05 02:49:42.156: W/System.err(16836): at org.apache.harmony.luni.net.PlainDatagramSocketImpl.doRecv(PlainDatagramSocketImpl.java:172)
04-05 02:49:42.156: W/System.err(16836): at org.apache.harmony.luni.net.PlainDatagramSocketImpl.receive(PlainDatagramSocketImpl.java:181)
04-05 02:49:42.156: W/System.err(16836): at java.net.DatagramSocket.receive(DatagramSocket.java:402)
04-05 02:49:42.160: W/System.err(16836): at com.example.gelorph_v1.gameServer.doInBackground(gameServer.java:139)
04-05 02:49:42.160: W/System.err(16836): at com.example.gelorph_v1.gameServer.doInBackground(gameServer.java:1)
04-05 02:49:42.160: W/System.err(16836): at android.os.AsyncTask$2.call(AsyncTask.java:185)
04-05 02:49:42.160: W/System.err(16836): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:306)
04-05 02:49:42.160: W/System.err(16836): at java.util.concurrent.FutureTask.run(FutureTask.java:138)
04-05 02:49:42.160: W/System.err(16836): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
04-05 02:49:42.160: W/System.err(16836): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
04-05 02:49:42.160: W/System.err(16836): at java.lang.Thread.run(Thread.java:1019)
04-05 02:49:42.167: W/System.err(16836): java.net.SocketTimeoutException: Try again
04-05 02:49:42.167: W/System.err(16836): at org.apache.harmony.luni.platform.OSNetworkSystem.recv(Native Method)
04-05 02:49:42.167: W/System.err(16836): at dalvik.system.BlockGuard$WrappedNetworkSystem.recv(BlockGuard.java:321)
04-05 02:49:42.171: W/System.err(16836): at org.apache.harmony.luni.net.PlainDatagramSocketImpl.doRecv(PlainDatagramSocketImpl.java:172)
04-05 02:49:42.171: W/System.err(16836): at org.apache.harmony.luni.net.PlainDatagramSocketImpl.receive(PlainDatagramSocketImpl.java:181)
04-05 02:49:42.171: W/System.err(16836): at java.net.DatagramSocket.receive(DatagramSocket.java:402)
[/HIGH]

If anyone could help me fix this last bug, it would be awesome.

Canvas

If you want more snippets of my code just let me know.
 
Some ideas:

  • Don't allocate 256 byte arrays all the time! Just make 1 and re-use it. Allocations and garbage collections are slow.
  • When you successfully read a position, try and read some more before you send it to all the clients. If your client code sends these packets frequently the socket will have a buffer full of these messages.
  • Only send updated positions to the clients if the positions have changed.
  • I guess the connect packets are not very common? Maybe try checking for them less frequently.
  • Don't print stack traces every time a timeout occurs, this isn't really an error, it is by design and you will be getting a huge number of them (proabably almost 1 every millisecond!)

You should really have another go at breaking the server down into multiple AsyncTasks, you will get much better responsiveness and burn through much less battery.
 
GeorgeN cheers for the reply, I never thought about these, also about the AsyncTasks, I have now created a worker thread in the MainActivity, so now the UI doesn't lag, but yea I still get quite low packet sending, so changing my buffer to 32 would maybe be better? also about wait to receive more than one is a idea i'm going to do, but still working on the worker thread :), I will of course get back to you with "better" code :)

Cheers
 
GREAT NEWS!!!
My server and client are working a lot better...but still a tiny problem
All of the threads now on the server have a sleep on them, and will never timeout, the application doesn't lock up, and the host can move with out a problem, but here is the problem. When a client joins
they can see the host move around without a problem, a very small small delay but that is fine, but when the client moves around it takes the host like 3 to 10 seconds before it sends those positions out to the other clients, but all other clients connected keep getting the the host positions fine.

Here is my server code as it has changed

MainActivity
[HIGH]
package com.example.gelorph_v1;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;

import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;

public class MainActivity extends Activity {

private static final String TAG = gameObject.class.getSimpleName();
//Server variables
private boolean serverStarted = false;
private int players = 0;
private int[] xValues = new int[8];
private int[] yValues = new int[8];
//Network encoding to tell java to only use 1byte not 2bytes per charcter of a string
private static final String encoding = "ISO-8859-1";
private ArrayList<InetAddress> addresses = new ArrayList<InetAddress>();
//int players = 1;
private DatagramSocket gameServerID = null;
private int portID = 45000;
private byte[] idBuffer = new byte[1];
private boolean ctsDataGot = false;
//GameServerPositions
private DatagramSocket gamePositions = null;
private int portPos = 45001;
private byte[] positionBuffer = new byte[18];
//GameServerSendPos
private DatagramSocket gameSendPos = null;
private byte[] sendPositionBuffer = new byte[158];
private int portSend = 45002;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Turn off title
requestWindowFeature(Window.FEATURE_NO_TITLE);
//Make the application full screen
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);

setContentView( new gamePanel( this ) );

TextView textView = new TextView(this);
textView.setTextSize(40);
String message = "hello";
textView.setText(message);

Log.d( TAG, "View added" );
//Server method
Runnable server = new Runnable() {
public boolean running = true;
public void run() {
while(running){
onServer(); // Make sure this blocks in some way
}
}
};
new Thread(server).start();
}

private void updateServer()
{
//Log.d(TAG, "testing");
}

private void connectToServer()
{
boolean run = true;
while (run)
{
try
{
ctsDataGot = false;
//if(gameServerID == null)
//{
//gameServerID = new DatagramSocket( portID );
//}
//try to receive data
DatagramPacket packet = new DatagramPacket( idBuffer, idBuffer.length );
try
{
//Log.d(TAG, "Wait for a client to connect");
//gameServerID.setSoTimeout( 1 );
gameServerID.receive( packet );
ctsDataGot = true;
}
catch (IOException e)
{
//Log.d(TAG, "Error with receiving data");
e.printStackTrace();
}
String data = new String( idBuffer, 0, packet.getLength() );
if( ctsDataGot == true )
{
//Log.d(TAG, data);
//Send out the ID to the client
//Send a message "connect" to the host
String msg = Integer.toString( players );
players = players + 1;
idBuffer = msg.getBytes();//may need to use a buffer here
InetAddress address;
//Default ip address of the host
address = packet.getAddress();
DatagramPacket p = new DatagramPacket( idBuffer, idBuffer.length , address, portID );
//Send packet
gameServerID.send( p );
addresses.add( address );
Thread.sleep( 250 );
}
}
catch (SocketException e)
{
//Log.d(TAG, "Error with socket");
//e.printStackTrace();
}
//Listen for a client to connect
catch (IOException e)
{
//Log.d(TAG, "Error with I/O");
//e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
}
}

private void receivePositions()
{
boolean run = true;
while( run )
{
try
{
boolean dataGot2 = false;
//Log.d(TAG, "Run the gamePositions code");
//if(gamePositions == null)
//{
//gamePositions = new DatagramSocket( portPos );
//}
//Receive position
//try to receive data
DatagramPacket packet = new DatagramPacket( positionBuffer, positionBuffer.length );
try
{
//gamePositions.setSoTimeout( 1 );
gamePositions.receive( packet );
dataGot2 = true;
}
catch (IOException e)
{
//Log.d(TAG, "Error with receiving data");
//e.printStackTrace();
}
String data = new String( positionBuffer, 0, packet.getLength() );

if( dataGot2 == true )
{
//Log.d(TAG, data);
String[] pos = data.split(":");
for(int i = 0;i<pos.length;i++)
{
//Log.d(TAG, pos);
}
xValues[ Integer.parseInt( pos[0] ) ] = Integer.parseInt( pos[1] );
yValues[ Integer.parseInt( pos[0] ) ] = Integer.parseInt( pos[2] );
Thread.sleep( 125 );
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
finally
{

}
//catch (SocketException e)
//{
//Log.d(TAG, "Error with socket");
//e.printStackTrace();
//}
//Listen for a client to connect
//catch (IOException e)
//{
//Log.d(TAG, "Error with I/O");
//e.printStackTrace();
//}
//Take in the host positions
//this should be put a the top of the try statement
//to stop the waste of process power
if( gamePanel.rtnClient().host == true && gamePanel.rtnClient() != null )
{
xValues[0] = gamePanel.rtnClient().rtnGameObject().returnPosX();
yValues[0] = gamePanel.rtnClient().rtnGameObject().returnPosY();
}
}
}

private void sendOutPositions()
{
boolean run = true;
while( run )
{
try
{
//Log.d(TAG, "Run the gamePositionsSend code");
String data = "";
//if( gameSendPos == null )
//{
// gameSendPos = new DatagramSocket( portSend );
//}

//create the string ready to be sent out
for(int i = 0; i < 8; i++)
{
if(xValues >= 0)
{
data += i + ":" + xValues + ":" + yValues + ":";
//Log.d(TAG, "data to be sent out : " + data);
}
}
//Log.d(TAG, "data finished");

//Convert characters from 2bytes to 1byte
sendPositionBuffer = data.getBytes( encoding );
DatagramPacket p = null;

for(int i = 0;i < addresses.size(); i++)
{
if( addresses.get(i) != null )
{
p = new DatagramPacket( sendPositionBuffer, sendPositionBuffer.length , addresses.get(i), portSend );
gameSendPos.send( p );
}
}
Thread.sleep( 50 );
}
catch (SocketException e)
{
// TODO Auto-generated catch block
//e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
//e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
}
}


//Update/run the server
private void onServer()
{
if( gamePanel.rtnServerState() == true && serverStarted == false )
{
if( serverStarted == false )
{
serverStarted = true;
for(int i = 1;i<xValues.length;i++)
{
xValues = -100; yValues = -100;
}
}


boolean run = true;
//Connect to server
Runnable ctsThread = new Runnable() {
public boolean running = true;
public void run() {
while(running){
connectToServer(); // Make sure this blocks in some way
}
}
};
//Receive the positions from clients
Runnable rpThread = new Runnable() {
public boolean running = true;
public void run() {
while(running){
receivePositions(); // Make sure this blocks in some way
}
}
};
//Send out positions
Runnable sopThread = new Runnable() {
public boolean running = true;
public void run() {
while(running){
sendOutPositions(); // Make sure this blocks in some way
}
}
};

try {
gameServerID = new DatagramSocket( portID );
gamePositions = new DatagramSocket( portPos );
gameSendPos = new DatagramSocket( portSend );
} catch (SocketException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
//Start that server
new Thread(ctsThread).start();
new Thread(rpThread).start();
new Thread(sopThread).start();
while( run )
{

/*addresses = gsID.addresses;
gsPos.addresses = addresses;
gsSendPos.addresses = addresses;
gsSendPos.positions = gsPos.positions;*/
//GameServerID
//NOTHING


//GameServerPositions


//GameServerSendPos


} //End of while statement

}

//Log.d(TAG, "Start the server");
//serverHandler.post( updateRunnable );
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}

public void onDestroy()
{
Log.d( TAG, "Destroying... " );
super.onDestroy();
}

public void onStop()
{
Log.d( TAG, "Stopping... " );
super.onStop();
}
}
[/HIGH]

Client code is the same, they just send out small packets instead.
 
Do you need to sleep in the "connect to server" and "receive" threads? Why not remove the timeout on the socket and it should block automatically until packets are available.

And do you really need to send clients 20 sets of new positions every second? Maybe you could delay the send by a bit more, or just send when you have new positions.

Also be careful about accessing arrays from multiple threads, you could end up reading some strange sets of positions (If the "sender" thread tries to get an x and y position when the "receiver" thread is updating it you might get an updated x value and an old y value.) You will need to use a synchronized block to prevent different threads from accessing the arrays concurrently.
 
Ok well I have changed the three methods above to not send so offend, also the receive sockets don't sleep anymore,

But I have a problem with the gameClient now :), I have a temp variable to hold the old position of the client player, if the client moves it then checks to see if the position is the same as the temp, if not then send the new position to the server, so if the client sits still nothing will be send, but now when the player sits still, it won't update any of the clients connected...I quite confused, there is a if statement, but that doesn't block the receive try block,

Here is the code for that part of the gameClient

[HIGH]
if( host == false )
{
//Here we send out the clients position,
//and receive the positions of over clients that are connected
if( checker.returnPosX() != assets[ID].returnPosX() && checker.returnPosY() != assets[ID].returnPosY())
{
try {
//Log.d(TAG, "Send my position");
//If the socket is not yet setup, set it up
if( socketPositions == null)
{
socketPositions = new DatagramSocket( sendPosPort );
}
//Using the ID given at the start, send X and Y position to the server
String msg = ID +":"+ assets[ID].returnPosX() +":"+ assets[ID].returnPosY();
int msgLength = msg.length();
positionBuffer = msg.getBytes( encoding );
InetAddress address;
address = InetAddress.getByName("192.168.1.59");
DatagramPacket p = new DatagramPacket( positionBuffer, positionBuffer.length , address, sendPosPort );
//Send the data
socketPositions.send( p );

} catch (UnknownHostException e2) {
// TODO Auto-generated catch block
//Log.d(TAG, "Error with unknown host");
//e2.printStackTrace();
} catch (SocketException e) {
// TODO Auto-generated catch block
//Log.d(TAG, "Error with socket");
//e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
//Log.d(TAG, "Error with sending/receiving data");
//e.printStackTrace();
}
checker.setPosition(assets[ID].returnPosX(), assets[ID].returnPosY());
}
//something is so wrong here!?!?!

//Receive the position of the clients connected

try {
//Log.d(TAG, "Receive data from server");
//If the socket is not yet setup, set it up
//Log.d(TAG, "Receive those positions");
if( socketReceive == null)
{
socketReceive = new DatagramSocket( recPosPort );
}
//Log.d(TAG, "data received?");
DatagramPacket packet = new DatagramPacket( outPositionBuffer, outPositionBuffer.length );
try
{
socketReceive.receive( packet );
}
catch (IOException e)
{
//Log.d(TAG, "Error with receiving data");
//e.printStackTrace();
}

String data = new String( outPositionBuffer, 0, packet.getLength() );
//with this data, NEW things shall HAPPEN!!!
//lets first off split the string
playerPositions( data );
//Log.d(TAG, data);

} catch (SocketException e) {
// TODO Auto-generated catch block
//Log.d(TAG, "Error with socket");
//e.printStackTrace();
}


}[/HIGH]
 
Back
Top Bottom