Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
Buy OSRS Gold

Sell OSRS Gold
IceKontroI

[SNIPPET] Event driven local Server/Client framework

Recommended Posts

Overview

A while back I had to create an implementation of a Server and Client communications system for a personal script of mine. My implementation was shit. Here's a much better one. It's still a bit unrefined so if you have an improvement, post it and I'll consider it for revision. The implementation for a lot of the class events is abstract, meaning the user determines what he wants to do when those events fire. Both Server and Client feature a heartbeat system, where after a certain time interval, the Client will send some info to the Server to let it know it's still alive. Likewise every time interval the Server does the same, but for each Client Connection it has saved. Servers and Clients both always have threads open which wait for Objects to be read. When an Object is read, an event will be fired as mentioned below. If the Object read is a Request (covered later on), then it will be displayed through a separate designated event. Implementations of the following events are not mandatory. If you don't want to handle certain events, simply create a constructor for the Server/Client, but leave the blocks for the unused event blank.

Server events

  • public abstract void onConnectionGain(Connection connection);
    • Fired whenever a Client successfully connects to the Server, providing you the Connection that was just established.
  • public abstract void onConnectionLoss(Connection connection, Exception e);
    • Fired whenever a Connection is dropped, except when the Server closes a Connection manually when its shutdown() method is called.
    • The Exception describes the circumstances that lead to the Connection being dropped.
  • public abstract void onWrite(Serializable object);
    • Fired whenever an Object is written to every Connection in the Server's list of current Connections.
  • public abstract void onWrite(Serializable object, Connection connection);
    • Same as above, but fires once for each Connection in the Server's Connection list at the time of writing the Object.
  • public abstract void onRead(Object object, Connection connection);
    • Fires whenever an Object is received on the Server's end from a Client Connection.
    • Does not fire when a Request is received, the following event handles those cases.
  • public abstract void onRequest(Request request, Connection connection);
    • Fires whenever a Request is received on the Server's end from a Client.
    • This class should handle implementation of how exactly you want to handle Requests.
    • Requests will be covered in detail later on in the post.
  • public abstract void onShutdown();
    • Fires at the end of Server#shutdown().

Client events

  • public abstract void onConnectionGain();
    • Fired when the Client successfully established a Connection to the Server.
  • public abstract void onConnectionLoss(Exception e);
    • Fired when the Client's Connection to the Server drops.
    • The Exception describes the circumstances that lead to the Connection being dropped.
  • public abstract void onRead(Object object);
    • Fires when the Client receives an Object from the Server.
    • This again does not fire when a Request is received, that is handled by the event below.
  • public abstract void onRequest(Request request);
    • Fires when a Request is received on the Client's end, from the Server.
  • public abstract void onShutdown();
    • Fires at the end of Client#shutdown().

Functionality

The primary function of a Server/Client implementation like this is to facilitate communication between the Client and Server. Communication can happen across multiple scripts and even multiple computers. They must all be on the same network, however. Reading more than one Object at a time is unsupported (would corrupt underlying streams), and the same for writing. You can, however, read and write at the same time. To set this up, the user must specify which port the Server will be set up on, and then create Clients that attempt to connect to that port. You can do this through the constructors like so:

Spoiler
int port = 2007;

Server server = new Server(port) {
    
    // Unused
    @Override public void onConnectionGain(Connection connection) {}
    @Override public void onConnectionLoss(Connection connection, Exception e) {}
    @Override public void onWrite(Serializable object) {}
    @Override public void onWrite(Serializable object, Connection connection) {}
    @Override public void onRead(Object object, Connection connection) {}
    @Override public void onRequest(Request request, Connection connection) {}
    @Override public void onShutdown() {}
};

Client client = new Client(port) {
    
    // Unused
    @Override public void onConnectionGain() {}
    @Override public void onConnectionLoss(Exception e) {}
    @Override public void onRead(Object object) {}
    @Override public void onRequest(Request request) {}
    @Override public void onShutdown() {}
};

