├── src └── fr │ └── slaynash │ ├── communication │ ├── enums │ │ ├── ClientType.java │ │ └── ConnectionState.java │ ├── handlers │ │ ├── PacketHandlerAdapter.java │ │ ├── PacketHandler.java │ │ └── OrderedPacketHandler.java │ ├── rudp │ │ ├── Packet.java │ │ ├── RUDPServer.java │ │ └── RUDPClient.java │ ├── utils │ │ ├── PacketQueue.java │ │ └── NetUtils.java │ └── RUDPConstants.java │ └── test │ ├── LocalServClientTest.java │ ├── RouterServerTest.java │ └── RouterClientTest.java ├── .gitattributes ├── .gitignore ├── LICENSE └── README.md /src/fr/slaynash/communication/enums/ClientType.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.communication.enums; 2 | 3 | public enum ClientType { 4 | NORMAL_CLIENT, 5 | SERVER_CHILD; 6 | } 7 | -------------------------------------------------------------------------------- /src/fr/slaynash/communication/enums/ConnectionState.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.communication.enums; 2 | 3 | public enum ConnectionState { 4 | STATE_DISCONNECTED, 5 | STATE_CONNECTING, 6 | STATE_CONNECTED, 7 | STATE_DISCONNECTING; 8 | } 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # Eclipse files # 22 | .classpath 23 | .project 24 | .settings/ 25 | bin/ 26 | 27 | # specials project files # 28 | !libs/** 29 | /build 30 | 31 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 32 | hs_err_pid* 33 | -------------------------------------------------------------------------------- /src/fr/slaynash/communication/handlers/PacketHandlerAdapter.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.communication.handlers; 2 | 3 | public class PacketHandlerAdapter extends PacketHandler { 4 | 5 | @Override 6 | public void onConnection() {} 7 | 8 | @Override 9 | public void onDisconnectedByLocal(String reason) {} 10 | 11 | @Override 12 | public void onDisconnectedByRemote(String reason) {} 13 | 14 | @Override 15 | public void onPacketReceived(byte[] data) {} 16 | 17 | @Override 18 | public void onReliablePacketReceived(byte[] data) {} 19 | 20 | @Override 21 | public void onRemoteStatsReturned(int sentRemote, int sentRemoteR, int receivedRemote, int receivedRemoteR) {} 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/fr/slaynash/communication/handlers/PacketHandler.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.communication.handlers; 2 | 3 | import fr.slaynash.communication.rudp.RUDPClient; 4 | 5 | public abstract class PacketHandler { 6 | /* Fields */ 7 | public RUDPClient rudp; 8 | 9 | /* Methods */ 10 | public abstract void onConnection(); 11 | 12 | public abstract void onDisconnectedByLocal(String reason); 13 | 14 | public abstract void onDisconnectedByRemote(String reason); 15 | 16 | public abstract void onPacketReceived(byte[] data); 17 | 18 | public abstract void onReliablePacketReceived(byte[] data); 19 | 20 | public abstract void onRemoteStatsReturned(int sentRemote, int sentRemoteR, int receivedRemote, int receivedRemoteR); 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Slaynash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/fr/slaynash/communication/rudp/Packet.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.communication.rudp; 2 | 3 | import fr.slaynash.communication.RUDPConstants; 4 | import fr.slaynash.communication.utils.NetUtils; 5 | 6 | /** 7 | * Simple packet definition to be extended by other packet types 8 | * @author iGoodie 9 | */ 10 | public abstract class Packet { 11 | 12 | public static final int HEADER_SIZE = 3; //bytes 13 | 14 | public static class PacketHeader { 15 | private boolean isReliable = false; 16 | private short sequenceNum; 17 | 18 | public boolean isReliable() { 19 | return isReliable; 20 | } 21 | 22 | public short getSequenceNo() { 23 | return sequenceNum; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | StringBuilder sb = new StringBuilder(); 29 | sb.append("Reliable:" + isReliable + ", "); 30 | sb.append("SequenceNo:" + sequenceNum); 31 | return sb.toString(); 32 | } 33 | } 34 | 35 | /* Fields*/ 36 | private PacketHeader header = new PacketHeader(); 37 | private byte[] rawPayload; 38 | 39 | /* Constructor */ 40 | public Packet(byte[] data) { 41 | //Parse header 42 | header.isReliable = RUDPConstants.isPacketReliable(data[0]); 43 | header.sequenceNum = NetUtils.asShort(data, 1); 44 | 45 | //Parse payload 46 | rawPayload = new byte[data.length - HEADER_SIZE]; 47 | System.arraycopy(data, HEADER_SIZE, rawPayload, 0, rawPayload.length); 48 | } 49 | 50 | /* Getter and Setters */ 51 | public PacketHeader getHeader() { 52 | return header; 53 | } 54 | 55 | public byte[] getRawPayload() { 56 | return rawPayload; 57 | } 58 | 59 | /* Overrides */ 60 | @Override 61 | public String toString() { 62 | StringBuilder sb = new StringBuilder(); 63 | sb.append("h{"); 64 | sb.append(header); 65 | sb.append("} p{"); 66 | sb.append(NetUtils.asHexString(rawPayload)); 67 | sb.append("}"); 68 | return sb.toString(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/fr/slaynash/test/LocalServClientTest.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.test; 2 | 3 | import fr.slaynash.communication.handlers.OrderedPacketHandler; 4 | import fr.slaynash.communication.handlers.PacketHandlerAdapter; 5 | import fr.slaynash.communication.rudp.Packet; 6 | import fr.slaynash.communication.rudp.RUDPClient; 7 | import fr.slaynash.communication.rudp.RUDPServer; 8 | import fr.slaynash.communication.utils.NetUtils; 9 | 10 | public class LocalServClientTest { 11 | private static RUDPServer server; 12 | private static RUDPClient client; 13 | 14 | public static class ServerPHandler extends PacketHandlerAdapter { 15 | 16 | } 17 | 18 | public static class ClientPHandler extends OrderedPacketHandler { 19 | 20 | @Override 21 | public void onPacketReceived(byte[] data) { 22 | System.out.println(NetUtils.asHexString(data)); 23 | } 24 | 25 | @Override 26 | public void onExpectedPacketReceived(Packet packet) { 27 | System.out.println(packet); 28 | } 29 | 30 | @Override 31 | public void onDisconnectedByRemote(String reason) { 32 | super.onDisconnectedByRemote(reason); 33 | System.out.println("DC reason: " + reason); 34 | } 35 | } 36 | 37 | public static void main(String[] args) throws Exception { 38 | server = new RUDPServer(1111); 39 | server.setPacketHandler(ServerPHandler.class); 40 | server.start(); 41 | 42 | client = new RUDPClient(NetUtils.getInternetAdress("localhost"), 1111); 43 | client.setPacketHandler(ClientPHandler.class); 44 | client.connect(); 45 | 46 | server.getConnectedClients().get(0).sendPacket(new byte[]{0}); 47 | 48 | client.disconnect(); 49 | client.connect(); 50 | 51 | server.getConnectedClients().get(0).sendPacket(new byte[]{0}); 52 | server.getConnectedClients().get(0).sendReliablePacket(new byte[]{0}); 53 | 54 | client.disconnect(); 55 | client.connect(); 56 | 57 | client.disconnect(); 58 | server.stop(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/fr/slaynash/communication/utils/PacketQueue.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.communication.utils; 2 | 3 | import java.util.Comparator; 4 | import java.util.Iterator; 5 | import java.util.PriorityQueue; 6 | 7 | import fr.slaynash.communication.rudp.Packet; 8 | 9 | /** 10 | * Priority queue wrapper for packet definitions 11 | * @author iGoodie 12 | */ 13 | public class PacketQueue { 14 | 15 | public static class PacketSeqComparator implements Comparator { 16 | @Override 17 | public int compare(Packet o1, Packet o2) { 18 | if(o1.getHeader().getSequenceNo() == o2.getHeader().getSequenceNo()) return 0; 19 | return o1.getHeader().getSequenceNo() < o2.getHeader().getSequenceNo() ? -1 : 1; 20 | } 21 | } 22 | 23 | private PriorityQueue packetQueue; 24 | 25 | public PacketQueue() { 26 | packetQueue = new PriorityQueue<>(new PacketSeqComparator()); 27 | } 28 | 29 | public void enqueue(Packet packet) { 30 | packetQueue.add(packet); 31 | } 32 | 33 | public Packet dequeue() { 34 | return packetQueue.isEmpty() ? null : packetQueue.remove(); 35 | } 36 | 37 | public Packet peek() { 38 | return packetQueue.peek(); 39 | } 40 | 41 | public int size() { 42 | return packetQueue.size(); 43 | } 44 | 45 | public boolean isEmpty() { 46 | return packetQueue.isEmpty(); 47 | } 48 | 49 | public Iterator iterator() { 50 | return packetQueue.iterator(); 51 | } 52 | 53 | /*public static void main(String[] args) { //Test queue 54 | Packet p1 = new Packet(new byte[]{0x01, 0x00, 0x00, 0x00, 0x10, 0x7F}) {}; 55 | Packet p2 = new Packet(new byte[]{0x01, 0x00, 0x00, 0x00, 0x01}) {}; 56 | Packet p3 = new Packet(new byte[]{0x01, 0x00, 0x00, 0x10, 0x01}) {}; 57 | System.out.println(p1); 58 | System.out.println(p2); 59 | System.out.println(p3); 60 | 61 | PacketQueue pq = new PacketQueue(); 62 | pq.enqueue(p1); 63 | pq.enqueue(p2); 64 | pq.enqueue(p3); 65 | 66 | System.out.println(); 67 | 68 | while((p1=pq.dequeue()) != null) { 69 | System.out.println(p1); 70 | } 71 | }*/ 72 | } 73 | -------------------------------------------------------------------------------- /src/fr/slaynash/communication/RUDPConstants.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.communication; 2 | 3 | public final class RUDPConstants { 4 | public static final int RECEIVE_MAX_SIZE = 4096; 5 | public static final int CLIENT_TIMEOUT_TIME = 5000; 6 | 7 | /** 8 | * maximum time between ping packets before disconnecting client 9 | */ 10 | public static final long CLIENT_TIMEOUT_TIME_MILLISECONDS = 5000L; 11 | 12 | /** 13 | * Packet's time before dropping it 14 | */ 15 | public static final long PACKET_TIMEOUT_TIME_MILLISECONDS = 5000L; 16 | 17 | /** 18 | * Packet's seq store time after being received (used to avoid duplicate packets) 19 | */ 20 | public static final long PACKET_STORE_TIME_MILLISECONDS = 2000L; 21 | 22 | public static final int VERSION_MAJOR = 1; 23 | public static final int VERSION_MINOR = 0; 24 | 25 | public static final long PING_INTERVAL = 1000; 26 | 27 | public static class PacketType { 28 | public static final byte UNRELIABLE = createPacketType((byte)0 , false); 29 | public static final byte RELIABLE = createPacketType((byte)1 , true ); 30 | public static final byte HANDSHAKE_START = createPacketType((byte)2 , false); 31 | public static final byte HANDSHAKE_OK = createPacketType((byte)3 , false); 32 | public static final byte HANDSHAKE_ERROR = createPacketType((byte)4 , false); 33 | public static final byte PING_REQUEST = createPacketType((byte)5 , false); 34 | public static final byte PING_RESPONSE = createPacketType((byte)6 , false); 35 | public static final byte DISCONNECT_FROMCLIENT = createPacketType((byte)7 , false); 36 | public static final byte DISCONNECT_FROMSERVER = createPacketType((byte)8 , true ); 37 | public static final byte RELY = createPacketType((byte)9 , false); 38 | public static final byte PACKETSSTATS_REQUEST = createPacketType((byte)10, false); 39 | public static final byte PACKETSSTATS_RESPONSE = createPacketType((byte)11, false); 40 | } 41 | 42 | private static byte createPacketType(byte id, boolean reliable) { 43 | if((id & 0b1000_0000) == 0b1000_0000) 44 | throw new IllegalArgumentException("Packet id too big for adding the reliability bit"); 45 | if(reliable) 46 | return (byte) (id | (1 << 7)); 47 | return id; 48 | } 49 | 50 | public static boolean isPacketReliable(int packetType) { 51 | return (packetType & 0b1000_0000) == 0b1000_0000; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jRUDP 2 | A Reliable Java UDP Library for multiplayer games and more 3 | 4 | Specials thanks 5 | --- 6 | Special Thanks to [iGoodie](https://github.com/iGoodie) for the work he had done and the help he gave me on this project 7 | 8 | Compile and run 9 | --- 10 | #### Requirements 11 | To use this library, you only need to have java 8 or newer. No additional libraries required ! 12 | 13 | Examples: 14 | --- 15 | ```java 16 | public class Server 17 | { 18 | public static RUDPServer serverInstance; 19 | public static final int SERVER_PORT = 56448; 20 | public static void main(String[] args) 21 | { 22 | try { 23 | serverInstance = new RUDPServer(SERVER_PORT); 24 | serverInstance.setPacketHandler(OrderedPacketHandler.class); 25 | serverInstance.start(); 26 | } 27 | catch(SocketException e) { 28 | System.out.println("Port " + SERVER_PORT + " is occupied. Server couldn't be initialized."); 29 | System.exit(-1); 30 | } 31 | 32 | //send data to every client 33 | for(RUDPClient c : serverInstance.getConnectedClients()) { 34 | c.sendPacket(new byte[]{0x00}); 35 | c.sendReliablePacket(new byte[]{0x00}); 36 | } 37 | 38 | serverInstance.kick("localhost", 1234); //kick localhost:1234 39 | serverInstance.stop(); 40 | } 41 | } 42 | ``` 43 | 44 | ```java 45 | public class Client 46 | { 47 | public static final InetAddress SERVER_HOST = NetUtils.getInternetAdress("localhost"); 48 | public static final int SERVER_PORT = 56448; 49 | 50 | public static RUDPClient client; 51 | 52 | public static void main(String[] args) 53 | { 54 | try { 55 | client = new RUDPClient(SERVER_HOST, SERVER_PORT); 56 | client.setPacketHandler(OrderedPacketHandler.class); 57 | client.connect(); 58 | } 59 | catch(SocketException e) { 60 | System.out.println("Cannot allow port for the client. Client can't be launched."); 61 | System.exit(-1); 62 | } 63 | catch(UnknownHostException e) { 64 | System.out.println("Unknown host: " + SERVER_HOST); 65 | System.exit(-1); 66 | } 67 | catch(SocketTimeoutException e) { 68 | System.out.println("Connection to " + SERVER_HOST + ":" + SERVER_PORT + " timed out."); 69 | } 70 | catch (InstantiationException e) {} //Given handler class can't be instantiated. 71 | catch (IllegalAccessException e) {} //Given handler class can't be accessed. 72 | catch(IOException e) {} 73 | 74 | client.sendPacket(new byte[]{0x00}); //Send packet to the server 75 | client.sendReliablePacket(new byte[]{0x00}); //Send packet to the server 76 | 77 | client.disconnect(); //Disconnect from server 78 | } 79 | } 80 | ``` 81 | 82 | ## Getting support 83 | If you have any question or you found a problem, you can [open an issue](https://github.com/Slaynash/Reliable-UDP-library/issues) on the Github repository, send me an email at [slaynash@survival-machines.fr](mailto:slaynash@survival-machines.fr), or contact me on Discord (Slaynash#2879). 84 | -------------------------------------------------------------------------------- /src/fr/slaynash/communication/handlers/OrderedPacketHandler.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.communication.handlers; 2 | 3 | import fr.slaynash.communication.rudp.Packet; 4 | import fr.slaynash.communication.utils.NetUtils; 5 | import fr.slaynash.communication.utils.PacketQueue; 6 | 7 | public class OrderedPacketHandler extends PacketHandler { 8 | 9 | protected PacketQueue reliableQueue = new PacketQueue(); 10 | protected short lastHandledSeq = -1; 11 | 12 | @Override 13 | public void onConnection() {} 14 | 15 | @Override 16 | public void onDisconnectedByRemote(String reason) { 17 | reliableQueue = new PacketQueue(); 18 | lastHandledSeq = Short.MAX_VALUE; 19 | } 20 | 21 | @Override 22 | public void onDisconnectedByLocal(String reason) { 23 | reliableQueue = new PacketQueue(); 24 | lastHandledSeq = Short.MAX_VALUE; 25 | } 26 | 27 | @Override 28 | public void onPacketReceived(byte[] data) {} 29 | 30 | @Override 31 | public void onRemoteStatsReturned(int sentRemote, int sentRemoteR, int receivedRemote, int receivedRemoteR) {} 32 | 33 | @Override 34 | public void onReliablePacketReceived(byte[] data) { 35 | Packet packet = new Packet(data) {}; //Parse received packet 36 | short expectedSeq = NetUtils.shortIncrement(lastHandledSeq); //last + 1 37 | 38 | if(NetUtils.sequence_greater_than(lastHandledSeq, packet.getHeader().getSequenceNo())) { // (last > received) == (received < last) 39 | return; // Drop the packet, because we already handled it 40 | } 41 | 42 | //Received an unexpected packet? Enqueue and pass 43 | if(packet.getHeader().getSequenceNo() != expectedSeq) { 44 | reliableQueue.enqueue(packet); 45 | return; 46 | } 47 | 48 | // Handle expected packet 49 | onExpectedPacketReceived(packet); 50 | lastHandledSeq = packet.getHeader().getSequenceNo(); 51 | expectedSeq = NetUtils.shortIncrement(lastHandledSeq); 52 | 53 | // Handle every waiting packet 54 | while(!reliableQueue.isEmpty() && reliableQueue.peek().getHeader().getSequenceNo() == expectedSeq) { 55 | packet = reliableQueue.dequeue(); 56 | onExpectedPacketReceived(packet); 57 | lastHandledSeq = expectedSeq; 58 | expectedSeq = NetUtils.shortIncrement(lastHandledSeq); 59 | } 60 | } 61 | 62 | public void onExpectedPacketReceived(Packet packet) { 63 | System.out.println("Handling: " + packet); //Print packet just to test 64 | } 65 | 66 | /* 67 | //Reliability ordering test 68 | public static void main(String[] args) { 69 | OrderedPacketHandler handler = new OrderedPacketHandler(); 70 | byte[][] list = new byte[][] { 71 | {0x1, 0b0000_0000, 0b0000_0000}, //0 * 72 | {0x1, 0b0000_0000, 0b0000_0001}, //1 * 73 | {0x1, 0b0000_0000, 0b0000_0011}, //3 74 | {0x1, 0b0000_0000, 0b0000_0101}, //5 75 | {0x1, 0b0000_0000, 0b0000_0100}, //4 * 76 | {0x1, 0b0000_0000, 0b0000_0111}, //7 77 | {0x1, 0b0000_0000, 0b0000_0010}, //2 * 78 | {0x1, 0b0000_0000, 0b0000_0110}, //6 79 | {0x1, 0b0000_0000, 0b0000_1000}, //8 * 80 | }; 81 | 82 | for(byte[] p_data : list) { 83 | handler.onReliablePacketReceived(p_data); 84 | } 85 | }*/ 86 | } 87 | -------------------------------------------------------------------------------- /src/fr/slaynash/test/RouterServerTest.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.test; 2 | 3 | import java.awt.Color; 4 | import java.awt.Font; 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.io.PrintStream; 8 | import java.net.SocketException; 9 | import java.util.Timer; 10 | import java.util.TimerTask; 11 | 12 | import javax.swing.DefaultListModel; 13 | import javax.swing.JButton; 14 | import javax.swing.JFrame; 15 | import javax.swing.JLabel; 16 | import javax.swing.JList; 17 | import javax.swing.JScrollBar; 18 | import javax.swing.JScrollPane; 19 | import javax.swing.JTextArea; 20 | import javax.swing.JTextField; 21 | import javax.swing.UIManager; 22 | 23 | import fr.slaynash.communication.handlers.OrderedPacketHandler; 24 | import fr.slaynash.communication.rudp.RUDPClient; 25 | import fr.slaynash.communication.rudp.RUDPServer; 26 | 27 | public final class RouterServerTest extends JFrame { 28 | 29 | private static final int ST_SERVER_PORT = 1111; 30 | private static final int ST_PACKETS_PER_SEC = 30; 31 | 32 | private static final long serialVersionUID = 0L; 33 | private static RouterServerTest gui_instance; 34 | 35 | public static class SPacketHandler extends OrderedPacketHandler { 36 | 37 | @Override 38 | public void onConnection() { 39 | String info = rudp.getAddress() + ":" + rudp.getPort(); 40 | System.out.println("[DEBUG]" + info + " has connected!"); 41 | gui_instance.modelConnClients.addElement(info); 42 | } 43 | 44 | @Override 45 | public void onDisconnectedByRemote(String reason) { 46 | String info = rudp.getAddress() + ":" + rudp.getPort(); 47 | System.out.println("[DEBUG]" + info + " has disconnected"); 48 | gui_instance.modelConnClients.removeElement(info); 49 | } 50 | } 51 | 52 | public RUDPServer serverInstance; 53 | public Timer packetTimer = new Timer(); 54 | 55 | private DefaultListModel modelConnClients = new DefaultListModel<>(); 56 | private JTextField tfSrvPort; 57 | private JButton btnStartSrv; 58 | private JTextArea taConsole; 59 | 60 | private RouterServerTest() { 61 | setResizable(false); 62 | setTitle("jRUDP Server Test"); 63 | setDefaultCloseOperation(EXIT_ON_CLOSE); 64 | setSize(289, 500); 65 | setLocationRelativeTo(null); 66 | getContentPane().setLayout(null); 67 | 68 | try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) {} 69 | 70 | JScrollPane scrollPane = new JScrollPane(); 71 | scrollPane.setEnabled(false); 72 | scrollPane.setBounds(10, 11, 264, 147); 73 | getContentPane().add(scrollPane); 74 | 75 | JList listConnClients = new JList<>(); 76 | listConnClients.setEnabled(false); 77 | listConnClients.setModel(modelConnClients); 78 | scrollPane.setViewportView(listConnClients); 79 | 80 | JLabel lblConnClients = new JLabel("Connected Clients"); 81 | scrollPane.setColumnHeaderView(lblConnClients); 82 | 83 | tfSrvPort = new JTextField(); 84 | tfSrvPort.setText(ST_SERVER_PORT + ""); 85 | tfSrvPort.setBounds(99, 397, 160, 20); 86 | getContentPane().add(tfSrvPort); 87 | tfSrvPort.setColumns(10); 88 | 89 | JLabel lblSrvPort = new JLabel("Server Port:"); 90 | lblSrvPort.setBounds(23, 400, 66, 14); 91 | getContentPane().add(lblSrvPort); 92 | 93 | btnStartSrv = new JButton("Start Server!"); 94 | btnStartSrv.addActionListener((action)-> { 95 | if(serverInstance!=null && serverInstance.isRunning()) { //Server running handle 96 | packetTimer.cancel(); 97 | packetTimer.purge(); 98 | serverInstance.stop(); 99 | btnStartSrv.setEnabled(false); 100 | btnStartSrv.setText("Server closing.."); 101 | new Timer().schedule(new TimerTask() { 102 | @Override 103 | public void run() { 104 | System.out.println("[INFO]Server stopped"); 105 | btnStartSrv.setEnabled(true); 106 | btnStartSrv.setText("Start Server!"); 107 | } 108 | }, 5000); 109 | modelConnClients.removeAllElements(); 110 | } 111 | else { //Server not running handle 112 | try { 113 | serverInstance = new RUDPServer(Integer.parseInt(tfSrvPort.getText())); 114 | serverInstance.setPacketHandler(SPacketHandler.class); //No handler yet 115 | serverInstance.start(); 116 | btnStartSrv.setText("Stop Server!"); 117 | } 118 | catch(SocketException e) { 119 | System.out.println("[ERROR]Server socket is unable to create."); 120 | e.printStackTrace(); 121 | System.exit(-1); 122 | } 123 | catch(NumberFormatException e) { 124 | System.out.println("[ERROR]Port should be an integer value"); 125 | } 126 | 127 | packetTimer = new Timer(); 128 | packetTimer.schedule(new TimerTask() { //Send every user a packet per X milliseconds 129 | @Override 130 | public void run() { 131 | for(RUDPClient c : serverInstance.getConnectedClients()) { 132 | c.sendReliablePacket(new byte[]{0x01}); 133 | } 134 | } 135 | }, 0, 1000 / ST_PACKETS_PER_SEC); 136 | } 137 | }); 138 | btnStartSrv.setBounds(23, 428, 236, 23); 139 | getContentPane().add(btnStartSrv); 140 | 141 | JScrollPane scrollPane_1 = new JScrollPane(); 142 | scrollPane_1.setBounds(10, 169, 264, 216); 143 | getContentPane().add(scrollPane_1); 144 | 145 | taConsole = new JTextArea(); 146 | taConsole.setWrapStyleWord(true); 147 | taConsole.setLineWrap(true); 148 | taConsole.setEditable(false); 149 | taConsole.setBackground(Color.LIGHT_GRAY); 150 | taConsole.setFont(new Font("SansSerif", Font.BOLD, 11)); 151 | scrollPane_1.setViewportView(taConsole); 152 | setVisible(true); 153 | 154 | System.setOut(new PrintStream(new OutputStream() { 155 | @Override 156 | public void write(int b) throws IOException { 157 | taConsole.append("" + (char)b); 158 | taConsole.setSize(taConsole.getPreferredSize()); 159 | JScrollBar sb = scrollPane_1.getVerticalScrollBar(); 160 | sb.setValue( sb.getMaximum() ); 161 | } 162 | })); 163 | 164 | System.out.println("[INFO]Console: on"); 165 | } 166 | 167 | /* Unique Main Method */ 168 | public static void main(String[] args) { 169 | gui_instance = new RouterServerTest(); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/fr/slaynash/communication/utils/NetUtils.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.communication.utils; 2 | 3 | import java.net.InetAddress; 4 | import java.net.UnknownHostException; 5 | 6 | /** 7 | * Fast values to bytes conversion 8 | */ 9 | public abstract class NetUtils { 10 | /* Readers */ 11 | public static short asShort(byte[] buffer) { 12 | return asShort(buffer, 0); 13 | } 14 | 15 | public static short asShort(byte[] buffer, int offset) { 16 | return (short) ( 17 | ((buffer[offset+0] & 0xFF) << 8) 18 | | ((buffer[offset+1] & 0xFF) << 0) 19 | ); 20 | } 21 | 22 | public static int asInt(byte[] buffer) { 23 | return asInt(buffer, 0); 24 | } 25 | 26 | public static int asInt(byte[] buffer, int offset) { 27 | return ((buffer[offset+0] & 0xFF) << 24) 28 | | ((buffer[offset+1] & 0xFF) << 16) 29 | | ((buffer[offset+2] & 0xFF) << 8) 30 | | ((buffer[offset+3] & 0xFF) << 0); 31 | } 32 | 33 | public static long asLong(byte[] buffer) { 34 | return asLong(buffer, 0); 35 | } 36 | 37 | public static long asLong(byte[] buffer, int offset) { 38 | return ((long)(buffer[offset+0] & 0xFF) << 56) 39 | | ((long)(buffer[offset+1] & 0xFF) << 48) 40 | | ((long)(buffer[offset+2] & 0xFF) << 40) 41 | | ((long)(buffer[offset+3] & 0xFF) << 32) 42 | | ((long)(buffer[offset+4] & 0xFF) << 24) 43 | | ((long)(buffer[offset+5] & 0xFF) << 16) 44 | | ((long)(buffer[offset+6] & 0xFF) << 8) 45 | | ((long)(buffer[offset+7] & 0xFF) << 0); 46 | } 47 | 48 | public static float asFloat(byte[] buffer) { 49 | return asFloat(buffer, 0); 50 | } 51 | 52 | public static float asFloat(byte[] buffer, int offset) { 53 | return Float.intBitsToFloat( 54 | ((buffer[offset+0] & 0xFF) << 24) 55 | | ((buffer[offset+1] & 0xFF) << 16) 56 | | ((buffer[offset+2] & 0xFF) << 8) 57 | | ((buffer[offset+3] & 0xFF) << 0) 58 | ); 59 | } 60 | 61 | public static double asDouble(byte[] buffer) { 62 | return asDouble(buffer, 0); 63 | } 64 | 65 | public static double asDouble(byte[] buffer, int offset) { 66 | return Double.longBitsToDouble( 67 | ((long)(buffer[offset+0] & 0xFF) << 56) 68 | | ((long)(buffer[offset+1] & 0xFF) << 48) 69 | | ((long)(buffer[offset+2] & 0xFF) << 40) 70 | | ((long)(buffer[offset+3] & 0xFF) << 32) 71 | | ((long)(buffer[offset+4] & 0xFF) << 24) 72 | | ((long)(buffer[offset+5] & 0xFF) << 16) 73 | | ((long)(buffer[offset+6] & 0xFF) << 8) 74 | | ((long)(buffer[offset+7] & 0xFF) << 0) 75 | ); 76 | } 77 | 78 | /* Writers */ 79 | public static void writeBytes(byte[] buffer, int offset, short num){ 80 | buffer[offset ] = (byte) (num >> 8); 81 | buffer[offset+1] = (byte) (num >> 0); 82 | } 83 | 84 | public static void writeBytes(byte[] buffer, int offset, int num){ 85 | buffer[offset ] = (byte) (num >> 24); 86 | buffer[offset+1] = (byte) (num >> 16); 87 | buffer[offset+2] = (byte) (num >> 8); 88 | buffer[offset+3] = (byte) (num >> 0); 89 | } 90 | 91 | public static void writeBytes(byte[] buffer, int offset, long num){ 92 | buffer[offset ] = (byte) (num >> 56); 93 | buffer[offset+1] = (byte) (num >> 48); 94 | buffer[offset+2] = (byte) (num >> 40); 95 | buffer[offset+3] = (byte) (num >> 32); 96 | buffer[offset+4] = (byte) (num >> 24); 97 | buffer[offset+5] = (byte) (num >> 16); 98 | buffer[offset+6] = (byte) (num >> 8); 99 | buffer[offset+7] = (byte) (num >> 0); 100 | } 101 | 102 | public static void writeBytes(byte[] buffer, int offset, float num){ 103 | int i = Float.floatToIntBits(num); 104 | buffer[offset ] = (byte) (i >> 24); 105 | buffer[offset+1] = (byte) (i >> 16); 106 | buffer[offset+2] = (byte) (i >> 8); 107 | buffer[offset+3] = (byte) (i >> 0); 108 | } 109 | 110 | public static void writeBytes(byte[] buffer, int offset, double num){ 111 | long l = Double.doubleToLongBits(num); 112 | buffer[offset ] = (byte) (l >> 56); 113 | buffer[offset+1] = (byte) (l >> 48); 114 | buffer[offset+2] = (byte) (l >> 40); 115 | buffer[offset+3] = (byte) (l >> 32); 116 | buffer[offset+4] = (byte) (l >> 24); 117 | buffer[offset+5] = (byte) (l >> 16); 118 | buffer[offset+6] = (byte) (l >> 8); 119 | buffer[offset+7] = (byte) (l >> 0); 120 | } 121 | 122 | /* Truncators */ 123 | public static String truncateString(String str, int max) { 124 | if(str.length() <= max) return str; 125 | return str.substring(0, max); //Truncate rightmost chars 126 | } 127 | 128 | public static int truncateToInt(long num) { 129 | String s1 = num + ""; 130 | return Integer.parseInt(s1.substring(Math.max(s1.length() - 9, 0))); 131 | //Evil digit truncator from long to int. 132 | //Might be a little bit slow, but there is no way to do it as we know 133 | } 134 | 135 | /**/ 136 | public static InetAddress getInternetAdress(String host) { 137 | try { 138 | return InetAddress.getByName(host); 139 | } 140 | catch(UnknownHostException e) { 141 | return null; 142 | } 143 | } 144 | 145 | public static String asHexString(byte[] source) { 146 | if(source.length == 0) return ""; 147 | StringBuilder sb = new StringBuilder("0x"); 148 | 149 | for(int i=0; i s2 ) && ( s1 - s2 <= 32768 ) ) || 202 | ( ( s1 < s2 ) && ( s2 - s1 > 32768 ) ); 203 | } 204 | 205 | public static byte byteIncrement(byte num) { 206 | if(num == Byte.MAX_VALUE) return Byte.MIN_VALUE; 207 | return (byte) (num + 1); 208 | } 209 | 210 | public static short shortIncrement(short num) { 211 | if(num == Short.MAX_VALUE) return Short.MIN_VALUE; 212 | return (short) (num + 1); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/fr/slaynash/test/RouterClientTest.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.test; 2 | 3 | import java.awt.Color; 4 | import java.awt.Font; 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.io.PrintStream; 8 | import java.net.InetAddress; 9 | import java.net.SocketException; 10 | import java.net.UnknownHostException; 11 | import java.util.Iterator; 12 | 13 | import javax.swing.DefaultListModel; 14 | import javax.swing.JButton; 15 | import javax.swing.JFrame; 16 | import javax.swing.JLabel; 17 | import javax.swing.JList; 18 | import javax.swing.JScrollBar; 19 | import javax.swing.JScrollPane; 20 | import javax.swing.JTextArea; 21 | import javax.swing.JTextField; 22 | import javax.swing.SwingUtilities; 23 | import javax.swing.UIManager; 24 | 25 | import fr.slaynash.communication.handlers.OrderedPacketHandler; 26 | import fr.slaynash.communication.rudp.Packet; 27 | import fr.slaynash.communication.rudp.RUDPClient; 28 | import fr.slaynash.communication.utils.NetUtils; 29 | import javax.swing.ScrollPaneConstants; 30 | 31 | public class RouterClientTest extends JFrame { 32 | 33 | public static final String ST_SERVER_HOST = "localhost"; 34 | public static final int ST_SERVER_PORT = 1111; 35 | 36 | private static final long serialVersionUID = 0L; 37 | private static RouterClientTest gui_instance; 38 | 39 | public static class ClientPacketHandler extends OrderedPacketHandler { 40 | 41 | short prevHandled = -1; 42 | 43 | @Override 44 | public void onReliablePacketReceived(byte[] data) { 45 | super.onReliablePacketReceived(data); 46 | gui_instance.lblRecPacketQueue.setText("Received Packet Queue (Front==index#0) (Size:" + reliableQueue.size() + ")"); 47 | gui_instance.modelRecPackets.clear(); 48 | 49 | Iterator iter = reliableQueue.iterator(); 50 | while(iter.hasNext()) { 51 | Packet p = iter.next(); 52 | gui_instance.modelRecPackets.addElement(p.toString()); 53 | } 54 | } 55 | 56 | @Override 57 | public void onExpectedPacketReceived(Packet packet) { 58 | short next = NetUtils.shortIncrement(prevHandled); 59 | gui_instance.taHandledPacket.setText("Last Handled Packet:"+ lastHandledSeq +"\n" + packet.toString()); 60 | if(packet.getHeader().getSequenceNo() != next) { 61 | System.out.printf("HANDLING ERROR PREV:%d, CUR:%d", prevHandled, next); 62 | gui_instance.disconnectWGui(); 63 | } 64 | prevHandled = next; 65 | } 66 | 67 | @Override 68 | public void onDisconnectedByRemote(String reason) { 69 | super.onDisconnectedByRemote(reason); 70 | System.out.println("[INFO]Disconnected: " + reason); 71 | prevHandled = Short.MAX_VALUE; 72 | gui_instance.disconnectWGui(); 73 | } 74 | 75 | @Override 76 | public void onDisconnectedByLocal(String reason) { 77 | super.onDisconnectedByLocal(reason); 78 | System.out.println("[INFO]Disconnected."); 79 | prevHandled = Short.MAX_VALUE; 80 | } 81 | } 82 | 83 | public RUDPClient clientInstance; 84 | 85 | public DefaultListModel modelRecPackets = new DefaultListModel<>(); 86 | private JTextField tfServerPort; 87 | private JTextField tfServerHost; 88 | private JButton btnConnection; 89 | private JTextArea taHandledPacket; 90 | private JTextArea taConsole; 91 | private JLabel lblRecPacketQueue; 92 | 93 | private RouterClientTest() { 94 | setResizable(false); 95 | setTitle("jRUDP Client Test"); 96 | setDefaultCloseOperation(EXIT_ON_CLOSE); 97 | setSize(289, 500); 98 | setLocationRelativeTo(null); 99 | getContentPane().setLayout(null); 100 | 101 | try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) {} 102 | 103 | JScrollPane scrollPane = new JScrollPane(); 104 | scrollPane.setBounds(10, 69, 263, 156); 105 | getContentPane().add(scrollPane); 106 | 107 | lblRecPacketQueue = new JLabel("Received Packet Queue (Front==index#0)"); 108 | scrollPane.setColumnHeaderView(lblRecPacketQueue); 109 | 110 | JList listPacketQueue = new JList<>(); 111 | listPacketQueue.setEnabled(false); 112 | listPacketQueue.setModel(modelRecPackets); 113 | scrollPane.setViewportView(listPacketQueue); 114 | 115 | btnConnection = new JButton("Connect"); 116 | btnConnection.addActionListener((action)->{ 117 | if(clientInstance != null && clientInstance.isConnected()) { 118 | disconnectWGui(); 119 | } 120 | else { 121 | connectWGui(); 122 | } 123 | }); 124 | btnConnection.setBounds(10, 438, 263, 23); 125 | getContentPane().add(btnConnection); 126 | 127 | tfServerPort = new JTextField(); 128 | tfServerPort.setText(ST_SERVER_PORT + ""); 129 | tfServerPort.setBounds(96, 407, 177, 20); 130 | tfServerPort.setColumns(10); 131 | getContentPane().add(tfServerPort); 132 | 133 | tfServerHost = new JTextField(); 134 | tfServerHost.setText(ST_SERVER_HOST); 135 | tfServerHost.setColumns(10); 136 | tfServerHost.setBounds(96, 376, 177, 20); 137 | getContentPane().add(tfServerHost); 138 | 139 | JLabel lblServerHost = new JLabel("Server Host:"); 140 | lblServerHost.setBounds(23, 379, 71, 14); 141 | getContentPane().add(lblServerHost); 142 | 143 | JLabel lblServerPort = new JLabel("Server Port:"); 144 | lblServerPort.setBounds(23, 410, 71, 14); 145 | getContentPane().add(lblServerPort); 146 | 147 | JScrollPane scrollPane_1 = new JScrollPane(); 148 | scrollPane_1.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); 149 | scrollPane_1.setBounds(10, 236, 263, 126); 150 | getContentPane().add(scrollPane_1); 151 | 152 | taConsole = new JTextArea(); 153 | taConsole.setLineWrap(true); 154 | taConsole.setWrapStyleWord(true); 155 | taConsole.setEditable(false); 156 | taConsole.setBackground(Color.LIGHT_GRAY); 157 | taConsole.setFont(new Font("SansSerif", Font.BOLD, 11)); 158 | scrollPane_1.setViewportView(taConsole); 159 | 160 | taHandledPacket = new JTextArea(); 161 | taHandledPacket.setEditable(false); 162 | taHandledPacket.setEnabled(false); 163 | taHandledPacket.setFont(new Font("SansSerif", Font.BOLD, 11)); 164 | taHandledPacket.setText("Last Handled Packet:\r\nnull"); 165 | taHandledPacket.setBounds(10, 11, 263, 47); 166 | getContentPane().add(taHandledPacket); 167 | setVisible(true); 168 | 169 | System.setOut(new PrintStream(new OutputStream() { 170 | @Override 171 | public void write(int b) throws IOException { 172 | taConsole.append("" + (char)b); 173 | taConsole.setSize(taConsole.getPreferredSize()); 174 | JScrollBar sb = scrollPane_1.getVerticalScrollBar(); 175 | sb.setValue( sb.getMaximum() ); 176 | } 177 | })); 178 | 179 | System.out.println("[INFO]Console: on"); 180 | 181 | setVisible(true); 182 | } 183 | 184 | private void connectWGui() { 185 | btnConnection.setText("Trying to connect.."); 186 | btnConnection.setEnabled(false); 187 | repaint(); 188 | 189 | SwingUtilities.invokeLater(()->{ 190 | try { 191 | InetAddress host = InetAddress.getByName(tfServerHost.getText()); 192 | int port = Integer.parseInt(tfServerPort.getText()); 193 | 194 | clientInstance = new RUDPClient(host, port); 195 | clientInstance.setPacketHandler(ClientPacketHandler.class); 196 | clientInstance.connect(); 197 | 198 | } 199 | catch(UnknownHostException e) { 200 | System.out.println("[ERROR]Cannot resolve host."); 201 | } 202 | catch(NumberFormatException e) { 203 | System.out.println("[ERROR]Given port should be an integer."); 204 | } 205 | catch(SocketException e) { 206 | System.out.println("[ERROR]Cannot allocate socket for the client"); 207 | } 208 | catch(IOException e) { 209 | System.out.println("[ERROR]Connection timed out"); 210 | } catch (InstantiationException e) { 211 | System.out.println("[ERROR]Cannot create an instance of the handler"); 212 | } catch (IllegalAccessException e) { 213 | System.out.println("[ERROR]Cannot access the constructor of the handler"); 214 | } 215 | finally { 216 | btnConnection.setText(clientInstance!=null && clientInstance.isConnected() ? "Disconnect" : "Connect"); 217 | btnConnection.setEnabled(true); 218 | } 219 | }); 220 | 221 | } 222 | 223 | private void disconnectWGui() { 224 | clientInstance.disconnect("DC"); 225 | btnConnection.setText("Connect"); 226 | } 227 | 228 | /* Unique Main Method */ 229 | public static void main(String[] args) { 230 | gui_instance = new RouterClientTest(); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/fr/slaynash/communication/rudp/RUDPServer.java: -------------------------------------------------------------------------------- 1 | package fr.slaynash.communication.rudp; 2 | 3 | import java.io.IOException; 4 | import java.lang.reflect.Modifier; 5 | import java.net.DatagramPacket; 6 | import java.net.DatagramSocket; 7 | import java.net.InetAddress; 8 | import java.net.SocketException; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | import fr.slaynash.communication.RUDPConstants; 15 | import fr.slaynash.communication.enums.ConnectionState; 16 | import fr.slaynash.communication.handlers.PacketHandler; 17 | import fr.slaynash.communication.utils.NetUtils; 18 | 19 | public class RUDPServer {// receive buffer is bigger (4096B) and client packet is dynamic (<4096B (reliable) / ~21B or ~45B (avoidable)) 20 | //Packet format: 21 | // 22 | //data: type: size: 23 | //packet type [byte] 1 24 | //sequence id [short] 2 25 | //payload [byte[]] <4094 26 | 27 | private int port; 28 | private DatagramSocket datagramSocket; 29 | 30 | private Thread serverThread; 31 | private Thread clientDropHandlerThread; 32 | 33 | private boolean running = false; 34 | private boolean stopping = false; 35 | private List clients = new ArrayList(); 36 | 37 | private Class clientManager; 38 | 39 | /* Constructor */ 40 | public RUDPServer(int port) throws SocketException{ 41 | this.port = port; 42 | datagramSocket = new DatagramSocket(port); 43 | 44 | startServerThread(); 45 | 46 | initClientDropHandler(); 47 | } 48 | 49 | /* Getter And Setters */ 50 | public int getPort(){ 51 | return port; 52 | } 53 | 54 | public boolean isRunning() { 55 | return running; 56 | } 57 | 58 | public List getConnectedClients(){ 59 | synchronized (clients) { 60 | return new ArrayList(clients); 61 | } 62 | } 63 | 64 | public RUDPClient getClient(String host, int port) { 65 | return getClient(NetUtils.getInternetAdress(host), port); 66 | } 67 | 68 | public RUDPClient getClient(InetAddress address, int port) { 69 | for(RUDPClient c : clients) { 70 | if(c.address.equals(address) && c.port==port) { 71 | return c; 72 | } 73 | } 74 | return null; 75 | } 76 | 77 | public void setPacketHandler(Class clientManager){ 78 | if(Modifier.isAbstract(clientManager.getModifiers())) { //Class should not be abstract! 79 | throw new IllegalArgumentException("Given handler class cannot be an abstract class!"); 80 | } 81 | 82 | this.clientManager = clientManager; 83 | } 84 | 85 | /* Actions */ 86 | public void start(){ 87 | if(running) return; 88 | running = true; 89 | 90 | serverThread.start(); 91 | clientDropHandlerThread.start(); 92 | 93 | System.out.println("[RUDPServer] Server started on UDP port "+port); 94 | } 95 | 96 | public void stop(){ 97 | System.out.println("Stopping server..."); 98 | synchronized(clients){ 99 | stopping = true; 100 | for(RUDPClient client : clients) { 101 | client.disconnect("Server shutting down"); 102 | } 103 | } 104 | int remainingClients = 0; 105 | System.out.println("Waiting for every clients to disconnect..."); 106 | while(clients.size() != 0) { 107 | if(clients.size() != remainingClients) { 108 | remainingClients = clients.size(); 109 | System.out.println(remainingClients+" client remaining..."); 110 | } 111 | try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();} 112 | } 113 | System.out.println("Closing server..."); 114 | running = false; 115 | datagramSocket.close(); 116 | } 117 | 118 | public void kick(String address, int port) { 119 | kick(address, port, "Kicked from server"); 120 | } 121 | 122 | public void kick(String address, int port, String reason) { 123 | synchronized(clients){ 124 | RUDPClient clientToRemove = null; 125 | for(RUDPClient client : clients) { 126 | if(client.address.getHostAddress().equals(address) && client.port == port) { 127 | clientToRemove = client; 128 | break; 129 | } 130 | } 131 | 132 | byte[] reasonB = reason.getBytes(StandardCharsets.UTF_8); 133 | clientToRemove.sendPacket(RUDPConstants.PacketType.DISCONNECT_FROMSERVER, reasonB); 134 | clientToRemove.state = ConnectionState.STATE_DISCONNECTED; 135 | 136 | clients.remove(clientToRemove); 137 | } 138 | } 139 | 140 | /* Helper Methods */ 141 | private void handlePacket(byte[] data, InetAddress clientAddress, int clientPort){ 142 | //Check if packet is not empty 143 | if(data.length == 0){ 144 | System.out.println("[RUDPServer] Empty packet received"); 145 | return; 146 | } 147 | 148 | //check if packet is an handshake packet 149 | if(data[0] == RUDPConstants.PacketType.HANDSHAKE_START){ 150 | //If client is valid, add it to the list and initialize it 151 | 152 | if(stopping) { 153 | byte[] error = "Server closing".getBytes(StandardCharsets.UTF_8); 154 | byte[] reponse = new byte[error.length+1]; 155 | reponse[0] = RUDPConstants.PacketType.HANDSHAKE_ERROR; 156 | System.arraycopy(error, 0, reponse, 1, error.length); 157 | sendPacket(reponse, clientAddress, clientPort); 158 | } 159 | else if(NetUtils.asInt(data, 1) == RUDPConstants.VERSION_MAJOR && NetUtils.asInt(data, 5) == RUDPConstants.VERSION_MINOR){//version check 160 | 161 | sendPacket(new byte[]{RUDPConstants.PacketType.HANDSHAKE_OK}, clientAddress, clientPort); 162 | 163 | final RUDPClient rudpclient = new RUDPClient(clientAddress, clientPort, this, clientManager); 164 | synchronized(clients) { 165 | clients.add(rudpclient); 166 | } 167 | System.out.println("[RUDPServer] Added new client !"); 168 | System.out.println("[RUDPServer] Initializing client..."); 169 | new Thread(() -> {rudpclient.initialize();}, "RUDP Client init thread").start(); 170 | return; 171 | 172 | } 173 | else { 174 | //Else send HANDSHAKE_ERROR "BAD_VERSION" 175 | 176 | byte[] error = "Bad version !".getBytes(StandardCharsets.UTF_8); 177 | byte[] reponse = new byte[error.length+1]; 178 | reponse[0] = RUDPConstants.PacketType.HANDSHAKE_ERROR; 179 | System.arraycopy(error, 0, reponse, 1, error.length); 180 | sendPacket(reponse, clientAddress, clientPort); 181 | 182 | } 183 | } 184 | 185 | //handle packet in ClientRUDP 186 | RUDPClient clientToRemove = null; 187 | for(RUDPClient client : clients) { 188 | if(Arrays.equals(client.address.getAddress(), clientAddress.getAddress()) && client.port == clientPort){ 189 | 190 | if(data[0] == RUDPConstants.PacketType.DISCONNECT_FROMCLIENT){ 191 | byte[] reason = new byte[data.length-3]; 192 | System.arraycopy(data, 3, reason, 0, reason.length); 193 | 194 | client.disconnected(new String(reason, StandardCharsets.UTF_8)); 195 | clientToRemove = client; 196 | } 197 | else{ 198 | client.handlePacket(data); 199 | return; 200 | } 201 | 202 | break; 203 | } 204 | } 205 | if(clientToRemove != null) { 206 | synchronized (clients) { 207 | clients.remove(clientToRemove); 208 | } 209 | } 210 | } 211 | 212 | protected void sendPacket(byte[] data, InetAddress address, int port){ 213 | DatagramPacket packet = new DatagramPacket(data, data.length, address, port); 214 | packet.setData(data); 215 | try { 216 | datagramSocket.send(packet); 217 | } catch (IOException e) {e.printStackTrace();} 218 | } 219 | 220 | void remove(RUDPClient client) { 221 | synchronized(clients){ 222 | clients.remove(client); 223 | } 224 | } 225 | 226 | private void startServerThread() { 227 | serverThread = new Thread(new Runnable() { 228 | @Override 229 | public void run() { 230 | while(running){ 231 | byte[] buffer = new byte[RUDPConstants.RECEIVE_MAX_SIZE]; 232 | DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length); 233 | 234 | try { 235 | datagramSocket.receive(datagramPacket); 236 | } catch (IOException e) { 237 | if(running){ 238 | System.err.println("[RUDPServer] An error as occured while receiving a packet: "); 239 | e.printStackTrace(); 240 | } 241 | } 242 | if(!running) break; 243 | byte[] data = new byte[datagramPacket.getLength()]; 244 | System.arraycopy(datagramPacket.getData(), datagramPacket.getOffset(), data, 0, datagramPacket.getLength()); 245 | 246 | handlePacket(data, datagramPacket.getAddress(), datagramPacket.getPort()); 247 | datagramPacket.setLength(RUDPConstants.RECEIVE_MAX_SIZE); 248 | } 249 | } 250 | }, "RUDPServer packets receiver"); 251 | } 252 | 253 | private void initClientDropHandler() { 254 | clientDropHandlerThread = new Thread(new Runnable() { 255 | @Override 256 | public void run() { 257 | try { 258 | while(running){ 259 | synchronized(clients){ 260 | long maxMS = System.currentTimeMillis()-RUDPConstants.CLIENT_TIMEOUT_TIME_MILLISECONDS; 261 | int i=0; 262 | while(i packetHandlerClass; 54 | 55 | private Thread reliableThread; 56 | private Thread receiveThread; 57 | private Thread pingThread; 58 | 59 | private LinkedHashMap packetsReceived = new LinkedHashMap(); 60 | private List packetsSent = Collections.synchronizedList(new ArrayList()); 61 | private int latency = 400; 62 | private RUDPClient instance = this; 63 | 64 | short sequenceReliable = 0; 65 | short sequenceUnreliable = 0; 66 | 67 | short lastPingSeq = 0; 68 | 69 | int sent, sentReliable; 70 | int received, receivedReliable; 71 | 72 | /* Constructor */ 73 | public RUDPClient(InetAddress address, int port) throws SocketException{ 74 | this.address = address; 75 | this.port = port; 76 | } 77 | 78 | RUDPClient(InetAddress clientAddress, int clientPort, RUDPServer rudpServer, Class clientManager) { 79 | this.address = clientAddress; 80 | this.port = clientPort; 81 | this.server = rudpServer; 82 | this.type = ClientType.SERVER_CHILD; 83 | this.sentReliable = 0; 84 | this.sent = 0; 85 | Constructor constructor; 86 | try { 87 | constructor = clientManager.getConstructor(); 88 | this.packetHandler = constructor.newInstance(); 89 | this.packetHandler.rudp = this; 90 | } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 91 | e.printStackTrace(); 92 | } 93 | 94 | lastPacketReceiveTime = System.currentTimeMillis(); 95 | 96 | state = ConnectionState.STATE_CONNECTING; 97 | } 98 | 99 | /* Getter & Setters */ 100 | public InetAddress getAddress() { 101 | return address; 102 | } 103 | 104 | public int getPort() { 105 | return port; 106 | } 107 | 108 | public boolean isConnected() { 109 | return state == ConnectionState.STATE_CONNECTED; 110 | } 111 | 112 | public void setPacketHandler(Class packetHandler){ 113 | if(Modifier.isAbstract(packetHandler.getModifiers())) { //Class should not be abstract! 114 | throw new IllegalArgumentException("Given handler class cannot be an abstract class!"); 115 | } 116 | this.packetHandlerClass = packetHandler; 117 | } 118 | 119 | public int getLatency(){ 120 | return latency; 121 | } 122 | 123 | public int getSent() { 124 | return sent; 125 | } 126 | 127 | public int getSentReliable() { 128 | return sentReliable; 129 | } 130 | 131 | public int getReceived() { 132 | return received; 133 | } 134 | 135 | public int getReceivedReliable() { 136 | return receivedReliable; 137 | } 138 | 139 | private short getReliablePacketSequence() { 140 | short prev = sequenceReliable; 141 | sequenceReliable = NetUtils.shortIncrement(sequenceReliable); 142 | return prev; 143 | } 144 | 145 | private short getUnreliablePacketSequence() { 146 | short prev = sequenceUnreliable; 147 | sequenceUnreliable = NetUtils.shortIncrement(sequenceUnreliable); 148 | return prev; 149 | } 150 | 151 | /* Actions */ 152 | public void connect() throws SocketTimeoutException, SocketException, UnknownHostException, IOException, InstantiationException, IllegalAccessException{ 153 | System.out.println("[RUDPClient] Connecting to UDP port "+port+"..."); 154 | if(state == ConnectionState.STATE_CONNECTED){System.out.println("[RUDPClient] Client already connected !");return;} 155 | if(state == ConnectionState.STATE_CONNECTING){System.out.println("[RUDPClient] Client already connecting !");return;} 156 | if(state == ConnectionState.STATE_DISCONNECTING){System.out.println("[RUDPClient] Client currently disconnecting !");return;} 157 | 158 | if(packetHandlerClass == null) throw new IOException("Unable to connect without packet handler"); 159 | packetHandler = packetHandlerClass.newInstance(); 160 | packetHandler.rudp = this; 161 | 162 | socket = new DatagramSocket(); 163 | socket.setSoTimeout(RUDPConstants.CLIENT_TIMEOUT_TIME); 164 | 165 | lastPacketReceiveTime = System.currentTimeMillis(); 166 | 167 | state = ConnectionState.STATE_CONNECTING; 168 | try { 169 | //Send handshake packet 170 | byte[] handshakePacket = new byte[9]; 171 | handshakePacket[0] = RUDPConstants.PacketType.HANDSHAKE_START; 172 | NetUtils.writeBytes(handshakePacket, 1, RUDPConstants.VERSION_MAJOR); 173 | NetUtils.writeBytes(handshakePacket, 5, RUDPConstants.VERSION_MINOR); 174 | sendPacketRaw(handshakePacket); 175 | 176 | //Receive handshake response packet 177 | byte[] buffer = new byte[8196]; 178 | DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length, address, port); 179 | socket.receive(datagramPacket); 180 | byte[] data = new byte[datagramPacket.getLength()]; 181 | System.arraycopy(datagramPacket.getData(), datagramPacket.getOffset(), data, 0, datagramPacket.getLength()); 182 | 183 | //Handle handshake response packet 184 | if(data[0] != RUDPConstants.PacketType.HANDSHAKE_OK){ 185 | 186 | state = ConnectionState.STATE_DISCONNECTED; 187 | byte[] dataText = new byte[data.length-1]; 188 | System.arraycopy(data, 1, dataText, 0, dataText.length); 189 | throw new IOException("Unable to connect: "+new String(dataText, "UTF-8")); 190 | 191 | } 192 | else{ 193 | 194 | state = ConnectionState.STATE_CONNECTED; 195 | initReceiveThread(); 196 | initPingThread(); 197 | initRelyThread(); 198 | 199 | reliableThread.start(); 200 | receiveThread.start(); 201 | pingThread.start(); 202 | 203 | System.out.println("[RUDPClient] Connected !"); 204 | 205 | } 206 | } catch (IOException e) { 207 | state = ConnectionState.STATE_DISCONNECTED; 208 | throw e; 209 | } 210 | } 211 | 212 | public void disconnect() { 213 | disconnect("Disconnected Manually"); 214 | } 215 | 216 | public void disconnect(String reason) { 217 | if(state == ConnectionState.STATE_DISCONNECTED || state == ConnectionState.STATE_DISCONNECTING) return; 218 | byte[] reponse = reason.getBytes(StandardCharsets.UTF_8); 219 | 220 | if(type == ClientType.SERVER_CHILD){ 221 | sendReliablePacket(RUDPConstants.PacketType.DISCONNECT_FROMSERVER, reponse); 222 | state = ConnectionState.STATE_DISCONNECTING; 223 | } 224 | if(type == ClientType.NORMAL_CLIENT){ 225 | sendPacket(RUDPConstants.PacketType.DISCONNECT_FROMCLIENT, reponse); 226 | state = ConnectionState.STATE_DISCONNECTED; 227 | socket.close(); 228 | } 229 | if(packetHandler != null) packetHandler.onDisconnectedByLocal(reason); 230 | } 231 | 232 | public void sendReliablePacket(byte[] data){ 233 | sendReliablePacket(RUDPConstants.PacketType.RELIABLE, data); 234 | } 235 | 236 | public void sendReliablePacket(byte packetType, byte[] data){ 237 | if(state == ConnectionState.STATE_DISCONNECTED) return; 238 | byte[] packet = new byte[data.length+3]; 239 | long timeMS = System.currentTimeMillis(); 240 | short seq = getReliablePacketSequence(); 241 | 242 | //byte[] dp = new byte[8]; 243 | //BytesUtils.writeBytes(dp, 0, timeNS); 244 | //System.out.println("[RUDPClient] reliable packet sent "+toStringRepresentation(dp)+" - "+timeNS); 245 | 246 | packet[0] = packetType; 247 | NetUtils.writeBytes(packet, 1, seq); 248 | System.arraycopy(data, 0, packet, 3, data.length); 249 | if(type == ClientType.SERVER_CHILD) server.sendPacket(packet, address, port); 250 | else{ 251 | DatagramPacket dpacket = new DatagramPacket(packet, packet.length, address, port); 252 | try { 253 | socket.send(dpacket); 254 | } catch (IOException e) { 255 | e.printStackTrace(); 256 | return; 257 | } 258 | } 259 | synchronized(packetsSent){ 260 | packetsSent.add(new ReliablePacket(seq, timeMS, packet)); 261 | } 262 | sentReliable++; 263 | } 264 | 265 | public void sendPacket(byte[] data){ 266 | sendPacket(RUDPConstants.PacketType.UNRELIABLE, data); 267 | } 268 | 269 | public void requestRemoteStats() { 270 | sendPacket(RUDPConstants.PacketType.PACKETSSTATS_REQUEST, new byte[0]); 271 | } 272 | 273 | /* Helper Methods */ 274 | void initialize() { 275 | initRelyThread(); 276 | reliableThread.start(); 277 | state = ConnectionState.STATE_CONNECTED; 278 | packetHandler.onConnection(); 279 | } 280 | 281 | private void initReceiveThread() { 282 | receiveThread = new Thread(()->{ 283 | while(state == ConnectionState.STATE_CONNECTED){ 284 | byte[] buffer = new byte[RUDPConstants.RECEIVE_MAX_SIZE]; 285 | DatagramPacket packet = new DatagramPacket(buffer, buffer.length); 286 | 287 | try { 288 | socket.receive(packet); 289 | } catch (SocketTimeoutException e) { 290 | state = ConnectionState.STATE_DISCONNECTED; 291 | disconnected("Connection timed out"); 292 | return; 293 | } catch (IOException e) { 294 | if(state == ConnectionState.STATE_DISCONNECTED) return; 295 | System.err.println("[RUDPClient] An error as occured while receiving a packet: "); 296 | e.printStackTrace(); 297 | } 298 | byte[] data = new byte[packet.getLength()]; 299 | System.arraycopy(packet.getData(), packet.getOffset(), data, 0, packet.getLength()); 300 | try { 301 | handlePacket(data); 302 | } 303 | catch(Exception e) { 304 | System.err.print("[RUDPClient] An error occured while handling packet:"); 305 | e.printStackTrace(); 306 | } 307 | packet.setLength(RUDPConstants.RECEIVE_MAX_SIZE); 308 | } 309 | }, "RUDPClient receive thread"); 310 | } 311 | 312 | private void initPingThread() { 313 | pingThread = new Thread(()->{ 314 | try { 315 | while(state == ConnectionState.STATE_CONNECTED){ 316 | byte[] pingPacket = new byte[8]; 317 | NetUtils.writeBytes(pingPacket, 0, System.currentTimeMillis()); 318 | sendPacket(RUDPConstants.PacketType.PING_REQUEST, pingPacket); 319 | 320 | Thread.sleep(RUDPConstants.PING_INTERVAL); 321 | } 322 | } catch (InterruptedException e) { 323 | e.printStackTrace(); 324 | } 325 | }, "RUDPClient ping thread"); 326 | } 327 | 328 | private void initRelyThread() { 329 | reliableThread = new Thread(()-> { 330 | try { 331 | while(state == ConnectionState.STATE_CONNECTING || state == ConnectionState.STATE_CONNECTED || (state == ConnectionState.STATE_DISCONNECTING && !packetsSent.isEmpty())){ 332 | synchronized(packetsSent){ 333 | long currentMS = System.currentTimeMillis(); 334 | long minMS = currentMS+(latency*2); 335 | int i=0; 336 | while(i> it = packetsReceived.entrySet().iterator(); 422 | while(it.hasNext()){ 423 | Entry storedSeq = it.next(); 424 | if(storedSeq.getKey() == seq){ 425 | //System.out.println("[RUDPClient] Packet already received"); 426 | return; 427 | } 428 | if(storedSeq.getValue() < currentTime) it.remove(); //XXX use another thread ? 429 | } 430 | receivedReliable++; 431 | packetsReceived.put(seq, packetOverTime); 432 | } 433 | else received++; 434 | 435 | if(data[0] == RUDPConstants.PacketType.RELY) { 436 | 437 | //byte[] dp = new byte[] {data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9]}; 438 | 439 | //System.out.println("RELY RECEIVED "+toStringRepresentation(dp)+" - "+BytesUtils.toLong(data, 2)); 440 | synchronized(packetsSent){ 441 | for(int i=0;i