Sample Engineering Code
Can be downloaded from the attachment
Specific instructions and usage are described later.
Demand and Purpose
A game server needs to handle a variety of business logic, each of which corresponds to a request message and a response message. Then the server needs to automatically distribute these different messages to the corresponding business logic.
The simplest way to do this is to base the Type field in the request message use switch case for handling separately, but this way, as the message increases, it shows some bad taste: a long lump is not very good-looking; if you want to add new messages, new logic, or remove new messages, new logic, In the code not only to modify these messages and logic, but also have to modify this long lump of Swtich case, such changes appear to be superfluous.
So our goal is to automate the distribution of messages, and to minimize the code changes when adding, modifying, and deleting messages and logic without having to modify the code to distribute the message.
Implementation Principle
In implementation, annotations are used (annotation)
Package com.company.game.dispatcher.annotation;
Import Java.lang.annotation.ElementType;
Import java.lang.annotation.Retention;
Import Java.lang.annotation.RetentionPolicy;
Import Java.lang.annotation.Target;
/**
* Modifies the message class and the business logic execution Class
* msgtype Specifies the corresponding type, counting starting from 1
* @author Xingchencheng
* *
/@Target ( Elementtype.type)
@Retention (retentionpolicy.runtime) public
@interface Usermsgandexecannotation { Short
msgtype ();
}
The unique field Msgtype represents the message type, which is the client-server engagement, where we count from 1.
When we want to add an addition message, we use this annotation to modify our Request message class:
Package com.company.game.dispatcher.msg;
Import com.company.game.dispatcher.annotation.UserMsgAndExecAnnotation;
/**
* Add Request message Class
*
* @author xingchencheng
*
/@UserMsgAndExecAnnotation (Msgtype = Msgtype.add) Public
class Useraddrequest extends Requestmsgbase {
private double leftnumber;
Private double rightnumber;
Public Useraddrequest () {
super (Msgtype.add);
}
Public double Getleftnumber () {return
leftnumber;
}
public void Setleftnumber (double leftnumber) {
this.leftnumber = leftnumber;
}
Public double Getrightnumber () {return
rightnumber;
}
public void Setrightnumber (double rightnumber) {
rightnumber = rightnumber;
}
}
Why do you have to decorate it like this? First from the service-side decoding (decode), in the instance code, a request message is specified:
0-1 bytes representing the length of the entire message (in bytes)
2-3 bytes represent the message type, corresponding to the annotation msgtype
The remaining is the JSON string for the message (UTF-8 encoding)
We need to get a class object of the corresponding request message class based on the 2-3-byte representation of msgtype, and use this class object to serialize the JSON string to get the specific request object. So how to get class object according to Msgtype. That's why you use annotation.
Before the server-side program starts, the following processing is performed:
Msgtype-> request, Response class object private static Map<short, class<?>> Typetomsgclassmap; Gets the class object of the corresponding message class based on the type public static class<?> Getmsgclassbytype (short type) {return Typetomsgclassmap.get (type)
; /** * Initialize TYPETOMSGCLASSMAP * Traverse Packet com.company.game.dispatcher.msg * Get class file for message classes * * @throws Classnotfounde Xception * @throws IOException */public static void Inittypetomsgclassmap () throws ClassNotFoundException, Ioexc
eption {map<short, class<?>> tmpmap = new Hashmap<short, class<?>> ();
Set<class<?>> Classset = getclasses ("com.company.game.dispatcher.msg"); if (classset!= null) {for (class<?> clazz:classset) {if clazz.isannotationpresent (usermsgandexecannotat
Ion.class)) {usermsgandexecannotation annotation = clazz.getannotation (Usermsgandexecannotation.class);
Tmpmap.put (Annotation.msgtype (), clazz); }} Typetomsgclassmap = Collections.unmodifiAblemap (TMPMAP); }
The program Initializes a mapping, finds the class of the requested message class in the specified package, reads the annotation on the class, and saves it to a map, so that the class object can be obtained based on the map at the following Msgtype.
And then give the implementation of the decoder:
Package Com.company.game.dispatcher.codec;
Import java.util.List;
Import Com.company.game.dispatcher.util.ClassUtil;
Import Com.company.game.dispatcher.util.GsonUtil;
Import Com.google.gson.Gson;
Import Io.netty.buffer.ByteBuf;
Import Io.netty.channel.ChannelHandlerContext;
Import Io.netty.handler.codec.ByteToMessageDecoder; /** * Decoder * Both client and server use * 0-1 bytes to indicate the length of the entire message (in bytes) * 2-3 bytes for message type, corresponding annotation * The rest is the JSON string for the message (UTF-8 code) * * @author X Ingchencheng * */public class Msgdecoder extends Bytetomessagedecoder {@Override protected void decode (Channelhand Lercontext CTX, Bytebuf buf, list<object> List) throws Exception {if (Buf.readablebytes () < 2) {Retu
Rn
} Gson Gson = Gsonutil.getgson ();
Short jsonbyteslength = (short) (Buf.readshort ()-2);
Short type = Buf.readshort ();
byte[] tmp = new Byte[jsonbyteslength];
Buf.readbytes (TMP);
String json = new String (tmp, "UTF-8"); Class<?> clazz = Classutil.getmsgclassbytype (type);
Object msgobj = Gson.fromjson (JSON, clazz);
List.add (Msgobj); }
}
After decoding is complete, the program goes to the handler of the service side:
@Override
protected void channelRead0 (Channelhandlercontext ctx, Object msgobject)
throws Exception {
// Distribute the message to the corresponding message Processor
Dispatcher.submit (Ctx.channel (), msgobject);
The dispatcher code is as follows:
Package com.company.game.dispatcher;
Import Io.netty.channel.Channel;
Import Java.util.concurrent.ExecutorService;
Import java.util.concurrent.Executors;
Import Com.company.game.dispatcher.exec.BusinessLogicExecutorBase;
Import Com.company.game.dispatcher.msg.RequestMsgBase;
Import Com.company.game.dispatcher.util.ClassUtil; /** * Abstract Distributor * Multithreaded execution * A Message object Msgobject specify a business logic object Executor * Submit to the thread pool * @author Xingchencheng * */public class D
Ispatcher {private static final int max_thread_num = 50;
private static Executorservice Executorservice = Executors.newfixedthreadpool (max_thread_num);
public static void Submit (Channel Channel, Object msgobject) throws Instantiationexception, Illegalaccessexception {
Requestmsgbase msg = (requestmsgbase) msgobject;
class<?> Executorclass = Classutil.getexecutorclassbytype (Msg.gettype ());
Businesslogicexecutorbase executor = (businesslogicexecutorbase) executorclass.newinstance (); Executor.setchannel (ChAnnel);
Executor.setmsgobject (Msgobject);
Executorservice.submit (executor); }
}
We see that in the code is also based on Msgtype to obtain a corresponding class object, and a new object out, to the thread pool for concurrent execution, this object is the business logic processor object, it implements the Runnable interface, do some business logic processing. The mapping process to get the class object from Msgtype is the same as the mapping principle mentioned earlier, and you can see the code. To post code for the Business logic processor object:
Package com.company.game.dispatcher.exec;
Import com.company.game.dispatcher.annotation.UserMsgAndExecAnnotation;
Import Com.company.game.dispatcher.msg.MsgType;
Import Com.company.game.dispatcher.msg.UserAddRequest;
Import Com.company.game.dispatcher.msg.UserAddResponse;
/** *
Specific business logic
* Implement addition
*
* @author xingchencheng
*
/@UserMsgAndExecAnnotation ( Msgtype = msgtype.add) public
class Useraddexecutor extends Businesslogicexecutorbase {public
void run () C14/>useraddresponse response = new Useraddresponse ();
if (this.msgobject instanceof useraddrequest) {
useraddrequest request = (useraddrequest) this.msgobject;
Double result = Request.getleftnumber () + Request.getrightnumber ();
Response.setresult (result);
Response.setsuccess (true);
else {
response.setsuccess (false);
}
SYSTEM.OUT.PRINTLN ("Service End processing Result:" + response.getresult ());
Channel.writeandflush (response);
}
Note that it also has to be decorated with annotation.
The idea is that, if you want to add a request, in the sample code, you need to do 3 things: Add a type Add in Msgtype request the corresponding message class add business logic processor class
You do not need to modify the code for message distribution.
description and use of the sample project
Engineering can be built at the beginning of the article in the GitHub or the attachment to the project using MAVEN3 build, the result is a jar, can run the server and the client by the command line is only an example, and not too much consideration of exception handling, performance, and other aspects of no unit test and other tests
The command-line tools are provided, and the Help information is as follows:
Server-side Startup command:
Client startup command:
Conclusion
The description of this article may not be clear, and a better way is to look directly at the code.
There must be a better way to distribute the message, here is just a tip, I hope that the passing of you can provide a better way to reference. Size: 16.7 KB size: 34.8 KB game-dispatcher.zip (1.6 MB) Download number of times: 49 Size: 27.5 KB View picture attachments