Skip to content
RandomAltThing edited this page Aug 22, 2015 · 5 revisions

Requirements

This tutorial assumes you have the JRakLib JAR in your compile and runtime classpaths. You must also have general knowledge of bytes and data types.

Setting up the server interface

In order to use the library, you must create a class that implements the interface net.beaconpe.jraklib.server.ServerInstance. An example is shown below:

import net.beaconpe.jraklib.protocol.EncapsulatedPacket;
import net.beaconpe.jraklib.server.ServerInstance;

/**
 * A very simple ServerInstance class.
 */
public class JRakLibInterface implements ServerInstance{
    @Override
    public void openSession(String identifier, String address, int port, long clientID) {
        
    }

    @Override
    public void closeSession(String identifier, String reason) {

    }

    @Override
    public void handleEncapsulated(String identifier, EncapsulatedPacket encapsulatedPacket, int flags) {

    }

    @Override
    public void handleRaw(String address, int port, byte[] payload) {

    }

    @Override
    public void notifyACK(String identifier, int identifierACK) {

    }

    @Override
    public void handleOption(String option, String value) {

    }
}

I will explain each function above, below. Please note that all the functions above ARE CALLED BY THE LIBRARY, not by you.

  • notifyACK is a function you don't need to worry about. Just leave it empty.

  • handleOption is called whenever you or the library sends an option. The only reason you would do anything with it is because it sends the bandwith (up and down) in bytes every few ticks. The option field will always be "bandwith" and the value will have the numbers.

  • handleRaw is called when a packet that is not recognized is received. This excludes packets send in encapsulated packets.

  • handleEncapsulated is called when an encapsulated packet is received. This is where you will handle the packets send by the client.

  • openSession and closeSession are called when a session is open/closed. The openSession method passes the identifier, address, port and the client's clientID.

Now for the parameters:

The methods above also pass in some parameters, named above in the example. Most are self-explanatory, but the interesting ones I will list here:

  • String identifier is the session's identifier, which is the address and port of the client combined. For example, this could be the client address of a client connecting from localhost: 127.0.0.1:5543

Ok, that's the interface, but how do I start the server?

Good point! Here is a better example of the interface:

import net.beaconpe.jraklib.Logger;
import net.beaconpe.jraklib.protocol.EncapsulatedPacket;
import net.beaconpe.jraklib.server.JRakLibServer;
import net.beaconpe.jraklib.server.ServerHandler;
import net.beaconpe.jraklib.server.ServerInstance;

import java.util.Arrays;

/**
 * A very simple ServerInstance class.
 */
public class JRakLibInterface implements ServerInstance{
    
    private JRakLibServer server;
    private ServerHandler handler;
    
    public JRakLibInterface(){
        server = new JRakLibServer(new MyLogger(), 8000, "0.0.0.0"); //Creates a new server on port 8000 binded to all interfaces
        handler = new ServerHandler(server, this);
    }

    @Override
    public void openSession(String identifier, String address, int port, long clientID) {
        System.out.println("New session!");
    }

    @Override
    public void closeSession(String identifier, String reason) {
        System.out.println("Bye session!");
    }

    @Override
    public void handleEncapsulated(String identifier, EncapsulatedPacket encapsulatedPacket, int flags) {
        System.out.println("EncapsulatedPacket: "+ Arrays.toString(encapsulatedPacket.buffer));
    }

    @Override
    public void handleRaw(String address, int port, byte[] payload) {
        System.out.println("Raw Packet: "+Arrays.toString(payload));
    }

    @Override
    public void notifyACK(String identifier, int identifierACK) {
        //Ignore this
    }

    @Override
    public void handleOption(String option, String value) {
        if(option.equals("bandwith")){
            //You can do things with the bandwith data you get, or you can just leave this method blank
        }
    }
    
    public static class MyLogger implements Logger{

        @Override
        public void notice(String s) {
            System.out.println("[NOTICE]: "+s);
        }

        @Override
        public void critical(String s) {
            System.err.println("[CRITICAL]: "+s);
        }

        @Override
        public void emergency(String s) {
            System.err.println("[EMERGENCY]: "+s);
        }
    }
}