Note  the implementations of each event in the example above do not need to contain any actual code, they just need to have their headers. Clients will automatically attempt to re-connect to the Server with their designated port number. If the Server connection is lost while a Client is still online, it will fire a onConnectionLoss(...) event and wait 1 second before reconnecting. If a Client connection drops while the Server is still online, the Server will simply fire onConnectionLoss(...) and do nothing special.

When a Connection is dropped, either Server or Client, you won't know about it until you try to read/write to it. This is why both Server and Client implement a "heartbeat" system. Every time interval (0.5 seconds) the Server sends a null Object to each Client, and each Client does the same for its Server. This simply ensures a minimum read/write frequency between the Server and Client so that dropped connections can be handled properly. On read/write from a disconnected Connection, an error will be thrown, which will properly remove the Connection from the Server's list and fire onConnectionLoss(...).

Writing

Communication between Server and Client is two-way, meaning the Server can send Objects and Requests to any of its Connections and the Client can do the same to its designated Server. A Server can have as many Connections as your heap space allows, however a Client can only have 1 Server Connection. Reads happen automatically via their own threads, however writes must be handled directly by the user. Anything you want to write must be Serializable, otherwise you'll get an Exception. Here's how the write methods work:

Server

  • public void write(Serializable object)
    • Simply writes the given Serializable to every Connection in the Server's current Connection list.
    • Fires onWrite(Serializable object);
  • public void write(Serializable object, Connection connection)
    • Writes the Serializable to only the specified Connection.
    • Fires onWrite(Serializable object, Connection connection);

Client

  • public void write(Serializable object)
    • Writes the given object
    • Fires onWrite(Serializable object);

When writing Requests, if the Request is unfulfilled (see section below), it will appear in the recipient's onRequest(...) event.

Requests

Finally I'll get to Requests, which is one of the main things I built this system to handle. A Request is a specialized Object, sent to a recipient, with the expectation for it to be returned, but with some modification. A Client may want to send a Request containing a Point with coordinates (-1, -1), expecting it to be returned with different coordinates. Here's an example of what that might look like:

Spoiler
public class CoordinateRequest extends Request<Point> implements Serializable {

    @Override
    public Point execute(Object... args) {
        
        if (args == null || args.length != 2 || !(args[0] instanceof Integer) || !(args[1] instanceof Integer)) {
            throw new IllegalArgumentException("Bad args, expected 2 ints");
        }
        
        return new Point((int) args[0], (int) args[1]);
    }
}

Simply extend Request<T> where T is the type of Object you want to be able to modify and implement Serializable so that the Request can actually be sent. When you initialize the Request<Point>, it will contain a Point (or otherwise specified type) variable called "target" which will be null until the Request is fulfilled. To fulfill the Request, simply call Request#fulfill(Object ... args) with the proper argument parameters (in this case 2 ints). The Request will automatically process the parameters in the way specified by your abstract implementation of Request#execute(Object ... args), and update the "target" from null to whatever the result actually is. If Request#execute(...) throws an Exception at any point, the Request will simply be processed as unfulfilled and ignored, even if it is written back to the sender. Here's what Request fulfillment looks like:

Spoiler
Server server = new Server(2007) {

    @Override 
    public void onRequest(Request request, Connection connection) {
        
        if (request instanceof CoordinateRequest) {
            // Process the Request in a specific way
            request.fulfill(getCoordinateX(), getCoordinateY());
            // Return to sender
            write(request, connection);
        } else {
            // Handle abnormal case
            System.out.println("Unrecognized Request class: " + request.getClass().getSimpleName());
        }
    }
    
    // Unused
    @Override public void onConnectionGain(Connection connection) {}
    @Override public void onConnectionLoss(Connection connection, Exception e) {}
    @Override public void onWrite(Serializable object) {}
    @Override public void onWrite(Serializable object, Connection connection) {}
    @Override public void onRead(Object object, Connection connection) {}
    @Override public void onShutdown() {}
};

