In the recent study of the Netty framework, the use of learning materials is Li Linfeng's "Netty Authority Guide." There are few books on Netty in China, and this book is a good entry resource.
I always feel that learning a new framework, in addition to the source code of the research framework, should also use the framework to develop a practical small application. To this end, I choose Netty as a communication framework, the development of a simulation QQ chat room.
The basic framework is designed using Netty as a communication gateway, using JAVAFX to develop the client interface, using spring as the IOC container, and using mybatics to support persistence. This paper will focus on the private stack development of Netty Gateway. Netty Server-side Program Example
Initiates the reactor thread group listening to the connection of the client link and reading and writing to the IO network.
public class Chatserver {private Logger Logger = Loggerfactory.getlogger (Chatserver.class);
Avoid using default thread number parameters private Eventloopgroup Bossgroup = new Nioeventloopgroup (1);
Private Eventloopgroup Workergroup = new Nioeventloopgroup (Runtime.getruntime (). Availableprocessors ());
public void bind (int port) throws Exception {Logger.info ("The server has started, listening for user requests ...");
try{Serverbootstrap B = new Serverbootstrap (); B.group (Bossgroup,workergroup). Channel (nioserversocketchannel.class). Option (Channeloption.so_backlog, 1024). Ch
Ildhandler (New Childchannelhandler ());
Channelfuture f = b.bind (new inetsocketaddress port). sync ();
F.channel (). Closefuture (). sync ();
}catch (Exception e) {logger.error ("", e);
Throw e;
}finally{bossgroup.shutdowngracefully ();
Workergroup.shutdowngracefully ();
} public void Close () {try{if (bossgroup!= null) {bossgroup.shutdowngracefully (); } if (Workergroup!= null) {Workergroup.shutdowngraCefully ();
}}catch (Exception e) {logger.error ("", e); } private class Childchannelhandler extends channelinitializer<socketchannel>{@Override protected void ini
TChannel (Socketchannel arg0) throws Exception {Channelpipeline pipeline = Arg0.pipeline ();
Pipeline.addlast (New Packetdecoder (1024*4,0,4,0,4));
Pipeline.addlast (New Lengthfieldprepender (4));
Pipeline.addlast (New Packetencoder ()); Client 300 seconds to confiscate the contract, will trigger the Usereventtriggered event to Messagetransporthandler pipeline.addlast ("Idlestatehandler", new
Idlestatehandler (0, 0, 300));
Pipeline.addlast (New Iohandler ()); }
}
}
Design of Communication private protocol stack
The private protocol stack is mainly used for data communication across processes, only for internal enterprises, and the protocol design is more dexterous and convenient.
Here, the message definition integrates the message header and the message body. Treats the first short data of a message as the type of the message, and the server handles different business logic based on the message type. Define packet abstract classes, abstract methods
Readfrombuff (Bytebuf buf) and writepacketmsg (Bytebuf buf) are abstract acts of reading and writing data, and the specific reading and writing methods are implemented by the corresponding subclasses. The code is as follows:
Package com.kingston.net;
Import Io.netty.buffer.ByteBuf;
Import java.io.UnsupportedEncodingException;
Public abstract class Packet {//protected String userId;
public void Writetobuff (Bytebuf buf) {Buf.writeshort (Getpackettype (). GetType ());
Writepacketmsg (BUF);
Abstract public void writepacketmsg (Bytebuf buf);
Abstract public void Readfrombuff (Bytebuf buf);
Abstract public Packettype getpackettype ();
Abstract public void Execpacket ();
Protected String readUTF8 (bytebuf buf) {int strsize = Buf.readint ();
byte[] content = new Byte[strsize];
Buf.readbytes (content);
try {return new String (content, "UTF-8");
catch (Unsupportedencodingexception e) {e.printstacktrace ();
Return "";
} protected void WriteUTF8 (Bytebuf buf,string msg) {byte[] content;
try {content = msg.getbytes ("UTF-8");
Buf.writeint (content.length);
Buf.writebytes (content);
catch (Unsupportedencodingexception e) {e.printstacktrace ();
}}
}
It should be noted that since Netty traffic essentially transmits byte data, it is not possible to directly transfer a string field string, which requires a simple codec into a byte array before it can be transmitted.
encoding and decoding of Pojo objects
The sending carrier of the data sender is Bytebuf, so the Pojo object should be encoded in the contract. This project uses Netty's own encoder Messagetobyteencoder to implement a custom coding method. The code is as follows:
Package com.kingston.net;
Import Io.netty.buffer.ByteBuf;
Import Io.netty.channel.ChannelHandlerContext;
Import Io.netty.handler.codec.MessageToByteEncoder;
public class Packetencoder extends messagetobyteencoder<packet> {
@Override
protected void encode ( Channelhandlercontext ctx, Packet msg, bytebuf out)
throws Exception {
msg.writetobuff (out);
}
The receiver actually receives the BYTEBUF data and needs to decode it into the corresponding Pojo object in order to process the corresponding logic. This project uses Netty's own decoder Bytetomessagedecoder (Lengthfieldbasedframedecoder inherits from Bytetomessagedecoder, its role sees below), realizes the custom decoding method. The code is as follows:
package Com.kingston.net.codec;
Import Io.netty.buffer.ByteBuf;
Import Io.netty.channel.ChannelHandlerContext;
Import Io.netty.handler.codec.LengthFieldBasedFrameDecoder;
Import Com.kingston.net.Packet;
Import Com.kingston.net.PacketManager; public class Packetdecoder extends lengthfieldbasedframedecoder{public packetdecoder (int maxframelength, int lengthf Ieldoffset, int lengthfieldlength, int lengthadjustment, int initialbytestostrip) {super (maxframelength, LengthF
Ieldoffset, Lengthfieldlength, Lengthadjustment, Initialbytestostrip); @Override public Object decode (Channelhandlercontext ctx, bytebuf in) throws Exception {bytebuf frame = (bytebuf) (
Super.decode (CTX, in));
if (frame.readablebytes () <= 0) return null;
Short packettype = Frame.readshort ();
Packet Packet = Packetmanager.createnewpacket (Packettype);
Packet.readfrombuff (frame);
return packet; }
}
The communication protocol treats the first short data of Baotou as a package type, gets the corresponding package class definition based on the package type reflection, and invokes the abstract read method to complete the reading of the message body.
parsing and execution of message protocol
The message uses the first short data as the type of the message. To differentiate each message protocol package, a data structure is required to cache the types of protocols and the corresponding message packet definitions. To do this, use an enumeration class to define all protocol packages. The code is as follows:
public enum Packettype {//Business uplink packet//link heartbeat packet reqheartbeat (short) 0x0001, reqheartbeatpacket.class),//new user Registration Requserr
Egister (short) 0x0100, requserregisterpacket.class),//user login Requserlogin (short) 0x0101, Requserloginpacket.class), Chat reqchat (short) 0x0102, reqchatpacket.class),//Business downlink Packet Respheartbeat ((short) 0x2001, Respheartbeatpacket.class) ,//New User Registration Resuserregister ((short) 0x2100, Resuserregisterpacket.class), Resplogin (short) 0x2102, Respuserloginpacket
. Class), Respchat ((short) 0x2103, Respchatpacket.class),;
private short type; Private class<?
Extends abstractpacket> Packetclass; private static map<short,class<? Extends abstractpacket>> packet_class_map = new Hashmap<short,class<?
Extends Abstractpacket>> ();
public static void Initpackets () {set<short> typeset = new hashset<short> ();
Set<class<?>> packets = new hashset<> (); For (Packettype p:packettype.values ()) {Short type = P.getType ();
if (Typeset.contains (type)) {throw new IllegalStateException ("Packet type protocol types repeat" +type);
} class<?> packet = P.getpacketclass ();
if (packets.contains (packet)) {throw new illegalstateexception ("Packet definition duplicates" +p);
} packet_class_map.put (Type,p.getpacketclass ());
Typeset.add (type);
Packets.add (packet);
} packettype (short type,class<? extends abstractpacket> Packetclass) {this.settype (type);
This.packetclass = Packetclass;
public short GetType () {return type;
The public void SetType (short type) {this.type = type; Public class<?
Extends Abstractpacket> Getpacketclass () {return packetclass;
public void Setpacketclass (class<? extends abstractpacket> packetclass) {this.packetclass = Packetclass; public static class<?
Extends Abstractpacket> Getpacketclassby (short packettype) {return packet_class_map.get (Packettype); }
}
The Packettype enumeration class has an initialization method, Initpackets (), that caches the mapping relationships of all package types to the corresponding entity classes. In this way, you can directly get the corresponding packet subclass according to the package type.
After decoding the full message package definition, the corresponding business method can be invoked through the reflection mechanism. This step is done by the package executor, and the code is as follows:
Package com.kingston.net;
Import java.lang.reflect.InvocationTargetException;
Import Java.lang.reflect.Method;
public class Packetexecutor {public
static void Execpacket (Packet Pact) {
if (pact = null) return;
Try {method
m = Pact.getclass (). GetMethod ("Execpacket");
M.invoke (pact, NULL);
catch (Nosuchmethodexception | SecurityException e) {
e.printstacktrace ();
} catch (Illegalaccessexception e) {
e.printstacktrace ();
catch (IllegalArgumentException e) {
e.printstacktrace ();
} catch (InvocationTargetException e) {
E.printstacktrace ();}}
The packet executor actually invokes the business processing method of the corresponding subclass message packet based on reflection.
Here, readers should be able to feel the definition of abstract package packet is the essence of this communication mechanism. It is the abstract public void Readfrombuff (Bytebuf buf); Abstract public void writepacketmsg (Bytebuf buf); Abstract public void Execpacket () Three abstraction methods to isolate the read-write and business logic of various message packs.
Writing here, I can't help thinking about a chat room course design that I did during college. Originally, I used Java as a server, flash as a client, based on the socket to communicate. The communication message body has only one long string, and the communication parties separate the strings several times according to the different message types. If the original agreement type a few more words, estimated to die of heart have. Netty of the half packet reading and writing solution
Messagetobyteencoder and Bytetomessagedecoder Two classes only solve the Pojo codec, and do not deal with sticky packets, unpacking the exception. In this example, using the Lengthfieldbasedframedecoder and Lengthfieldprepender two tool classes, you can easily resolve half-packet read-write exceptions.
server-side and client data communication methods
After the client TCP link is established, the server must cache the corresponding Channelhandlercontext object. This allows the server to send data to all connected users. Send the data Base service class code as follows:
Package com.kingston.base;
Import Io.netty.channel.ChannelHandlerContext;
Import Java.util.Map;
Import Java.util.concurrent.ConcurrentHashMap;
Import Com.kingston.net.Packet;
Import Com.kingston.util.StringUtil; public class Servermanager {//Caching the Correspondence context environment (primarily for business data processing) of all logged-in users private static map<integer,channelhandlercontext>
User_channel_map = new concurrenthashmap<> (); Cached communication context environment corresponding to the login user (mainly for service) private static map<channelhandlercontext,integer> Channel_user_map = new
Concurrenthashmap<> (); public static void Sendpacketto (Packet pact,string userId) {if (pact = null | |
Stringutil.isempty (userId)) return;
map<integer,channelhandlercontext> contextmap = User_channel_map;
if (Stringutil.isempty (Contextmap)) return;
Channelhandlercontext Targetcontext = Contextmap.get (userId);
if (Targetcontext = null) return;
Targetcontext.writeandflush (Pact); /** * Send packets to all online users */public static void Sendpackettoallusers (Packet Pact){if (pact = null) return;
map<integer,channelhandlercontext> contextmap = User_channel_map;
if (Stringutil.isempty (Contextmap)) return;
Contextmap.values (). ForEach ((CTX)-> Ctx.writeandflush (Pact)); /** * Send packets to a single online user/public static void Sendpacketto (Packet pact,channelhandlercontext targetcontext) {if P act = = NULL | |
Targetcontext = = null) return;
Targetcontext.writeandflush (Pact);
public static Channelhandlercontext Getonlinecontextby (String userId) {return user_channel_map.get (userId); public static void Addonlinecontext (Integer userid,channelhandlercontext context) {if (context = = null) {throw NE
W NullPointerException ();
} user_channel_map.put (Userid,context);
Channel_user_map.put (context, userId); /** * Log off User communication channel */public static void Ungisterusercontext (Channelhandlercontext context) {if (Context!= null
{int userId = Channel_user_map.getordefault (context,0); Channel_user_map.remove (context);
User_channel_map.remove (USERID);
Context.close (); }
}
}
demo of the server that simulates user login
1. Demo process for the client to send a req-named upstream package to the server, the server to accept data, directly send a RESP at the beginning of the response package named to the client.
The uplink package Requserlogin code is as follows:
public class Requserloginpacket extends packet{
private long userId;
Private String userpwd;
@Override public
void Writepacketbody (Bytebuf buf) {
buf.writelong (userId);
WriteUTF8 (buf, userpwd);
}
@Override public
void Readpacketbody (Bytebuf buf) {
This.userid = Buf.readlong ();
This.userpwd =readutf8 (BUF);
System.err.println ("id=" +userid+ ", pwd=" +userpwd);
}
@Override public
Packettype Getpackettype () {return
packettype.requserlogin;
}
@Override public
void Execpacket () {
} public
String getuserpwd () {return
userpwd;
}
public void Setuserpwd (String userpwd) {
this.userpwd = userpwd;
}
Public long GetUserID () {return
userId;
}
public void Setuserid (long userId) {
This.userid = userId;
}
}
2. Business logic Service, after receiving the login package, call the corresponding business processing method for processing
@Component public
class Loginservice {
@Autowired
private Userdao Userdao;
public void Validatelogin (Channel Channel, long userId, String password) {
User user = validate (userId, password);
iosession session = Channelutils.getsessionby (channel);
Respuserloginpacket resp = new Respuserloginpacket ();
if (user!= null) {
resp.setisvalid ((byte) 1);
Resp.setalertmsg ("Login Successful");
ServerManager.INSTANCE.registerSession (user, session);
} else{
resp.setalertmsg ("Account or password error");
}
ServerManager.INSTANCE.sendPacketTo (Session, RESP);
}
/**
* Verify account password is consistent
*
/private User Validate (long userId, String password) {
if userId <= 0 | | Stringutils.isempty (password)) {return
null;
}
User user = Userdao.findbyid (userId);
if (user!= null &&
User.getpassword (). Equals (password)) {return
user;
}
return null;
}
}
3. After the business processing, issued a response package. The downlink package Respuserlogin code is as follows:
public class Respuserloginpacket extends abstractpacket{
private String alertmsg;
private byte IsValid;
@Override public
void Writepacketbody (Bytebuf buf) {
writeUTF8 (buf, alertmsg);
Buf.writebyte (IsValid);
}
@Override public
void Readpacketbody (Bytebuf buf) {
this.alertmsg = readUTF8 (BUF);
This.isvalid = Buf.readbyte ();
}
@Override public
Packettype Getpackettype () {return
packettype.respuserlogin;
}
@Override public
void Execpacket () {
System.err.println ("Receive login" + alertmsg);
Loginmanager.getinstance (). Handleloginresponse (this);
Public String getalertmsg () {return
alertmsg;
}
public void Setalertmsg (String alertmsg) {
this.alertmsg = alertmsg;
}
Public byte Getisvalid () {return
isValid;
}
public void Setisvalid (byte isValid) {
this.isvalid = isValid;
}
}
At this point, the main communication logic of the service end is completed.
demo of clients simulating user login
The client-side private protocol and codec are identical to the server. The client focuses on the presentation of the data interface. Only the code that launches the application is given below, as well as sample code for testing traffic.
1. Start the reactor thread group to establish a connection with the server, and handle IO network read and write.
public class Socketclient {/** The current number of times * private int reconnecttimes = 0;
public void Start () {try{connect (clientconfigs.remote_server_ip, clientconfigs.remote_server_port); }catch (Exception e) {}} public void Connect (String host,int port) throws Exception {Eventloopgroup group = new
Nioeventloopgroup (1);
try{Bootstrap B = new Bootstrap (); B.group (Group). Channel (Niosocketchannel.class). Handler (new channelinitializer<socketchannel> () {@Overri de protected void Initchannel (Socketchannel arg0) throws Exception {Channelpipeline pipeline = arg0.
Pipeline ();
Pipeline.addlast (New Packetdecoder (1024*1, 0,4,0,4));
Pipeline.addlast (New Lengthfieldprepender (4));
Pipeline.addlast (New Packetencoder ());
Pipeline.addlast (New Clienttransporthandler ());
}
}); Channelfuture f = b.connect (new inetsocketaddress (host, Port), new Inetsocketaddress (Clientconfigs.local_seRVER_IP, Clientconfigs.local_server_port)). sync ();
F.channel (). Closefuture (). sync ();
}catch (Exception e) {e.printstacktrace (); }finally{//group.shutdowngracefully (); This is no longer elegant. Set the maximum number of times to prevent the server from normal shutdown caused by the empty loop if (Reconnecttimes < clientconfigs.max_reconnect_times) {Reconnect
Server (); }
}
}
}
2. The Clienttransporthandler code to handle business logic is as follows:
public class Clienttransporthandler extends channelhandleradapter{public Clienttransporthandler () {} @Override PU Blic void Channelactive (Channelhandlercontext ctx) {//register session ClientBaseService.INSTANCE.registerSession (
Ctx.channel ()); @Override public void Channelread (Channelhandlercontext ctx, Object msg) throws exception{Abstractpacket Packe
t = (abstractpacket) msg;
PacketManager.INSTANCE.execPacket (packet);
@Override public void Close (Channelhandlercontext ctx,channelpromise Promise) {System.err.println ("TCP closed ...");
Ctx.close (Promise);
@Override public void channelinactive (Channelhandlercontext ctx) throws Exception {System.err.println ("Client shutdown 1"); @Override public void Disconnect (Channelhandlercontext ctx, Channelpromise Promise) throws Exception {Ctx.disconne
CT (promise);
SYSTEM.ERR.PRINTLN ("Client close 2"); @Override public void Exceptioncaught (Channelhandlercontext ctx, Throwable cause) throws Exception {System.err. println ("Client close 3");
Channel Channel = Ctx.channel ();
Cause.printstacktrace ();
if (channel.isactive ()) {System.err.println ("SimpleClient" +channel.remoteaddress () + "exception"); }
}
}
3. Start the server first, then start JavaFX Client (Clientstartup), you can see the login interface
At this point, the chat room login process is basically completed. Limited to space, this demo example does not appear spring,mybatic related code, but the private protocol communication mode code has been given. With a user login example, it's not too difficult to build other business logic.
Finally, the process of writing code. This demo is my residence during the Spring Festival, the use of fragmentary time to do, an average of one hours a day. Many developers should have such a experience, when reading a book often feel can understand, but in fact, they will encounter a variety of card thinking. When I do this demo, I spend more time looking up data.
I will also continue to add features to this project to make it look more and more "flashy". (^-^)
All code is hosted on GitHub (code is refactored several times, slightly different from the Code on the blog)
Full service-side code please--> Netty Chat room Server
Full client code please--> Netty Chat room Client