Now this server does things! But, in order to handle packets you must use the ServerHandler class. An example is to run a ticker that calls a function every tick (20 ticks per second). This function would look something similar to this:

 public void tick() {
        if(handler.handlePacket()){
            while(handler.handlePacket()) { } //Handle packets.
        }

        if(server.getState() == Thread.State.TERMINATED){ //Check to see if thread crashed.
            //Print out error information, dump thread stack, etc.
        }
    }

Now, lets finish our example class by making it work:

import net.beaconpe.jraklib.Logger;
import net.beaconpe.jraklib.protocol.EncapsulatedPacket;
import net.beaconpe.jraklib.server.JRakLibServer;
import net.beaconpe.jraklib.server.ServerHandler;
import net.beaconpe.jraklib.server.ServerInstance;

import java.util.Arrays;

/**
 * A very simple ServerInstance class.
 */
public class JRakLibInterface implements ServerInstance{
    
    private JRakLibServer server;
    private ServerHandler handler;
    
    public JRakLibInterface(){
        server = new JRakLibServer(new MyLogger(), 8000, "0.0.0.0"); //Creates a new server on port 8000 binded to all interfaces
        handler = new ServerHandler(server, this);
    }

    public static void main(String[] args) {
        jraklib = new JRakLibInterface();
        while(true) {
            jraklib.tick(); //Handle packets forever.
        }
    }

    public void tick() {
        if(handler.handlePacket()){
            while(handler.handlePacket()) { } //Handle packets.
        }

        if(server.getState() == Thread.State.TERMINATED){ //Check to see if thread crashed.
            //Print out error information, dump thread stack, etc.
        }
    }

    @Override
    public void openSession(String identifier, String address, int port, long clientID) {
        System.out.println("New session!");
    }

    @Override
    public void closeSession(String identifier, String reason) {
        System.out.println("Bye session!");
    }

    @Override
    public void handleEncapsulated(String identifier, EncapsulatedPacket encapsulatedPacket, int flags) {
        System.out.println("EncapsulatedPacket: "+ Arrays.toString(encapsulatedPacket.buffer));
    }

    @Override
    public void handleRaw(String address, int port, byte[] payload) {
        System.out.println("Raw Packet: "+Arrays.toString(payload));
    }

    @Override
    public void notifyACK(String identifier, int identifierACK) {
        //Ignore this
    }

    @Override
    public void handleOption(String option, String value) {
        if(option.equals("bandwith")){
            //You can do things with the bandwith data you get, or you can just leave this method blank
        }
    }
    
    public static class MyLogger implements Logger{

        @Override
        public void notice(String s) {
            System.out.println("[NOTICE]: "+s);
        }

        @Override
        public void critical(String s) {
            System.err.println("[CRITICAL]: "+s);
        }

        @Override
        public void emergency(String s) {
            System.err.println("[EMERGENCY]: "+s);
        }
    }
}

And there you go! A fully functioning JRakLib ServerInstance. The class could use some work, and of course, handling packets, and sending!

Sending packets

In order to send packets, you need to wrap them into EncapsulatedPackets. An example way to send a packet is here:

public void sendPacket(byte[] packetBuffer, String identifier) {
    EncapsulatedPacket pkt = new EncapsulatedPacket();
    pkt.messageIndex = 0;
    pkt.reliability = 2;
    pkt.buffer = packetBuffer;
    handler.sendEncapsulated(identifier, pkt, (byte) (0 | JRakLib.PRIORITY_NORMAL);
}

You may be wondering why the last argument is zero with an bitwise OR and PRIORITY_NORMAL. The first zero is if the packet has the flag NEED_ACK. Unless you understand what it is, just leave that part as zero. The next flag after the OR is the priority of the packet. You can specify flags for the packet, such as if it is very important and it is to be handled immediately instead of being placed in a queue to be sent/handled. Here is an updated method that allows that:

public void sendPacket(byte[] packetBuffer, String identifier, boolean immediate) {
    EncapsulatedPacket pkt = new EncapsulatedPacket();
    pkt.messageIndex = 0;
    pkt.reliability = 2;
    pkt.buffer = packetBuffer;
    if(immediate){
        handler.sendEncapsulated(identifier, pkt, (byte) (0 | JRakLib.PRIORITY_IMMEDIATE);
    } else {
        handler.sendEncapsulated(identifier, pkt, (byte) (0 | JRakLib.PRIORITY_NORMAL);
    }
}