Plugin Messenger Spigot - Bungeecord

Discussion in 'Spigot Plugin Development' started by Il_totore, Jul 28, 2018.

  1. Hi,

    I need to know how to send messages to bungeecord from an empty server.
    Unfortunately, I do not know how to do it. I would like to be able to send bungee messages without players to the server or simulate a player to allow messages to be sent

    Cordially,
    Il_totore
     
  2. Using two files is the most simplest way to connect each other.

    Sending a messge
    1. Writing data into a file.

    Receiving a message
    1. Reading data from the file.

    I assume you have two files for Bungeecord-sending and Spigot-sending.

    ObjectOutputStream/ObjectInputStream will enable you to treat the data easily.

    You can also use ServerSocket and Socket.
    It might be the most easiest way for skillful programmers.
     
    #2 Toshimichi, Jul 29, 2018
    Last edited: Jul 29, 2018
  3. Could you give an example?
     
  4. You cannot send a plugin message without a player. Use something like Redis or RabbitMQ (side note: they are brokered so you will also need to manage that as well)
     
  5. I can't create a "fake player" for that ?
     
  6. Good luck. It's a hassle to deal with the extra housekeeping faking the player when you could be using something that was literally designed specifically for this purpose.
     
  7. I made this for you.

    Connector(The main class (Facade Design Pattern))
    Code (Java):
    package net.hacbase.connector;

    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.function.Consumer;

    public class Connector<T>{

        private final Consumer<T> listener;
        private final Sender sender;
        private final Reader reader;
        private final List<Object> sendQueue;
        private final Thread sendThread;
        private final Thread readThread;
        private boolean run;

        public Connector(File send, File read, File sendLock, File readLock, Consumer<T> listener) {
            this.listener = listener;
            this.sendQueue = new ArrayList<>();
            this.sendThread = new Thread(this::sendThread);
            this.readThread = new Thread(this::readThread);

            if(send != null && sendLock != null) {
                this.sender = new Sender(send, sendLock);
                if(!resetFile(send))
                    throw new IllegalArgumentException("Failed to initialize the file: " + send.getAbsolutePath());
                sendThread.setDaemon(true);
            } else {
                this.sender = null;
            }

            if(read != null && readLock != null && listener != null) {
                this.reader = new Reader(read, readLock);
                if(!resetFile(read))
                    throw new IllegalArgumentException("Failed to initialize the file: " + read.getAbsolutePath());
                readThread.setDaemon(true);
            } else {
                this.reader = null;
            }

            sendThread.setDaemon(true);
            if(listener != null)
                sendThread.start();

        }

        public void start() {
            run = true;
            sendThread.start();
            readThread.start();
        }

        public void stop() {
            run = false;
        }

        public void interrupt() {
            //Call the interrupt method twice to avoid Thread#sleep
            if(sendThread != null) {
                sendThread.interrupt();
                sendThread.interrupt();
            }
            if(readThread != null) {
                readThread.interrupt();
                readThread.interrupt();
            }
        }

        public void send(Object obj) {
            synchronized (sendQueue) {
                sendQueue.add(obj);
            }
        }


        private void sendThread() {
            while(run) {
                if(!sender.isSendable()) {
                    sleep(15);
                    continue;
                }

                Object send;
                synchronized (sendQueue) {
                    if (sendQueue.size() == 0) {
                        sleep(15);
                        continue;
                    }
                    send = sendQueue.get(0);
                    sendQueue.remove(0);
                }
                try {
                    sender.send(send);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        private void readThread() {
            while(run) {
                if(!reader.isReadable()) {
                    sleep(10);
                    continue;
                }
                try {
                    listener.accept(reader.read());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        private void sleep(int mills) {
            try {
                Thread.sleep(mills);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        private boolean resetFile(File f) {
            try {
                if(f.exists() && !f.delete())
                    return false;
                if(!f.createNewFile())
                    return false;
                return true;
            } catch (IOException e) {
                return false;
            }
        }
    }
    Sender
    Code (Java):
    package net.hacbase.connector;

    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;

    public class Sender {

        private final File file;
        private final File lock;

        public Sender(File file, File lock) {
            this.file = file;
            this.lock = lock;
        }

        public boolean isSendable() {
            return !lock.exists();
        }

        public void send(Object obj) throws IOException {
            if(!file.isFile() && !file.createNewFile())
                throw new IOException("Failed to create the file: " + file.getAbsolutePath());
            if(!lock.createNewFile())
                throw new IOException("Failed to create The lock file: " + lock.getAbsolutePath());
            try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
                out.flush();
                out.writeObject(obj);
            }
        }
    }
    Reader
    Code (Java):
    package net.hacbase.connector;

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;

    public class Reader {

        private final File file;
        private final File lock;

        public Reader(File file, File lock) {
            this.file = file;
            this.lock = lock;
        }

        public boolean isReadable() {
            if(!file.exists())
                return false;
            if(file.getTotalSpace() == 0)
                return false;
            try {
                if(internalRead() == null)
                    return false;
            } catch (Exception e) {
                return false;
            }
            return true;
        }

        public <T> T read() throws IOException{
            T result = internalRead();
            if(!file.delete() || !file.createNewFile())
                throw new IOException("Failed to reset the file: " + file.getAbsolutePath());
            if(!lock.delete())
                throw new IOException("Failed to delete the lock file: " + lock.getAbsolutePath());
            return result;
        }

        private <T> T internalRead() throws IOException {
            if (!file.isFile())
                throw new IOException("The file was not found: " + file.getAbsolutePath());
            try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
                T result = (T) in.readObject();
                return result;
            } catch (Exception e) {
                throw new IOException(e);
            }
        }

    }
    • You can use this code without considering the difference between BungeeCord and Bukkit
    • Debug yourself
    • If you have any questions or bugs, please ask.
     
    #8 Toshimichi, Jul 31, 2018
    Last edited: Jul 31, 2018
  8. Wow Thank you =D
     
  9. I have frequently edited my post so if this code doesn't work please re-paste the code again.
    And you should call Connector#stop method before shutting down the server to avoid potential data loss.
     
    #10 Toshimichi, Jul 31, 2018
    Last edited: Jul 31, 2018
    • Like Like x 1
  10. I found a obvious bug in my code
    I'll fix it later
     
    • Agree Agree x 1
  11. I have succeeded in making my unique connection system.

    Connector
    Code (Java):

    package net.hacbase.connector;

    import java.io.File;
    import java.io.IOException;
    import java.util.concurrent.ConcurrentLinkedDeque;
    import java.util.function.Consumer;

    public class Connector<T> {

        private final Consumer<T> listener;
        private final Sender sender;
        private final Reader reader;
        private final ConcurrentLinkedDeque<Object> sendQueue;
        private final Thread sendThread;
        private final Thread readThread;
        private boolean run;

        public Connector(File send, File read, File sendLock, File readLock, Consumer<T> listener) {
            this.listener = listener;
            this.sendQueue = new ConcurrentLinkedDeque<>();
            this.sendThread = new Thread(this::sendThread);
            this.readThread = new Thread(this::readThread);

            if (send != null && sendLock != null) {
                this.sender = new Sender(send, sendLock);
                if (!resetFile(send))
                    throw new IllegalArgumentException("Failed to initialize the file: " + send.getAbsolutePath());
                sendThread.setDaemon(true);
            } else {
                this.sender = null;
            }

            if (read != null && readLock != null && listener != null) {
                this.reader = new Reader(read, readLock);
                if (!resetFile(read))
                    throw new IllegalArgumentException("Failed to initialize the file: " + read.getAbsolutePath());
                readThread.setDaemon(true);
            } else {
                this.reader = null;
            }

        }

        public void start() {
            run = true;
            if (sender != null)
                sendThread.start();
            if (reader != null)
                readThread.start();
        }

        public void stop() {
            run = false;
        }

        public void interrupt() {
            //Call the interrupt method twice to avoid Thread#sleep
            if (sendThread != null) {
                sendThread.interrupt();
                sendThread.interrupt();
            }
            if (readThread != null) {
                readThread.interrupt();
                readThread.interrupt();
            }
        }

        public void send(Object obj) {
            sendQueue.offer(obj);
        }


        private void sendThread() {
            while (run) {
                if (!sender.isSendable()) {
                    sleep(15);
                    continue;
                }

                if (sendQueue.size() == 0) {
                    sleep(15);
                    continue;
                }
                Object send = sendQueue.poll();
                try {
                    sender.send(send);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        private void readThread() {
            while (run) {
                if (!reader.isReadable()) {
                    sleep(10);
                    continue;
                }
                try {
                    listener.accept(reader.read());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        private void sleep(int mills) {
            try {
                Thread.sleep(mills);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        private boolean resetFile(File f) {
            try {
                if (f.exists() && !f.delete())
                    return false;
                if (!f.createNewFile())
                    return false;
                return true;
            } catch (IOException e) {
                return false;
            }
        }
    }
     
    Sender
    Code (Java):
    package net.hacbase.connector;

    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;

    public class Sender {

        private final File file;
        private final File lock;

        public Sender(File file, File lock) {
            this.file = file;
            this.lock = lock;
        }

        public boolean isSendable() {
            return !lock.exists();
        }

        public void send(Object obj) throws IOException {
            if(!file.isFile() && !file.createNewFile())
                throw new IOException("Failed to create the file: " + file.getAbsolutePath());
            if(!lock.createNewFile())
                throw new IOException("Failed to create The lock file: " + lock.getAbsolutePath());
            try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
                out.flush();
                out.writeObject(obj);
            }
        }
    }
    Reader
    Code (Java):
    package net.hacbase.connector;

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;

    public class Reader {

        private final File file;
        private final File lock;

        public Reader(File file, File lock) {
            this.file = file;
            this.lock = lock;
        }

        public boolean isReadable() {
            if(!file.exists())
                return false;
            if(file.getTotalSpace() == 0)
                return false;
            try {
                if(internalRead() == null)
                    return false;
            } catch (Exception e) {
                return false;
            }
            return true;
        }

        public <T> T read() throws IOException{
            T result = internalRead();
            if(!file.delete() || !file.createNewFile())
                throw new IOException("Failed to reset the file: " + file.getAbsolutePath());
            if(!lock.delete())
                throw new IOException("Failed to delete the lock file: " + lock.getAbsolutePath());
            return result;
        }

        private <T> T internalRead() throws IOException {
            if (!file.isFile())
                throw new IOException("The file was not found: " + file.getAbsolutePath());
            try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
                T result = (T) in.readObject();
                return result;
            } catch (Exception e) {
                throw new IOException(e);
            }
        }
    }

    Simple Test
    Code (Java):
    package net.hacbase.connector;

    import org.junit.Test;

    import java.awt.*;
    import java.io.File;
    import java.util.ArrayList;
    import java.util.List;

    import static org.junit.Assert.assertEquals;

    public class ConnectorTest {

        @Test
        public void connectTest() throws Exception {
            //Files
            File bukkitSending = new File("bukkit-sending");
            File bungeeSending = new File("bungee-sending");
            File bukkitLock = new File("bukkit-sending.lock");
            File bungeeLock = new File("bungee-sending.lock");
            List<String> bukkitReceived = new ArrayList<>();
            List<String> bungeeReceived = new ArrayList<>();
            Connector<String> bukkitConnection = new Connector<>(bukkitSending, bungeeSending, bukkitLock, bungeeLock, bukkitReceived::add);
            Connector<String> bungeeConnection = new Connector<>(bungeeSending, bukkitSending, bungeeLock, bukkitLock, bungeeReceived::add);
            bukkitConnection.start();
            bungeeConnection.start();
            String text = "Hello, world";
            String text2 = "Yay";
            String message = Color.GREEN + "Hello, Bukkit. You're my friend";
            bukkitConnection.send(text);
            bukkitConnection.send(text2);
            bungeeConnection.send(message);
            Thread.sleep(100);
            assertEquals(text, bungeeReceived.get(0));
            assertEquals(text2, bungeeReceived.get(1));
            assertEquals(message, bukkitReceived.get(0));
        }
    }
    • Sending data through this will take about 5ms~50ms
    • The efficiency of this system can be improved more
    • You don't have to touch Sender/Reader class because Connector class is the only one you need
    • The debugging is NOT perfect
    • Connection<A> = new Connection(...); Substitute the class of Object you want to send/receive for A
    • Warning this is an example. Do not try to copy/paste this code
     
    #12 Toshimichi, Aug 1, 2018
    Last edited: Aug 2, 2018
  12. MiniDigger

    Supporter

    I would suggest that you replace your syncronized list with a more suited data structure like a concurrent linked queue
     
    • Agree Agree x 1
  13. Fixed
     
    • Like Like x 1
  14. Er it really doesn't need to be this complicated, your lock file is unneeded, you are not properly handling InterruptedException, you should probably be buffering your IO because of the large amount of data that can possibly be sent through OOI/OS, and the fact that it takes potentially up to 50ms is horrendous especially since you are not traversing a network. You are also making too many checks for useless conditions like the file writable size and if it's a file or a folder; don't do that, an IOException will get thrown anyways and it makes your code unnecessarily long without any added benefit. Not sure why you decided to flush an empty stream as well, that doesn't do anything. Your isReadable method will throw away an already read item if it exists, you should be condensing that into your read method. It really doesn't need to be this complicated lol
     
    • I need to flush my steam before writing to send Array object.
    • Not handling interrupt exception does not affect how the system work too much
    • Without locked file, Sender#send may be called before Reader#read
    • He cannot even connect BungeeCord and Bukkit. Optimization comes after Prototype.
    • I'm Japanese and my naming may be wrong
    And this is just an EXAMPLE. I made this as an EXAMPLE.
    Who cares if the example code is buggy and laggy.
    And I also said "The program can be improved more."
    If you blame my example, how do you fix it for him?
     
    #16 Toshimichi, Aug 2, 2018
    Last edited: Aug 2, 2018
  15. It shouldn't be, the only reason you would need to flush before writing is if you don't want OIS to not block waiting for the header, but it will never do that anyway because you aren't buffering
    That's not an excuse to give a poorly written example. OP is not the only one who is benefitting from this, there are other people who will read this thread and copy/paste the code without reading through everything.
    What a terrible attitude. You're the one who wrote it, not me. I gave pointed feedback on your code and specific ways that you can improve upon it, nothing more. If you don't want to take constructive criticism, you can just flat out say it.
     
  16. I added warning at the bottom of my post.
    I'm not to take care of my code anymore
     
  17. And here I was thinking that you would be open to feedback

    For future readers of this thread: ObjectInputStream has an implicit requirement of having your objects implement Serializable, and is notorious for being slow. Both issues can be fixed by using a DataOutputStream over a buffered stream and manually writing your object schema instead. I would also shy away from using files to share data on a machine-local bungee setup, use a socket with a configurable port on localhost instead. That will get rid of all the busy waiting and the housekeeping needed to manage the files in Toshimichi's solution.
     
  18. How I detect when I received a message ?