-
Notifications
You must be signed in to change notification settings - Fork 8
Home
The Async-mmocore is primary designed to Massive Multiplayer Online (MMO) Game Servers. The Goal of the Async-mmocore is to provide a easy way to handle MMO connections to a server abstracting the networking layer complexity.
The Async-mmocore is built on top of Java NIO.2 API using Asyncronous Socket Channels. It requires Java 8+ to build and run.
These classes, herein referenced as packets, are the abstraction of data send through the network. All packets must have a Header and a optional payload.
The header is composed by a Short number the carries out the size the packet. The payload is the essential information to the server or client. The packet must be composed by at maximum 32767 bytes. Packets greater than this can be lead to unexpected behaviour.
The client Class is a representation of a external connection. Thus it's the unique source of incoming packets and the target of the outcome packets.
The Client Class must implement the abstract class Client
public class ClientImpl extends Client<Connection<ClientImpl>> {
public ClientImpl(Connection<ClientImpl> connection) {
super(connection);
}
@Override
public boolean decrypt(byte[] data, int offset, int size) {
return myCrypter.decrypt(data, offset, size);
}
@Override
public int encrypt(byte[] data, int offset, int size) {
return myCrypter.encrypt(data, offset, size);
}
@Override
protected void onDisconnection() {
saveDataAndReleaseResources();
}
@Override
public void onConnected() {
doTheInitialJob();
}
public void sendPacket(WritablePacket<ClientImpl> packet) {
writePacket(packet);
}
}
The Client Factory instantiate the new incoming connections.
The Client Factory must implement the interface ClientFactory
public class ClientFactoryImpl implements ClientFactory<ClientImpl> {
@Override
public ClientImpl create(Connection<ClientImpl> connection) {
return new ClientImpl(connection);
}
}
The Packet Handler converts the incoming data into a ReadablePacket.
The Packet Handler must implement the interface PacketHandler
public class PacketHandlerImpl implements PacketHandler<ClientImpl> {
@Override
public ReadablePacket<ClientImpl> handlePacket(ByteBuffer buffer, ClientImpl client) {
ReadablePacket<ClientImpl> packet = convertToPacket(buffer, client);
return packet;
}
}
The Packet Executor executes the incoming Packets.
Although the packet can be execute in the same Thread, it's highly recommended that the Executors executes the packet on a apart Thread. Thats because the Thread that calls the execute method is the same that process the network I/O operations. Thus these threads must be short-living and execute only no-blocking operations.
The Packet Executor must implement the interface PacketExecutor
public class PacketExecutorImpl implements PacketExecutor<ClientImpl> {
@Override
public void execute(ReadablePacket<AsyncClient> packet) {
threadPoolExecutor.execute(packet);
}
}
To listen Connections it's necessary to build a ConnectionHandler
public class ServerHandler {
public void startListen(String host, int port) {
ConnectionHandler<ClientImpl> connectionHandler = ConnectionBuilder.create(new InetSocketAddress(host, port), new ClientFactoryImpl(), new PacketHandlerImpl(), new PacketExecutorImpl()).build();
connectionHandler.start();
}
}
To send a Packet it's necessary to implement the abstract class WritablePacket
public class ServerInfo implements WritablePacket<ClientImpl> {
@Override
protected void write(ClientImpl client, ByteBuffer buffer) {
buffer.put(this.getServerId());
writeString(this.getServerName(), buffer);
buffer.putLong(this.getServerCurrentTime());
buffer.putInt(this.getServerCurrentUsers());
}
}
and just send it through the client
public class ServerHandler {
public void sendServerInfoToClient(ClientImpl client) {
client.sendPacket(new ServerInfo());
}
}
The receiving packet is almost all done by the Async-mmocore. The only part that needs to be implemented to fully read are the steps described in Define a Packet Handler Implementation and Define a Packet Executor Implementation sections.
public class ReceivedServerInfo implements ReadablePacket<ClientImpl> {
@Override
protected void read(ByteBuffer buffer) {
this.serverId = buffer.get();
this.serverName = readString(buffer);
this.serverCurrentTime = buffer.getLong();
this.serverCurrentUsers = buffer.getInt();
}
@Override
public void run() {
showServerInfoToClient();
}
}
The class Connector was designed to provides client side asynchronous connection support. It works just like ConnectionBuilder, so you must define the ClientFactory, the PacketHandler and the PacketExecutor implementations.
public class ConnectionFactory {
public static ClientImpl create(String host, int port) {
ClientImpl client = Connector.create(clientFactory, packetHandler, packetExecutor).connect(new InetSocketAddress(host, port));
return client;
}
}
On ConnectionBuilder has a method filter where can be defined a ConnectionFilter to decide if an address can be connected or not.
public class Filter implements ConnectionFilter {
@Override
public boolean accept(AsynchronousSocketChannel channel) {
return acceptIfAddressIsNotBanned(channel.getRemoteAddress());
}
}
There is a method size on WratablePacket where can set the quantity of bytes will be send on the packet. This can reduce the use of resources using only necessary space on memory. But beware that returning a smaller size than which will really send will cause BufferUnderFlowException errors.
Based on the size of packet a buffer will be picked from a group on the resource pool. There are four groups of buffers: small, medium, large and default. The size of which group can be defined on ConnectionBuilder through the methods: bufferSmallSize, bufferMediumSize, bufferLargeSize and bufferDefaultSize.
The ReadablePackets always will use the bufferDefaultSize, because there is no way to know the income packet size previously. So the bufferDefaultSize must be with the bigger ReadablePacket size.
Each buffer group has a associated pool used to avoid the massive creation and destruction of ByteBuffers that can lead to overhead caused by GC. On ConnectionBuilder can be configured the size of each buffer pool using the methods: bufferSmallPoolSize, bufferMediumPoolSize, bufferLargePoolSize and bufferPoolSize.
The best size for the each group depends on the quantity of simultaneous connections and the frequency of use of each size of buffer i.e. The more used pool must be bigger. However the pool size influences directly on the amount of usage of memory.
The method threadPoolSize controls the size of threads used to handle the IO operations. The default size is based on the number of processors. If the network operation is the bottleneck the thread pool size must be increased. If size provided is less than or equal to 0 or bigger than 32767 will be used a cached thread pool strategy which the Threads will be created on demand and cached.
Generally this error is caused when the size of buffer is smaller than necessary, so the size of the buffer group must be increased.