To send Requests, simply call the Client or Server's fulfill(...) method. It will write the specified Request to the target(s), wait for it to be returned as fulfilled, and then return it. If it doesn't receive the Request within a designated time frame, it'll throw a RequestTimeoutException. Requests use System.nanoTime() as an identifier to ensure the originally sent Request is returned at the end of the method call. This is a failsafe to ensure you don't accidentally return a different Request to the one that was originally sent out.

Classes

That's it. Here are the classes:

Spoiler
import java.io.Serializable;

public abstract class Request <T extends Serializable> implements Serializable {

    public enum FulfillmentStatus {FULFILLED, UNFULFILLED, FAILED}

    public long nanoTime;
    public FulfillmentStatus status;
    public T target;

    public Request() {

        this(null);
    }

    public Request(T target) {

        this.nanoTime = System.nanoTime();
        this.status = FulfillmentStatus.UNFULFILLED;
        this.target = target;
    }

    public boolean fulfill(Object ... args) {

        try {
            target = execute(args);
            status = FulfillmentStatus.FULFILLED; // Only fulfilled if executed successfully
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            status = FulfillmentStatus.FAILED;
            return false;
        }
    }

    public abstract T execute(Object ... args);
}
Spoiler
import com.IceKontroI.Networking.Requests.CoordinateRequest;
import com.IceKontroI.Networking.Requests.Request;

import java.io.Serializable;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@SuppressWarnings({"StatementWithEmptyBody", "unchecked", "unused"})
public abstract class Client {

    public int port;
    public volatile Connection connection = null;
    public ConcurrentHashMap<Long, Request> requestQueue = new ConcurrentHashMap<>();
    public Thread readInput = new Thread(() -> {
        while (!Thread.interrupted()) {
            Object object;
            do {
                while (connection == null) {}
                try {
                    object = connection.read();
                    if (object != null) { // Usually heartbeat if null
                        break;
                    }
                } catch (Exception e) {connection = null;}
            } while (true);
            try {
                if (object instanceof Request) {
                    Request request = (Request) object;
                    switch (request.status) {
                        case FULFILLED: {
                            requestQueue.put(request.nanoTime, request);
                            break;
                        }
                        case UNFULFILLED: {
                            onRequest(request);
                            break;
                        }
                        default: break; // Ignore failed requests
                    }
                } else {
                    onRead(object);
                }
            } catch (Exception e) {e.printStackTrace();} // Prevent user error from killing thread
        }
    });
    public Thread reconnect = new Thread(() -> {
        Exception exception = null;
        while (!Thread.interrupted()) {
            while (connection == null) {
                if (exception != null) {
                    onConnectionLoss(exception);
                    try {
                        Thread.sleep(TimeUnit.SECONDS.toMillis(1));
                    } catch (Exception ignored) {}
                }
                try {
                    Socket socket = new Socket();
                    socket.connect(new InetSocketAddress(port));
                    connection = Connection.connectClient(socket);
                    onConnectionGain();
                } catch (Exception e) {
                    connection = null;
                    exception = e;
                }
            }
        }
    });
    public Thread heartbeat = new Thread(() -> {
        while (!Thread.interrupted()) {
            try {
                Thread.sleep(TimeUnit.MILLISECONDS.toMillis(500));
            } catch (InterruptedException e) {e.printStackTrace();}
            write(null);
        }
    });

    public Client(int port) {

        this.port = port;
        this.readInput.start();
        this.reconnect.start();
        this.heartbeat.start();
    }

