Only this pageAll pages
Powered by GitBook
1 of 3

Latest (v1.3.3)

Loading...

Loading...

Loading...

Simple Network Chat Walkthrough

Getting a reference to ProtonManager

Before you do anything with Proton, you need a reference to ProtonManager. In your onEnable() method, store a reference to it.

public void onEnable(){
    this.protonManager = ProtonProvider.get();
}

A reference to ProtonManager will allow you to use all of Proton's API.

Forward chat messages network-wide

Create an event listener for the async chat event and broadcast your custom data there. In this case, we opted for a container class which holds the player name and message.

@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent event){
    PlayerMessage message = new PlayerMessage(event.getPlayer().getName(), event.getMessage());
    protonManager.broadcast("networkchat", "chatMessage", message);
}

Here we're broadcasting to the namespace networkchat with the subject chatMessage

Listen for the broadcasted message

Here we will listen for broadcasted messages using a MessageHandler

@MessageHandler(namespace = "networkchat", subject="chatMessage")
public void onChatReceived(PlayerMessage message){
    getServer().broadcastMessage(String.format("<%s> %s", message.getPlayer(), message.getMessage()));
}

Notice: We are listening for the same namespace and subject as we sent. We are also expecting the same datatype we sent.

We must also register our MessageHandlers with Proton. This is done through the registerMessageHandlers method.

public void onEnable(){
    this.protonManager = ProtonProvider.get();
    this.protonManager.registerMessageHandlers(this);
}

We're done! You will now be able to chat network-wide.

Full Code (with Bukkit API calls)

NetworkChat.java
public class NetworkChat extends JavaPlugin implements Listener {

    ProtonManager manager;
 
    public void onEnable(){
        manager = Proton.getProtonManager();
        if(manager != null){
            manager.registerMessageHandlers(this, this);
        }
        getServer().getPluginManager().registerEvents(this, this);
    }

    @EventHandler
    public void onPlayerChat(AsyncPlayerChatEvent event){
        PlayerMessage message = new PlayerMessage(event.getPlayer().getName(), event.getMessage());
        manager.broadcast("networkchat", "chatMessage", message);
    }

    @MessageHandler(namespace = "networkchat", subject="chatMessage")
    public void onChatReceived(PlayerMessage message){
        getServer().broadcastMessage(String.format("<%s> %s", message.getPlayer(), message.getMessage()));
    }

    public class PlayerMessage {
        String player;
        String message;

        public PlayerMessage(String player, String message) {
            this.player = player;
            this.message = message;
        }

        public String getPlayer() {
            return player;
        }
        
        public String getMessage() {
            return message;
        }
    }
}

Introduction

Proton

Proton is a library which aims to give you a reliable and flexible solution to cross-server messaging. It uses your choice of Redis or RabbitMQ. Other methods, such as Plugin Messaging, are either difficult and messy to implement or have restrictions such as the inability to send messages to a server with no active players. Proton is different in that it

  1. creates a simple system for messaging between servers and

  2. is robust and versatile enough to where you can implement any messaging need you require.

Proton is still being actively developed and tested. Your feedback is welcome.

What is RabbitMQ?

RabbitMQ is a queue based messaging broker. In its simplest form, a producer sends a message to a queue, then a consumer consumes that message from the queue. However, RabbitMQ can and usually does support more complex networks than that. Proton acts as an interface between your plugin and the client API for RabbitMQ. RabbitMQ can be hosted easily on your own servers or by a cloud provider. You can read more here.

Can I use other AMQP services?

While Proton was built with RabbitMQ in mind, it should be able to support any AMQP service. So far it has only been tested with RabbitMQ and LavinMQ.

What is Redis?

Redis a in-memory data structure store which is often used as a database or message broker. While it is not solely used for brokering messages, it is very fast and is a certainly a good choice for many situations. You can read more here.

Getting Started

A guide that should kickstart your usage of Proton. Prefer to go through an example? Check out the global chat walkthrough.

Proton as a Dependency

Using Maven

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>
<dependency>
    <groupId>com.github.mcgrizzz</groupId>
    <artifactId>Proton</artifactId>
    <version>v1.3.2</version>
    <scope>provided</scope>
</dependency>

Using Gradle

repositories {
    ...
    maven { url 'https://jitpack.io' }
}
dependencies {
        implementation 'com.github.mcgrizzz:Proton:v1.3.0'
}

Setting-up

Setting-up RabbitMQ

If you want to use Proton with RabbitMQ, you will first need to set up an instance of RabbitMQ.

You can do this in one of two ways:

1. Host it yourself

Here is a great guide to setting it up yourself: https://www.rabbitmq.com/download.html

You will most likely need to setup a new username and password for your rabbitMQ instance: https://www.rabbitmq.com/access-control.html#user-management

This is for two reasons. One, RabbitMQ restricts the access of the default guest account only to localhost connections. Two, leaving the default username/password leaves you open to attacks.

2. Find an online host

Here are some hosts for RabbitMQ that you may find helpful. One of them actually has a free tier that may fit your needs.

  1. Stackhero - No free tiers but the lowest tier can probably support up to 200 servers

  2. CloudAMQP - Expensive dedicated servers but there are cheaper and free shared servers.

*When choosing a host and plan consider your network's size and needs.

Setting-up Redis

This section is under-construction. Please take a look at Redis Quickstart

Proton's config.yml

Before integrating with Proton, you should configure your servers' Proton configs.

Let's take a look at the config.

