I am making an online java game. Right now I am using UDP packets. I have about a 50% packet drop rate right now at updating 60 15 times a second. This both happen when it's being hosted on my local machine and hosted on my server. I have tried sending fewer packets and sending them slower neither have helped. Thanks in advance.
Sending packets:
while(true) {
try {
socket = new DatagramSocket();
if(id >= 0) {
data = (PacketID.POS.toString() + tempobj.x + "/" + tempobj.y + "/" + tempobj.getHelth() + "/" + id + "/").getBytes();
}else {
data = (PacketID.POS.toString() + tempobj.x + "/" + tempobj.y + "/" + tempobj.getHelth() + "/" + tempobj.isSheild + "/" + tempobj.name + "/" + tempobj.size + "/" + (int)(tempobj.getVelx()) + "/" + (int)(tempobj.getVely())).getBytes();
}
DatagramPacket packet = new DatagramPacket(data, data.length, ip, port);
if(tempobj.x != lx || tempobj.y != ly || tempobj.helth != lh) {
packetssent++;
System.out.println(packetssent);
lx = tempobj.x;
ly = tempobj.y;
lh = tempobj.helth;
socket.send(packet);
}
Thread.sleep(66);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
receiving packets:
byte[] arr = new byte[40];
DatagramPacket data = new DatagramPacket(arr, arr.length);
while(true) {
socket.receive(data);
String s = new String(data.getData(),0, data.getLength());
if(s.substring(0, 3).equalsIgnoreCase(PacketID.POS.name())) {
String[] split = s.substring(3).split("/");
System.out.println(reciver);
for(int i = 0; i < players.size(); i++) {
if((players.get(i).id - 1) == Integer.parseInt(split[3].trim())) {
players.get(i).x = Integer.parseInt(split[0].trim());
players.get(i).y = Integer.parseInt(split[1].trim());
players.get(i).helth = Integer.parseInt(split[2].trim());
}
}
}
s = null;
}
There is probably nothing you can do about UDP packet loss that you haven't already tried. Except ... maybe ... try and get a better end-to-end network connection.
Packet loss typically happens as a result of network congestion on the most congested links. Unless you own and manage those links, there is nothing you can do about it.
Maybe you should be using TCP instead. Or changing your application so that it can cope with 50% packet loss ...
There is one problem I noticed with your code. Your sending code is creating a brand new DatagramSocket to send each UDP message, and then not closing the socket. That's inefficient, and could lead to resource leak problems. (It shouldn't cause packet loss though.)
If this is a linux host you may try & check if you're not exceeding any buffers. I've been bitten by similar problem in the past. Check the output of netstat -s and checkout the udp section for any drops.
Related
I want to use android's VpnService to capture packets filter them based off IP address. I can get the packets from the "tun" interface just fine but after that i'm not sure how to forward them to their original destination. Based off of the comments from this answer it seems like i just need to:
Create a new socket to the destination IP address and port
Trim the IP and TCP header to send only the data
Re-attach the IP and TCP header when i get a response
Send the complete packet to the output stream
I have tried to send the data like this:
Socket socket = new Socket();
socket.bind(new InetSocketAddress(0));
if (protect(socket)){
Log.e(TAG, "Socket protected");
}else{
Log.e(TAG, "Socket NOT protected");
}
socket.connect(new InetSocketAddress(ipPacket.getDestinationIp(), ipPacket.getDstPort()));
Log.e(TAG, "Socket connected: " + socket.isConnected());
socket.getOutputStream().write(getTCPHeader(getIpHeader(packet)[1])[1].array());
The methods getTCPHeader(ByteArray packet) and getIpHeader(ByteArray packet) simply splits the packet into two ByteArray's as follows:
private ByteBuffer[] getIpHeader(ByteBuffer packet){
packet.position(0);
ByteBuffer ipHeader = ByteBuffer.allocate(20);
ByteBuffer data = ByteBuffer.allocate(packet.limit() - 20);
packet.get(ipHeader.array(), 0, 20);
packet.get(data.array(), 0, packet.limit() - 20);
return new ByteBuffer[]{ipHeader, data};
}
private ByteBuffer[] getTCPHeader(ByteBuffer packet){
packet.position(20);
ByteBuffer tcpHeader = ByteBuffer.allocate(20);
ByteBuffer data = ByteBuffer.allocate(packet.limit() - 20);
packet.get(tcpHeader.array(), 0, 20);
packet.get(data.array(), 0, packet.limit() - 40);
return new ByteBuffer[]{tcpHeader, data};
}
Now to get a response from the server, i am using the following code:
ByteBuffer responsePacket = ByteBuffer.allocate(65535);
InputStream socketInputStream = socket.getInputStream();
try{
int responseLength = socketInputStream.read(responsePacket.array());
if (responseLength > 20){
Log.e(TAG, "===Server Response===");
Log.e(TAG, "Length: " + responseLength);
ByteBuffer trimmedResponseData = ByteBuffer.allocate(responseLength);
System.arraycopy(responseData.array(), 0, trimmedResponseData.array(), 0, responseLength);
String resp = "";
for (int i = 0; i < responseLength; i++){
resp += String.valueOf(responseData.get(i) + " ");
}
Log.e(TAG, "Response data: " + resp);
ByteBuffer finalPacket = ByteBuffer.allocate(40 + responseLength);
ByteBuffer swappedIpHeader = swapSrcDstAddress(getIpHeader(packet)[0]);
ByteBuffer swappedTcpHeader = swapTCPSrcDst(getTCPHeader(getIpHeader(packet)[1])[0]);
finalPacket.put(swappedIpHeader.array());
finalPacket.put(swappedTcpHeader.array());
finalPacket.put(serverResponseData.array());
Packet finPack = debugPacket(finalPacket);
Log.e("VPN", "Final packet --> Packet size: " + finPack.getTotalLength() + " from " + finPack.getSourceIp() + " src port: " + finPack.getSrcPort() + " going to " + finPack.getDestinationIp() + " dst port: " + finPack.getDstPort());
out.write(finalPacket.array());
}
}catch (Exception e){
//Log.e(TAG, "EXCEPTION: " + e);
e.printStackTrace();
}
This code seems to work either EXTREMELY slowly, or not at all. Sometimes if i go to www.google.com it will load slowly but most of the time it doesn't. Also some times i am getting the following error on the line int responseLength = socketInputStream.read(serverResponse.array());
java.net.SocketException: recvfrom failed: ECONNRESET (Connection reset by peer)
What is causing this error, and how can i properly forward these packets to the appropriate destination? Any help is greatly appreciated!
What is causing this error?
recvfrom failed exception means that Server has closed the client socket but client was still reading input data (in your case serverResponse.array(). For details, see this.
how can i properly forward these packets to the appropriate
destination?
There is a sample-code from google-sources here that forwards available packets. Please go through the code and the relevant comments. According to google-sources:
This application consists of an Android client and a sample
implementation of a server. It performs IP over UDP and is capable of
doing seamless handover between different networks as long as it
receives the same VPN parameters.It shows how to build a VPN client
using the VpnService class introduced in API level 14.
The sample code of the server-side implementation is Linux-specific
and is available in the server directory. To run the server or port
it to another platform, please see comments in the code for the
details.
one more helpful app link here
I am working on a server software for Minecraft: Pocket Edition. Right now I am working on the MOTD for the server, and it works Just fine. When sending a response to a ping packet (0x01) with a 0x1c. It shows up in the world list with the name just fine. But, for some reason, If I send the same data from another program, the Ping ID and ServerID will show different in the consoles. Why is this?
Ping response code:
public PingResponse(DatagramPacket Packet, long ServerID) throws IOException {
// Data from Ping
ByteBuffer ReceivedPing = ByteBuffer.wrap(Packet.getData());
// Set variables
this.ServerID = ServerID;
this.ServerName = ServerPropertiesHandler.getMOTD();
this.PingID = ReceivedPing.getLong();
// Server Name
String Identifier = "MCCPP;MINECON;" + ServerPropertiesHandler.getMOTD();
ByteBuffer PingResponseBuffer = ByteBuffer.allocate(35 + (short) Identifier.length());
// Put Packet ID
PingResponseBuffer.put(PacketIDList.ID_UNCONNECTED_PING_OPEN_CONNECTIONS);
// Ping ID
PingResponseBuffer.putLong(this.PingID);
System.out.println("Ping ID: " + this.PingID);
// Server ID
PingResponseBuffer.putLong(this.ServerID);
System.out.println("Server ID: " + this.ServerID);
// Sugar Spice and everything nice
PingResponseBuffer.put(PacketIDList.MAGIC);
// Server Name
PingResponseBuffer.putShort((short) Identifier.length());
PingResponseBuffer.put(Identifier.getBytes());
// Send
PacketHandler.Socket.send(new DatagramPacket(PingResponseBuffer.array(), PingResponseBuffer.array().length), Packet.getAddress(), Packet.getPort());
}
Client Example:
public static void main(String[] args) {
try {
// SEND
final long PacketID = new Random().nextLong();
DatagramSocket ClientSocket = new DatagramSocket();
ByteBuffer PingBuffer = ByteBuffer.allocate(25);
PingBuffer.put(PacketIDList.ID_CONNECTED_PING_OPEN_CONNECTIONS);
PingBuffer.putLong(PacketID);
PingBuffer.put(PacketIDList.MAGIC);
ClientSocket.send(new DatagramPacket(PingBuffer.array(), PingBuffer.array().length, InetAddress.getByName("localhost"), 19132));
// RECEIVE
byte[] buffer = new byte[1535];
DatagramPacket PongPacket = new DatagramPacket(buffer, buffer.length);
ClientSocket.receive(PongPacket);
byte[] PongPacketData = PongPacket.getData();
ByteBuffer PongBuffer = ByteBuffer.wrap(PongPacketData);
if(PongPacketData[0] == (byte) 0x1c) {
System.out.println("PingID From Server: " + PongBuffer.getLong());
System.out.println("ServerID From Server: " + PongBuffer.getLong());
System.out.println("MAGIC From Server: " + PongBuffer.get());
System.out.println("MOTD From Server: " + PongBuffer.get());
}
else {
System.out.println("UNKNOWN PACKET");
}
ClientSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
3 months and no reply. pingID and serverID are identifiers of time used on respective systems to determine the time since start. These provide a double functionality. Firstly, they allow a server to determine any latency in the communications. Secondly, they help provide a packet order to commands. Rebooting a server would generate a new serverID starting from 0, while rejoining a server would provide a new pingID starting at 0.
I would be quite interested to see the progress you've made, as I've begun working on my own.
I am writting a Java application that reads game server information. That's all it does, it runs in a loop doing the same thing.
The problem is that at first it works great, I get a response. After some(not a constant number) times it starts throwing the SocketTimeoutException.
This is the method I use for querying a SAMP server.
private ByteBuffer sendQuery(char opcode) throws IOException {
ByteBuffer bf = ByteBuffer.allocate(11);
bf.order(ByteOrder.LITTLE_ENDIAN);
bf.put("SAMP".getBytes("US-ASCII"));
bf.put(encodeIP(getIp()));
bf.putShort((short)getPort());
bf.put((byte)opcode);
Logger.getLogger().log("Request to " + this.getAddress().getHostString() + ":"+ByteConvert.bytesToHexString(bf.array()));
DatagramPacket packet = new DatagramPacket(bf.array(), bf.capacity(), getAddress().getAddress(), getPort());
getSocket().send(packet);
byte[] buffer = new byte[4096];
packet = new DatagramPacket(buffer, buffer.length, getAddress().getAddress(), getPort());
getSocket().receive(packet);
if(packet.getLength() > 4096)
Logger.getLogger().log("Large packet received from " + this.getIp() + " :"+packet.getLength());
bf = ByteBuffer.allocate(packet.getLength());
bf.order(ByteOrder.LITTLE_ENDIAN);
bf.put(buffer, 0, packet.getLength());
bf.flip();
return bf;
}
encodeIP method:
private static byte[] encodeIP(String s) throws IOException {
String[] ip = s.split("\\.");
byte[] bytes = new byte[ip.length];
for(int i = 0; i < ip.length; i++) {
bytes[i] = (byte)Integer.parseInt(ip[i]);
}
return bytes;
}
The protocol is documented here.
Other games have the same issue but currently i'm investigating SAMP as its protocol seems to be the simplest.
The application is running on Linux, Debian 7.0. I tried disabling the firewall completely.
So the reply got lost, or didn't get sent. You have to cope with either. UDP is an unreliable protocol, and code contains bugs, or unexpected exits.
NB there's no point in initializing the address and port of a DatagramPacket you're about to receive into. They will get overwritten by the source address:port of the incoming packet.
At Client I chunks buffer with Arrays.CopyofRange() from 1 FileEvent object:
byte[] data = outputStream.toByteArray();
for (int i = 0; i <= data.length; i += incommingData.length) {
byte[] forSent = new byte[0x3ff];
forSent = Arrays.copyOfRange(data, i, i + 1023);
DatagramPacket sendPacket = new DatagramPacket(forSent, forSent.length, ipAddress, PORT);
socket.send(sendPacket);
System.out.println("File sent from client : " + i);
}
String done = "Done";
DatagramPacket stringDone = new DatagramPacket(done.getBytes(), done.length(), ipAddress, PORT);
socket.send(stringDone);
And at Server:
do {
byte[] incomingData = new byte[1024 * 1000 * 50];
incomingPacket = new DatagramPacket(incomingData, incomingData.length);
socket.receive(incomingPacket);
byte[] data ;
data = incomingPacket.getData();
result = Arrays.copyOfRange(data, 0, data.length);
System.out.println("Da nhan:" + result.length);
message = new String(incomingPacket.getData(), 0, incomingPacket.getLength());
System.out.println(message);
} while ( !message.equals("Done"));
after send all buffer, i send 1 String "done". And at Server i check end of file by String "done". But it not work, via UDP, how can i detect end of file transfer. Thanks!
UDP is an unreliable protocol. Messages may be lost without either the sender or receiver being notified. This is particularly likely if the sender sends a large number of messages in quick succession ... as your code is liable to do.
So I suspect that what is happening in your case is that the message that contains the "done" string is being lost.
There is no easy cure for this using UDP. It would entail implementing flow control, message loss detection and retransmission ... at the application protocol level. This is technically possible, but it is simpler to either switch to TCP, or look for an alternative reliable protocol.
Morning.
I'm pretty new in Java and socket connections but I'm trying to send out a UDP packet/broadcast on 255.255.255.255 on port 8001 to a device. I can get the data to send just fine, however when it comes time to receive the data the connection times out. I have a packet sniffer and I can see the packet send and then the device respond.
I'm pretty sure it is a rookie mistake that I'm missing in my code but I've been stuck on it for awhile and any help would be appreciated.
m_Socket = new DatagramSocket(m_SERVERPORT);
InetAddress address = InetAddress.getByName(m_SERVERIP);
m_DataPack = new DatagramPacket(m_SERVERCMD.getBytes(), m_SERVERCMD.getBytes().length,
address, m_SERVERPORT);
m_Socket.setBroadcast(true);
m_Socket.connect(address, m_SERVERPORT);
m_Socket.send(m_DataPack);
m_DataPack = new DatagramPacket(data, data.length,
address, m_SERVERPORT);
m_Socket.receive(m_DataPack); // This is where it times out
data = m_DataPack.getData();
String received = data.toString();
System.out.println("Received: " + received);
m_Socket.close();
Thanks and Gig'Em.
EDIT:
I'm not sure if this helps but when I watch the m_Socket object I can see the following right before it sends:
bound = true;
close = false;
connectedAddress = Inet4Address (id = 32) (-1,-1,-1,-1);
connectedPort = 8001;
connectState = 1;
created = true;
impl = PlainDatagramSocketImpl;
oldImpl = false;
and the m_DataPack object is the following:
address = Inet4Address (id = 32) (-1,-1,-1,-1);
bufLength = 6 (size of packet I'm sending is 6 char long);
offset = 0;
port = 8001;
This doesn't make sense. You are broadcasting, which is 1-to-many, and you are also connecting, which is 1-to-1. Which is it?
Lose the connect. And lose the 255.255.255.255. This has been heavily deprecated for about 20 years. Use a subnet-local broadcast address, e.g. 192.168.1.255.
You can also take a look at MulticastSocket described at Broadcasting to Multiple Recipients. Hope this helps.
If you want to receive a datagram you need to bind() to the local endpoint (address + port).
You are sending and receiving the packet on same device. You could separate send and receive ports (e.g send on 8001, receive on 8002). And run send and receive codes as separate threads. If both device must find each other (or one device find itself).
import java.io.IOException;
import java.net.*;
Receiving:
private DatagramSocket getReceiveSocket() throws UnknownHostException, SocketException {
if (receiveSocket == null) {
receiveSocket = new DatagramSocket(8002, InetAddress.getByName("0.0.0.0")); // 0.0.0.0 for listen to all ips
receiveSocket.setBroadcast(true);
}
return receiveSocket;
}
public void receive() throws IOException {
// Discovery request command
byte[] buffer = "__DISCOVERY_REQUEST__".getBytes();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
getReceiveSocket().receive(packet);
System.out.println("Discovery package received! -> " + packet.getAddress() + ":" + packet.getPort());
// Get received data
String data = new String(packet.getData()).trim();
if (data.equals("__DISCOVERY_REQUEST__")) { // validate command
// Send response
byte[] response = new byte["__DISCOVERY_RESPONSE".length()];
DatagramPacket responsePacket = new DatagramPacket(response, response.length, packet.getAddress(), packet.getPort());
getReceiveSocket().send(responsePacket);
System.out.println("Response sent to: " + packet.getAddress() + ":" + packet.getPort());
} else {
System.err.println("Error, not valid package!" + packet.getAddress() + ":" + packet.getPort());
}
}
Sending:
private DatagramSocket getSendSocket() throws UnknownHostException, SocketException {
if (sendSocket == null) {
sendSocket = new DatagramSocket(8001, InetAddress.getLocalHost());
sendSocket.setBroadcast(true);
}
return sendSocket;
}
public void send() throws IOException {
// Discovery request command
byte[] data = "__DISCOVERY_REQUEST__".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("255.255.255.255"), 8002);
getSendSocket().send(packet);
System.out.println("Discovery package sent!" + packet.getAddress() + ":" + packet.getPort());
// Discovery response command
byte[] response = new byte["__DISCOVERY_RESPONSE__".length()];
DatagramPacket responsePacket = new DatagramPacket(response, response.length);
getSendSocket().receive(responsePacket);
System.out.println("Discovery response received!" + responsePacket.getAddress() + ":" + responsePacket.getPort());
String responseData = new String(responsePacket.getData()).trim();
if (responseData.equals("__DISCOVERY_RESPONSE__")) { // Check valid command
System.out.println("Found buddy!" + responsePacket.getAddress() + ":" + responsePacket.getPort());
}
}
Of course should put this code in a loop in a thread.
Based on this example: https://demey.io/network-discovery-using-udp-broadcast/
UPDATE
The link above is dead. But same method described here also: https://www.baeldung.com/java-broadcast-multicast