    public void write(Serializable object) {

        while (connection == null) {}
        try {
            connection.write(object);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public abstract void onConnectionGain();

    public abstract void onConnectionLoss(Exception e);

    public abstract void onRead(Object object);

    public abstract void onRequest(Request request);

    public abstract void onShutdown();

    public <T extends Request> T fulfill(T request) throws RequestTimeoutException {

        write(request);
        long start = System.nanoTime();
        requestQueue.keySet().forEach(nanoTime -> {
            if (start - nanoTime > TimeUnit.SECONDS.toNanos(100)) {
                requestQueue.remove(nanoTime);
            }
        });
        do {} while (System.nanoTime() - start < TimeUnit.MILLISECONDS.toNanos(500) && !requestQueue.keySet().contains(request.nanoTime));
        if (System.nanoTime() - start > TimeUnit.MILLISECONDS.toNanos(500)) {
            throw new RequestTimeoutException(request.getClass().getSimpleName() + " was not fulfilled by Server within the timeout period");
        }
        return (T) requestQueue.get(request.nanoTime); // Fulfilled Request always matches type T, but Compiler disagrees
    }

    public void shutdown() {

        readInput.interrupt();
        reconnect.interrupt();
        heartbeat.interrupt();
        connection.close();
    }
}
Spoiler
import com.IceKontroI.Networking.Requests.Request;
import javafx.util.Pair;

import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

@SuppressWarnings({"StatementWithEmptyBody", "unchecked", "unused"})
public abstract class Server {

    public int port;
    public ServerSocket serverSocket;
    public ConcurrentHashMap<Connection, Thread> connections = new ConcurrentHashMap<>();
    public ConcurrentHashMap<Long, Pair<Request, Connection>> requestQueue = new ConcurrentHashMap<>();
    public Thread heartbeat = new Thread(() -> {
        while (!Thread.interrupted()) {
            try {
                Thread.sleep(TimeUnit.MILLISECONDS.toMillis(500));
            } catch (InterruptedException e) {e.printStackTrace();}
            write(null);
        }
    });

    public Server(int port) {

        this.port = port;
        try {
            this.serverSocket = new ServerSocket(port);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(port);
        }
        heartbeat.start();
        connect(); // Start the connection chain
    }

    public void connect() {

        new Thread(() -> {
            try {
                Socket socket = serverSocket.accept(); // Wait until a connection is made
                if (socket != null) {
                    Connection connection = Connection.connectServer(socket);
                    if (connection != null) {
                        onConnectionGain(connection);
                        Thread reader = new Thread(() -> {
                            while (!Thread.interrupted()) {
                                try {
                                    Object object = connection.read();
                                    if (object != null) { // Usually heartbeat if null
                                        try {
                                            if (object instanceof Request) {
                                                Request request = (Request) object;
                                                switch (request.status) {
                                                    case FULFILLED: {
                                                        requestQueue.put(request.nanoTime, new Pair<>(request, connection));
                                                        break;
                                                    }
                                                    case UNFULFILLED: {
                                                        onRequest(request, connection);
                                                        break;
                                                    }
                                                    default:
                                                        break; // Ignore failed requests
                                                }
                                            } else {
                                                onRead(object, connection);
                                            }
                                        } catch (Exception e) {e.printStackTrace();} // Prevent user error from killing thread
                                    }
                                } catch (Exception e) {disconnect(connection, e);}
                            }
                        });
                        reader.start();
                        connections.put(connection, reader);
                    }
                }
            } catch (Exception e) {e.printStackTrace();}
            connect(); // Extend the connection chain
        }).start();
    }

    public void disconnect(Connection connection, Exception e) {

        if (!(e instanceof ServerShutdownException)) {
            onConnectionLoss(connection, e);
        }
        connection.close();
        if (connections.containsKey(connection)) {
            connections.get(connection).interrupt();
            connections.remove(connection);
        }
    }

    public void write(Serializable object) {

        onWrite(object);
        connections.keySet().forEach(connection -> {
            onWrite(object, connection);
            write(object, connection);
        });
    }

    public void write(Serializable object, Connection connection) {

        try {
            connection.write(object);
        } catch (Exception e) {disconnect(connection, e);}
    }

    public abstract void onConnectionGain(Connection connection);

    public abstract void onConnectionLoss(Connection connection, Exception e);

    public abstract void onWrite(Serializable object);

    public abstract void onWrite(Serializable object, Connection connection);

    public abstract void onRead(Object object, Connection connection);

    public abstract void onRequest(Request request, Connection connection);

    public abstract void onShutdown();

    public <T extends Request> CopyOnWriteArrayList<Pair<T, Connection>> fulfill(T request) {

        CopyOnWriteArrayList<Pair<T, Connection>> fulfilled = new CopyOnWriteArrayList<>();
        long start = System.nanoTime();
        requestQueue.keySet().forEach(nanoTime -> {
            if (start - nanoTime > TimeUnit.SECONDS.toNanos(100)) {
                requestQueue.remove(nanoTime);
            }
        });
        connections.keySet().stream().parallel().forEach(connection -> {
            write(request, connection);
            do {} while (System.nanoTime() - start < TimeUnit.MILLISECONDS.toNanos(500) && !requestQueue.keySet().contains(request.nanoTime));
            if (System.nanoTime() - start > TimeUnit.MILLISECONDS.toNanos(500)) {
                return;
            }
            fulfilled.add(new Pair<>((T) requestQueue.get(request.nanoTime).getKey(), connection)); // Fulfilled Request always matches type T, but Compiler disagrees
        });
        return fulfilled;
    }

    public void shutdown() {

        heartbeat.interrupt();
        connections.keySet().forEach(connection -> disconnect(connection, new ServerShutdownException("Closing connection " + connection.socket.getLocalAddress() + " on Server shutdown")));
        onShutdown();
    }
}
Spoiler
public class ServerShutdownException extends Exception {

    public ServerShutdownException(String message) {

        super(message);
    }
}
Spoiler
public class RequestTimeoutException extends Exception {

    public RequestTimeoutException(String message) {

        super(message);
    }
}
Spoiler
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.Socket;

public class Connection {

    // Vars are final so they can be used as reliable locks/monitors for synchronization

    public final Socket socket;
    public final ObjectInputStream input;
    public final ObjectOutputStream output;

    // Constructor

    public static Connection connectServer(Socket socket) {

        try { // Server and client I/O stream order creation order reversed to prevent deadlock
            ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
            ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
            return new Connection(socket, input, output);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static Connection connectClient(Socket socket) {

        try { // Server and client I/O stream order creation order reversed to prevent deadlock
            ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
            return new Connection(socket, input, output);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public Connection(Socket socket, ObjectInputStream input, ObjectOutputStream output) {

        this.socket = socket;
        this.input = input;
        this.output = output;
    }

    // Synchronized I/O

    public void write(Serializable object) throws Exception {

        // Synchronized independently of read(), just need to prevent concurrent writes on same stream
        synchronized (output) {
            output.writeObject(object);
            output.reset(); // ObjectOutputStreams cause memory leaks since they keep a reference to everything they write
        }
    }

    public Object read() throws Exception {

        // Synchronized independently of write(), just need to prevent concurrent reads on same stream
        synchronized (input) {
            return input.readObject();
        }
    }

    public void close() {

        // Heavily synchronized, ignore errors
        try {
            synchronized (socket) {
                socket.close();
            }
        } catch (Exception ignored) {}
        try {
            synchronized (input) {
                input.close();
            }
        } catch (Exception ignored) {}
        try {
            synchronized (output) {
                output.close();
            }
        } catch (Exception ignored) {}
    }
}

 

Edited by IceKontroI
  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
Array

Interesting post, im sure you will help out some people with this.

I would however recommend to to release the source on a github page instead of pasting it inside your thread.

Also, in your connect function you're using multiple nested try{}catch{} statements, where your simply catch Exception. Since your entire function body is already wrapped inside a try{}catch{Exception e}, it is redundant to also wrap each individual element in one aswell (it will trigger when an exception is thrown anyways), since you're catching the same exception. If you were to catch the specific exception that method can throw, it would not be redundant.

I would also like to see the abstract methods in your code before your methods with a body, so you can immediately see which methods you still have to implement yourself.

 

 

Share this post


Link to post
Share on other sites
44 minutes ago, gef30 said:

Also, in your connect function you're using multiple nested try{}catch{} statements, where your simply catch Exception. Since your entire function body is already wrapped inside a try{}catch{Exception e}, it is redundant to also wrap each individual element in one aswell (it will trigger when an exception is thrown anyways), since you're catching the same exception. If you were to catch the specific exception that method can throw, it would not be redundant.

I went back and looked at it and the structure isn't actually redundant. Each try/catch does serve a purpose. I've added comments to show the purpose of each, but essentially the first one is obligatory and doesn't do anything, the second actually disconnects the Connection because of a read failure, and the third prevents the user's implementation of events from shutting down the thread.

            try { // Initial try/catch to deal with error thrown by serverSocket.accept()
                Socket socket = serverSocket.accept();
                if (socket != null) {
                    Connection connection = Connection.connectServer(socket);
                    if (connection != null) {
                        onConnectionGain(connection);
                        Thread reader = new Thread(() -> {
                            while (!Thread.interrupted()) { // This gets interrupted at the end of method call disconnect(connection, exception)
                                try { // This one catches specifically read() errors, and forces a Connection disconnect
                                    Object object = connection.read();
                                    if (object != null) {
                                        try { // This one prevents errors caused by user's implementation of onRequest() and onRead(), but doesn't force a disconnect
                                            if (object instanceof Request) {
                                                Request request = (Request) object;
                                                switch (request.status) {
                                                    case FULFILLED: {
                                                        requestQueue.put(request.nanoTime, new Pair<>(request, connection));
                                                        break;
                                                    }
                                                    case UNFULFILLED: {
                                                        onRequest(request, connection);
                                                        break;
                                                    }
                                                    default:
                                                        break; // Ignore failed requests
                                                }
                                            } else {
                                                onRead(object, connection);
                                            }
                                        } catch (Exception e) {e.printStackTrace();} // Prevent user error from killing thread
                                    }
                                } catch (Exception e) {disconnect(connection, e);}
                            }
                        });
                        reader.start();
                        connections.put(connection, reader);
                    }
                }
            } catch (Exception e) {e.printStackTrace();}
            connect(); // Extend the connection chain

Share this post


Link to post
Share on other sites
11 minutes ago, IceKontroI said:

I went back and looked at it and the structure isn't actually redundant. Each try/catch does serve a purpose. I've added comments to show the purpose of each, but essentially the first one is obligatory and doesn't do anything, the second actually disconnects the Connection because of a read failure, and the third prevents the user's implementation of events from shutting down the thread.

            try { // Initial try/catch to deal with error thrown by serverSocket.accept()
                Socket socket = serverSocket.accept();
                if (socket != null) {
                    Connection connection = Connection.connectServer(socket);
                    if (connection != null) {
                        onConnectionGain(connection);
                        Thread reader = new Thread(() -> {
                            while (!Thread.interrupted()) { // This gets interrupted at the end of method call disconnect(connection, exception)
                                try { // This one catches specifically read() errors, and forces a Connection disconnect
                                    Object object = connection.read();
                                    if (object != null) {
                                        try { // This one prevents errors caused by user's implementation of onRequest() and onRead(), but doesn't force a disconnect
                                            if (object instanceof Request) {
                                                Request request = (Request) object;
                                                switch (request.status) {
                                                    case FULFILLED: {
                                                        requestQueue.put(request.nanoTime, new Pair<>(request, connection));
                                                        break;
                                                    }
                                                    case UNFULFILLED: {
                                                        onRequest(request, connection);
                                                        break;
                                                    }
                                                    default:
                                                        break; // Ignore failed requests
                                                }
                                            } else {
                                                onRead(object, connection);
                                            }
                                        } catch (Exception e) {e.printStackTrace();} // Prevent user error from killing thread
                                    }
                                } catch (Exception e) {disconnect(connection, e);}
                            }
                        });
                        reader.start();
                        connections.put(connection, reader);
                    }
                }
            } catch (Exception e) {e.printStackTrace();}
            connect(); // Extend the connection chain

though simply catching Exception multiple times is bad practise, i would definitely change the Exception to the actual exception which is thrown.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Similar Content

    • By Realist
      Need Tribot credits for scripts/VIP etc? You're at the right place!
      or
      Do you have credits you want to SELL? Message me!
      I accept cash & 07 Gold/ Rs3 Gold / CS:GO Skins.
       
      1-5 credits is 3m each, other amounts = 1.8-2.3m per credit
       
      Skype is live:realistgold
       
      Discord: Realist#1834 
      Unique Discord ID: 194091681836957696
    • By MNO
      We offer 3 packages, all packages come with the following things pre-installed:
      Windows 10 Java Google Chrome Firefox Dreambot You will get acces through a Remote Desktop Connection (rdp) which we will setup for you.
      Ordering can be done through discord:
      Server: https://discord.gg/jzwNzED
      User: Nex#8648 (UID: 109992979057373184)
      We accept most crypto & rsgp. (Paypal for trusted users)
      We currently offer the following packages:
      Bronze Server
      CPU: Intel Core i7-4770  (or equiv) RAM: 8 GB  Can run: ~12 bots $40/Month
      Iron Server
      CPU: Intel Xeon E3-1275V6  (or equiv) RAM: 64 GB  Can run: ~17 bots $60/Month
      Steel Server
      CPU: AMD Ryzen 7 1700X (or equiv) RAM: 64 GB  Can run: ~25 bots $80/Month  
      TOS:
      1) You will not use this service for any illegal activity.
      2) You will notify us in time for a renewal of service's (>1 week ahead).
      3) All payments are final and we offer no refunds.
      4) By using our service you agree to these terms.
      Breaking any of these will result in instant termination of service with NO refund.
       
    • By Friday321
      Hey I’m new to hitting on mobile and need help
    • By schenkelenkel
      Getting this error, client won't start.

    • By Rileyz
      TRiBot is looking to improve a lot of its customer relationship management, customer on boarding process, customer experience, design elements, community engagement and pretty much everything else you can imagine when it comes to marketing.
      Our goal: To ensure that the marketing done TRULY reflects the experience and does not shine an inaccurate light on what TRiBot is lacking in.
      So I ask, what do you love about TRiBot and what do you hate about TRiBot? What does O S Bot, Rune M8, PowR Bot and Dre amBot do better? (yes I purposely didn't spell it right 😂).
      Love, 
      RileyZ
    • By Fluffee
      Fluffee's Server Setup 3.0
      As some of you may know, I released an automated server setup script a little over a year ago. Albeit that script worked well, I was never fully satisfied with it, as I found it to be kind of messy to use. As a result, I rewrote the script, and restructured it, to add more, and stronger support for the different versions of Debian, the different versions of CentOS and the different versions of Ubuntu. However, with so many different Operating Systems being supported, and many different server providers having different setups, it's hard for me to test every possible scenario on my own. Which is why I've been privately handing this script out, and am now publicly looking for help.

      What does the script do?
      Similar to my previous setup script, this script changes the SSH port and creates a new user for connecting and using the server and disables root SSH connections; it installs the latest version of TightVNC Server (1.3.10) and sets that up with the desired port and password. It installs JDK 102 (32 or 64 bit depending on the OS), and installs TRiBot and OSBuddy and sets up the appropriate .jar file associations.
      What operating systems does it support?
      - CentOS (6.x and 7.x) (32 and 64 bit)
      - Debian (7.x and 8.x) (32 and 64 bit)
      - Ubuntu (12.x, 14.x and 16.x) (32 and 64 bit)
      Does it work?
      As far as I know, yes it does in fact work, and it works well if I might add. However, given that I can't test every possible setup, there is the potential for differences and issues
      How do I run the script?
      Login as root to a fresh VPS/dedi running one of the supported operating systems. From there run the commands listed below and follow the onscreen instructions. For those who would like to see the script's code, it can be found on my bitbucket here. The commands to run are as follows:
      wget --no-check-cert https://bitbucket.org/teamfluffee/fluffees-server-setup/raw/add-shared-functions/fluffees-server-setup.sh && chmod +x fluffees-server-setup.sh && bash fluffees-server-setup.sh -v Version History
       
    • By Mr Dapper
      looking for private dice bot. i'd explain but it's straight forward just the basic spam messages and bot. looking to be low ban rate lmk how much u guys charge for ur scrips !
    • By TheIronPride
      Hey everyone i been using looking glass for a few months now, and just today i no longer see it as an option, i only see "new Client" and "New Client (advanced)" where there is normally a "new Client (looking Glass)" option, any suggestions?
    • By twizzletwanger
      Hi!
      I have multiple proxies that I like to jump to and from. Can I do this without having to close the app and re-open to select a new proxy?
      Thanks!
    • By twizzletwanger
      Hi! 
      I'm somewhat new to this and am looking for some guidance. Money is of no concern, I just need the communities best recommendations. I'm looking for scripts meeting the following criteria:
      f2p world
      mining
      money making
      magic casting
      I've also been using LG with OSBuddy, however; My accounts seem to be getting hacked within 48 hours of using the OSB. Not sure what the problem is there, but I've heard of it happening with that particular client before. Let me know what clients you recommend. I don't think it has anything to do with my botting hours as I've only been experimenting with a few scripts in 1-2 hour sessions every 24 hours on innocent accounts several hours old using proxys. Look forward to your opinions.
      Thanks!
       
  • Our picks

    • Over the past few months, I’ve been working diligently on a new project - TRiBot X. Everything has been written from the ground up, with all of the best practices of software engineering. Every aspect of TRiBot has been re-imagined to support three main goals: flexibility, useability, and reliability.
      • 48 replies
    • Come give us feedback on the next version of TRiBot!
      • 86 replies
    • TRiBot is looking to improve a lot of its customer relationship management, customer on boarding process, customer experience, design elements, community engagement and pretty much everything else you can imagine when it comes to marketing.

      Our goal: To ensure that the marketing done TRULY reflects the experience and does not shine an inaccurate light on what TRiBot is lacking in.

      So I ask, what do you love about TRiBot and what do you hate about TRiBot? What does O S Bot, Rune M8, PowR Bot and Dre amBot do better? (yes I purposely didn't spell it right 😂).

      Love, 

      RileyZ
      • 25 replies
    • Over the last three weeks, I've been working on upgrading our server infrastructure. It's finally ready and is now live!

      Why?

      Increased reliability - less server errors


      Increased availability - less downtime


      Increased security - keeping us and you secure


      Increased capacity - ability to serve you better


      Increased speed - less waiting for things to load


      Faster development - server and service updates will come faster


      What are the changes?

      Move from a single AWS EC2 instance to AWS ECS (Elastic Container Service)


      Distributed computing


      Load balancing


      Git management of server files and filesystem


      Redis caching


      How?

      AWS ECS (with 10 EC2 instances)


      AWS ElastiCache (Redis)


      AWS Load Balancing


      AWS EFS (Elastic file system)


      Please bare with us as I continue to tune the server for maximum performance. Slow loading speeds may occur temporarily. I thank everyone for their patience.

      Please post on this thread if you experience any issues other than slow loading times.
      • 51 replies
    • This release will:

      Fix prayers and world hopper API (Thanks @JoeDezzy1 and @erickho123)


      Improve banking API (Thanks @Encoded)


      Adds methods for returning and using Java Lists, rather than arrays


      Slightly randomizes some hardcoded behaviour


      Removes sleeps from waitConditions; the efficiency saving potential is negligible in these use-cases, therefore cleaner code is preferable


      Other back-end improvements





      Note: If you are using LG, please restart both the RS client and TRiBot.
      • 90 replies
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...