rabbitMQ:
  useRabbitMQ: true
  host: "localhost"
  virtualHost: '/'
  port: 5672
  authorization:
    useAuthorization: true
    username: guest
    password: guest
redis:
  useRedis: false
  host: "localhost"
  port: 6379
  usePassword: true
  password: "password"
identification:
  clientName: "client1"
  groups: []
checkForUpdates: true

There are a lot of options here, but let's look at them in smaller pieces.

The first section is for the configuration of RabbitMQ.

useRabbitMQ sets whether Proton will try to use RabbitMQ host is the ip of your RabbitMQ instance. port is its port which you will probably not need to change. virtualHost is the virtual host which you can think of as an extension of the host. If you want to learn more about this there's a great writeup here. useAuthorization sets whether Proton should try to authorize the connection username is the username for the connection password is the password for the connection

rabbitMQ:
  useRabbitMQ: true
  host: "localhost"
  virtualHost: '/'
  port: 5672
  authorization:
    useAuthorization: true
    username: guest
    password: guest

Next we have the Redis section of the config.

useRedis sets whether Proton will try to use Redis. (NOTE: Proton only uses one service at a time, if both are selected, Proton will use RabbitMQ) host is the ip of your Redis instance. port is its port usePassword sets whether Proton will try to use a password for the connection useAuthorization is the password Proton will try to use. username is the username for the connection password is the password for the connection

redis:
  useRedis: false
  host: "localhost"
  port: 6379
  usePassword: true
  password: "password"

Lastly, we have the identification section of the config. This is what Proton uses to know which servers are which.

clientName should be a unique name to your server. No two servers should have the same name. This is important if you want to know later on which server a message came from.

groups is a list of groups that the current server belongs to. For example, you can have a server that belongs to the group 'hub'. Therefore, when you send a message to the hub group, only servers in that group will receive that message. You can leave groups empty if you don't need it.

identification:
  clientName: "client1"
  groups: []

checkForUpdates is a boolean value that enables update checks which will run only once, on server startup.

Your plugin

Proton can run either on Bukkit or Bungeecord

Just make sure that in your plugin.yml, you include the dependency for Proton.

main: org.test.Test
name: TestPlugin
version: 1.0
depend:
  - Proton

Just make sure that in your bungee.yml, you include the dependency for Proton.

main: org.test.Test
name: TestPlugin
author: Me
version: 1.0
depends: ["Proton"]

Proton Usage

Getting an instance to ProtonManager

To start using Proton, you should first get an instance of ProtonManager.

private ProtonManager protonManager;

@Override
public void onEnable() {
    this.protonManager = ProtonProvider.get();
}

ProtonManager should not be null at this point. If it is, Proton will throw an error. You should check your configuration and dependencies if this occurs.

Sending your first message

Now that you have a reference to ProtonManager, you can send your first message.

String namespace = "namespace";
String subject = "subject";
String recipient = "recipient";
Object data = new Object();
protonManager.send(namespace, subject, data, recipient);

Let's break down these arguments.

  • namespace identifies your organization or plugin. This is what keeps your messages within the scope of your plugin or organization

  • subject is used to identify the type of message you're sending, you can put any value, but we recommend something relevant and descriptive.

  • recipient is used to define the client or group you wish to send to.

  • data is the object that you wish to send.

The data you send can be any object or primitive. The only caveat is that is that is must be Json serializable. Otherwise, you will receive exceptions.

If you want to send a message to all clients that may be listening to a specific namespace and subject you can use the broadcast method instead:

String namespace = "myPluginOrOrganization";
String subject = "subjectOfMyMessage";
Object data = new Object();
protonManager.broadcast(namespace, subject, data);

namespace and subjectform what is called a MessageContext. Each MessageContext can only have one defined datatype. So if you define a namespace and subject, make sure you always send the same type of data through that context.

The use of . (period) is not allowed when defining a namespace, subject, recipient, or group. It is a reserved character used for internal processing.

Receiving a message

We tried to model the message receive system similarly to the Event system you probably use regularly.

In any class or object, you can define a MessageHandler. A MessageHandler is an annotated method which receives data for a specific MessageContext.

Let's take a look at the receiving end of the message sent above.

class MyClass {
    ...
    @MessageHandler(namespace="namespace", subject="subject")
    public void anyMethodName(Object data){
        //do something with the data received
    }
    ...
}

If you want to know the sender of the message, you can attach a second parameter to your MessageHandler method.

class MyClass {
    ...
    @MessageHandler(namespace="namespace", subject="subject")
    public void anyMethodName(Object data, MessageAttributes attr){
        String senderName = attr.getSenderName();
        UUID senderID = attr.getSenderID();
    }
    ...
}

The code within a MessageHandler is synchronous with Bukkit by default. This was a design decision to match the fact that most API calls must be synchronous. However, you can receive messages asynchronously if you wish by adding an optional attribute.

@MessageHandler(namespace="namespace", subject="subject", async=true)

If using bungee, this flag will be ignored and all handlers will be called asynchronously

The final step to actual receive any messages, is to register your MessageHandler(s). Similarly to the Event API, you just register your class instance with the ProtonManager.

@Override
public void onEnable() {
    this.protonManager = ProtonProvider.get();
    this.protonManager.registerMessageHandlers(this, new MyClass());
}

If you want, you can register all of your handlers in one call.

this.protonManager.registerMessageHandlers(handler1, handler2, handler3...);

If you have any lingering questions, feel free to consult the examples repo. You can also submit question issue here.