JBoss Marshalling 是一個 Java 對象序列化包,對 JDK 默認的序列化框架進行了優化,但又保持與 Serializable 接口的兼容,同時增加了一些可調用的參數和附加的屬性,這些參數可通過工廠類進行配置。
本章主要內容包括:
- Marshalling環境配置
- 基於Netty和Marshalling的圖書訂購案例
- 基於Netty和Marshalling的自定義消息案例
兩個案例帶你搞定JBoss Marshalling編解碼在Netty中的應用
1. Marshalling環境配置
本節主要介紹Marshalling開發環境的配置,本文所用工具版本如下:
- JDK 1.8
- Netty 4.0
- IDEA 2020.1
- Marshalling 2.0.9
關於JDK的安裝在這裏就不再介紹,自行百度即可,關於Netty的安裝,大家可以移步博客閱讀。
在本次實踐中,我們使用的是基於maven的項目,因此我們只需要在pom.xml文件中添加相應的依賴即可,在這裏我們給出Marshalling的依賴。
<!-- jboss-marshalling編解碼和序列號架包 -->
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling</artifactId>
<version>2.0.9.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling-serial</artifactId>
<version>2.0.9.Final</version>
</dependency>
</dependencies>
2. 基於Netty和Marshalling的圖書訂購案例
本節我們用一個圖書訂購的案例來介紹一下Marshalling編解碼在Netty中的應用,我們首先給出需要編解碼消息的定義,然後分服務端和客戶端分別介紹實現代碼。
2.1 圖書訂購消息定義
首先給出客戶端發出的訂購消息,消息定義如下所示:
字段名稱 | 字段類型 | 備註 |
---|---|---|
subReqID | 整型 | 訂購編號 |
userName | 字符串 | 用戶名 |
productName | 字符串 | 訂購的產品名稱 |
productNumber | 字符串 | 訂購者電話號碼 |
address | 字符串 | 訂購者的家庭住址 |
服務端接收到客戶端的訂購消息之後,對訂單進行驗證,如果符合條件則返回訂購成功的消息給客戶端,具體消息定義如下:
字段名稱 | 字段類型 | 備註 |
---|---|---|
subReqID | 整型 | 訂購編號 |
respCode | 整型 | 訂購結果:0表示成功 |
desc | 字符串 | 可選的詳細描述信息 |
下面給出兩個消息的具體代碼:
package netty.codec.pojo;
import java.io.Serializable;
/**
* created by LMR on 2020/5/20
*/
public class SubscribeReq implements Serializable {
/**
* 默認的序列號ID
*/
private static final long serialVersionUID = 1L;
private int subReqID;
private String userName;
private String productName;
private String phoneNumber;
private String address;
public final int getSubReqID() {
return subReqID;
}
public final void setSubReqID(int subReqID) {
this.subReqID = subReqID;
}
/**中間的get和set方法省略
*/
@Override
public String toString() {
return "SubscribeReq [subReqID=" + subReqID + ", userName=" + userName
+ ", productName=" + productName + ", phoneNumber="
+ phoneNumber + ", address=" + address + "]";
}
}
package netty.codec.pojo;
import java.io.Serializable;
public class SubscribeResp implements Serializable {
/**
* 默認序列ID
*/
private static final long serialVersionUID = 1L;
private int subReqID;
private int respCode;
private String desc;
public final int getSubReqID() {
return subReqID;
}
public final void setSubReqID(int subReqID) {
this.subReqID = subReqID;
}
/**
* 中間的get和set方法省略*/
@Override
public String toString() {
return "SubscribeResp [subReqID=" + subReqID + ", respCode=" + respCode
+ ", desc=" + desc + "]";
}
}
2.2. 服務端開發
package netty.codec.marshalling;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* created by LMR on 2020/5/20
*/
public class SubReqServer {
public void bind(int port) throws Exception {
// 配置服務端的NIO線程組
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
//添加Marshalling解碼器
ch.pipeline().addLast(
MarshallingCodeCFactory.buildMarshallingDecoder());
//添加Marshalling編碼器
ch.pipeline().addLast(
MarshallingCodeCFactory.buildMarshallingEncoder());
ch.pipeline().addLast(new SubReqServerHandler());
}
});
// 綁定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服務端監聽端口關閉
f.channel().closeFuture().sync();
} finally {
// 優雅退出,釋放線程池資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new SubReqServer().bind(port);
}
}
在initChannel方法中,在ChannelPipeline中添加Marshalling編解碼器,Marshalling編解碼器是通過自定義的MarshallingCodeCFactory工廠類來創建。下面來看看MarshallingCodeCFactory工具類是如何實現的。
package netty.codec.marshalling;
import io.netty.handler.codec.marshalling.DefaultMarshallerProvider;
import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallingDecoder;
import io.netty.handler.codec.marshalling.MarshallingEncoder;
import io.netty.handler.codec.marshalling.UnmarshallerProvider;
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;
/**
* created by LMR on 2020/5/20
*/
public final class MarshallingCodeCFactory {
//創建Jboss Marshalling解碼器MarshallingDecoder
public static MarshallingDecoder buildMarshallingDecoder() {
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024);
return decoder;
}
//創建Jboss Marshalling編碼器MarshallingEncoder
public static MarshallingEncoder buildMarshallingEncoder() {
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
MarshallingCodeCFactory 工程類中首先通過Marshalling工具類的getProvidedMarshallerFactory靜態方法獲取MarshallerFactory 實例,參數爲“serial”表示創建的是Java序列化工廠對象。然後創建MarshallingConfiguration 對象。然後針對解碼和編碼分別創建DefaultMarshallerProvider和DefaultUnmarshallerProvider對象,最後利用該對象分別創建MarshallingEncoder 和MarshallingDecoder 用於編碼和解碼。
服務端在接收到訂購消息之後會進行一系列的處理,下面我們來介紹服務端的處理類SubReqServerHandler。
package netty.codec.marshalling;
import io.netty.channel.ChannelHandlerContext;
import netty.codec.pojo.SubscribeReq;
import netty.codec.pojo.SubscribeResp;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* created by LMR on 2020/5/20
*/
public class SubReqServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
SubscribeReq req = (SubscribeReq) msg;
if ("LMRZero".equalsIgnoreCase(req.getUserName())) {
System.out.println("Service accept client subscrib req : [" + req.toString() + "]");
ctx.writeAndFlush(resp(req.getSubReqID()));
}
}
//根據訂單ID創建訂購成功的返回消息
private SubscribeResp resp(int subReqID) {
SubscribeResp resp = new SubscribeResp();
resp.setSubReqID(subReqID);
resp.setRespCode(0);
resp.setDesc("Book order succeed, 3 days later, sent to the designated address");
return resp;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();// 發生異常,關閉鏈路
}
}
SubReqServerHandler 的邏輯十分簡單,服務端在接收到訂購消息之後,會判斷用戶名是否是“LMRZero”,如果是,則首先輸出接收消息。然後構建訂購成功的消息返回給客戶端。
2.3. 客戶端開發
package netty.codec.marshalling;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* created by LMR on 2020/5/20
*/
public class SubReqClient {
public void connect(int port, String host) throws Exception {
// 配置客戶端NIO線程組
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
MarshallingCodeCFactory.buildMarshallingDecoder());
ch.pipeline().addLast(
MarshallingCodeCFactory.buildMarshallingEncoder());
ch.pipeline().addLast(new SubReqClientHandler());
}
});
// 發起異步連接操作
ChannelFuture f = b.connect(host, port).sync();
// 當代客戶端鏈路關閉
f.channel().closeFuture().sync();
} finally {
// 優雅退出,釋放NIO線程組
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new SubReqClient().connect(port, "127.0.0.1");
}
}
客戶端啓動類的代碼與服務端基本一致,在這裏就不進行介紹,下面看看客戶端處理類的實現代碼。
package netty.codec.marshalling;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import netty.codec.pojo.SubscribeReq;
/**
* created by LMR on 2020/5/20
*/
public class SubReqClientHandler extends ChannelInboundHandlerAdapter {
/**
* Creates a client-side handler.
*/
public SubReqClientHandler() {
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 10; i++) {
ctx.write(subReq(i));
}
ctx.flush();
}
private SubscribeReq subReq(int i) {
SubscribeReq req = new SubscribeReq();
req.setAddress("Beijing Jiaotong University, BeiJing ");
req.setPhoneNumber("010-5168****");
req.setProductName("Test For Marshalling");
req.setSubReqID(i);
req.setUserName("LMRZero");
return req;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("Receive server response : [" + msg + "]");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
在SubReqClientHandler 類中,爲了測試TCP粘包/拆包是否能夠被正確處理,本例中連續發送10條訂購消息策略。由於我們在ChannelPipeline中添加了編解碼器,在這裏我們不需要在進行任何設置,Netty會幫我們將訂購消息進行編碼,將返回的消息進行解碼。
2.4. 運行結果
服務端結果:
客戶端結果:
3. 基於Netty和Marshalling的自定義消息案例
3.1 消息定義
相較於第一個案例,本節中的案例更爲複雜。在本節中,我們將模擬私有協議傳遞的消息。
設計的Netty協議消息主要包含兩個部分:
- 消息頭;
- 消息體;
Netty消息的具體定義如下:
變量名稱 | 變量類型 | 描述 |
---|---|---|
header | Header | 消息頭定義(自定義) |
body | Object | 對於請求消息,它是方法的參數;對於響應消息,它是返回值 |
Netty消息頭Header定義如下:
變量名稱 | 變量類型 | 描述 |
---|---|---|
crcCode | 整型int | 校驗碼,本例中是固定值 |
length | 整型int | 整條消息的長度,包括消息頭和消息體 |
type | Byte | 0:業務請求消息;1:業務響應消息;2:業務one way消息(既是請求又是響應);3:握手請求消息;4:握手應答消息;5:心跳請求消息;6:心跳應答消息 |
attachment | Map<String, Object> | 可選字段,用於擴展 |
消息定義代碼如下所示:
package netty.protocol.struct;
/**
* created by LMR on 2020/5/20
*/
public final class NettyMessage {
//消息頭
private Header header;
//消息內容
private Object body;
//省略get和set方法
@Override
public String toString() {
return "NettyMessage [header=" + header + "]";
}
}
Header 定義代碼如下所示:
package netty.protocol.struct;
import java.util.HashMap;
import java.util.Map;
/**
* created by LMR on 2020/5/20
*/
public final class Header {
//校驗碼
private int crcCode = 0xabef0101;
// 消息長度
private int length;
// 消息類型
private byte type;
//附件
private Map<String, Object> attachment = new HashMap<String, Object>();
//省略get和set方法
@Override
public String toString() {
return "Header [crcCode=" + crcCode + ", length=" + length
+ ", type=" + type + ", attachment=" + attachment + "]";
}
}
3.2. 消息編解碼器
由於本例中的消息類型比較複雜,我們需要自定義消息的編解碼器。本節中將具體介紹編解碼器的實現代碼。
3.2.1. 編碼器
package netty.protocol.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import java.io.IOException;
import java.util.Map;
import netty.protocol.struct.NettyMessage;
/**
* created by LMR on 2020/5/23
*/
public final class NettyMessageEncoder extends
MessageToByteEncoder<NettyMessage> {
MarshallingEncoder marshallingEncoder;
public NettyMessageEncoder() throws IOException {
this.marshallingEncoder = new MarshallingEncoder();
}
@Override
protected void encode(ChannelHandlerContext ctx, NettyMessage msg,
ByteBuf sendBuf) throws Exception {
if (msg == null || msg.getHeader() == null)
throw new Exception("The encode message is null");
sendBuf.writeInt((msg.getHeader().getCrcCode()));
sendBuf.writeInt((msg.getHeader().getLength()));
sendBuf.writeByte((msg.getHeader().getType()));
sendBuf.writeInt((msg.getHeader().getAttachment().size()));
String key = null;
byte[] keyArray = null;
Object value = null;
for (Map.Entry<String, Object> param : msg.getHeader().getAttachment().entrySet()) {
//獲取key
key = param.getKey();
//將key轉化爲字節數組
keyArray = key.getBytes("UTF-8");
//標識key的字節數組大小,以便於後續取出key
sendBuf.writeInt(keyArray.length);
//寫入key
sendBuf.writeBytes(keyArray);
//獲取value
value = param.getValue();
//寫入value
marshallingEncoder.encode(value, sendBuf);
}
key = null;
keyArray = null;
value = null;
if (msg.getBody() != null) {
marshallingEncoder.encode(msg.getBody(), sendBuf);
} else
sendBuf.writeInt(0);
//最終更新整條消息的長度
sendBuf.setInt(4, sendBuf.readableBytes() - 8);
}
}
NettyMessageEncoder 類實現了對消息的編碼功能。該類繼承MessageToByteEncoder,主要實現了其encode方法,可以將一個消息類型的數據NettyMessage轉化爲字節類型數據ByteBuf 。在具體是實現時,按照頭數據(Header)和主體數據(body)的順序依次編碼。對於基本數據類型的數據直接調用ByteBuf的方法將數據轉化爲字節類型。而Attachment爲map類型數據,其中key爲String類型,而value爲Object類型,都無法直接調用ByteBuf的方法進行編碼。字符串數據可以設置標識並且寫入字節數組,而Object類型數據則調用MarshallingEncoder的encode方法進行編碼,下面來看看該類的具體實現。
package netty.protocol.codec;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
import org.jboss.marshalling.Marshaller;
/**
* created by LMR on 2020/5/23
*/
public class MarshallingEncoder {
private static final byte[] LENGTH_PLACEHOLDER = new byte[4];
Marshaller marshaller;
public MarshallingEncoder() throws IOException {
marshaller = MarshallingCodecFactory.buildMarshalling();
}
protected void encode(Object msg, ByteBuf out) throws Exception {
try {
//獲取當前緩衝區指針位置
int lengthPos = out.writerIndex();
//設置臨時的佔位符,表明當前存儲的Object的size
out.writeBytes(LENGTH_PLACEHOLDER);
//利用Marshaller來寫msg
ChannelBufferByteOutput output = new ChannelBufferByteOutput(out);
marshaller.start(output);
marshaller.writeObject(msg);
marshaller.finish();
//重新更新當前Object的size
out.setInt(lengthPos, out.writerIndex() - lengthPos - 4);
} finally {
marshaller.close();
}
}
}
MarshallingEncoder 類則是調用Marshaller的方法進行編碼,這與第一個例子相同,這裏就不再解析。
3.2.2. 解碼器
package netty.protocol.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import netty.protocol.struct.Header;
import netty.protocol.struct.NettyMessage;
/**
* created by LMR on 2020/5/23
*/
public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder {
MarshallingDecoder marshallingDecoder;
public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) throws IOException {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
marshallingDecoder = new MarshallingDecoder();
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
return null;
}
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setCrcCode(frame.readInt());
header.setLength(frame.readInt());
header.setType(frame.readByte());
//讀取附件的個數
int size = frame.readInt();
if (size > 0) {
Map<String, Object> attch = new HashMap<String, Object>(size);
int keySize = 0;
byte[] keyArray = null;
String key = null;
//依次解碼每個附件
for (int i = 0; i < size; i++) {
keySize = frame.readInt();
keyArray = new byte[keySize];
frame.readBytes(keyArray);
key = new String(keyArray, "UTF-8");
attch.put(key, marshallingDecoder.decode(frame));
}
keyArray = null;
key = null;
header.setAttachment(attch);
}
//如果有消息體,則解碼
if (frame.readableBytes() > 4) {
message.setBody(marshallingDecoder.decode(frame));
}
message.setHeader(header);
return message;
}
}
NettyMessageDecoder 繼承了LengthFieldBasedFrameDecoder ,該解碼器支持自動的TCP粘包和半包處理,只需要給出標識消息長度字段偏移量和消息長度本身的字節數,Netty就能夠實現對半包的處理。在具體業務上先調用LengthFieldBasedFrameDecoder 的解碼方法解決粘包或半包的問題,返回整包數據或者空數據,之後再進行具體的解碼方法,其中MarshallingDecoder 實現代碼如下:
package netty.protocol.codec;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
import java.io.StreamCorruptedException;
import org.jboss.marshalling.ByteInput;
import org.jboss.marshalling.Unmarshaller;
/**
* created by LMR on 2020/5/23
*/
public class MarshallingDecoder {
private final Unmarshaller unmarshaller;
public MarshallingDecoder() throws IOException {
unmarshaller = MarshallingCodecFactory.buildUnMarshalling();
}
protected Object decode(ByteBuf in) throws Exception {
//獲取消息的size
int objectSize = in.readInt();
//獲取當前消息緩衝區的子區域
ByteBuf buf = in.slice(in.readerIndex(), objectSize);
ByteInput input = new ChannelBufferByteInput(buf);
try {
//利用Unmarshaller解碼
unmarshaller.start(input);
Object obj = unmarshaller.readObject();
unmarshaller.finish();
in.readerIndex(in.readerIndex() + objectSize);
return obj;
} finally {
unmarshaller.close();
}
}
}
實現過程再代碼中有詳細註釋,就不再解釋。
3.3. 服務端代碼
package netty.protocol.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.io.IOException;
import netty.protocol.codec.NettyMessageDecoder;
import netty.protocol.codec.NettyMessageEncoder;
/**
* created by LMR on 2020/5/23
*/
public class NettyServer {
public static void main(String[] args) throws Exception {
new NettyServer().bind(8080);
}
public void bind(int port) throws Exception {
// 配置服務端的NIO線程組
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws IOException {
//自定義消息解碼器
ch.pipeline().addLast(new NettyMessageDecoder(1024 * 1024, 4, 4));
//自定義消息編碼器
ch.pipeline().addLast(new NettyMessageEncoder());
//服務端處理類
ch.pipeline().addLast("ServerHandler", new ServerHandler());
}
});
// 綁定端口,同步等待成功
b.bind(port).sync();
System.out.println("Netty server start ok : " + port);
}
}
在initChannel方法中,我們在ChannelPipeline中添加了自定義的解碼器和編碼器,其中解碼器設置的最大消息長度爲1024*1024,消息長度標識所在的位置4(字節數組下標爲4)和消息長度標識本身的字節長度4(int類型)。下面我們看看具體的服務端處理類ServerHandler的實現代碼:
package netty.protocol.server;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import netty.protocol.struct.Header;
import netty.protocol.struct.NettyMessage;
/**
* created by LMR on 2020/5/23
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
NettyMessage message = (NettyMessage) msg;
// 如果是握手請求消息,處理,(這裏僅僅考慮這種情況)
if (message.getHeader() != null && message.getHeader().getType() == (byte) 3) {
NettyMessage loginResp = buildResponse((byte) 0);
System.out.println("The service receive is : " + message + " body [" + message.getBody() + "]");
ctx.writeAndFlush(loginResp);
}
}
private NettyMessage buildResponse(byte result) {
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType((byte)4);
message.setHeader(header);
message.setBody(result);
return message;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();// 發生異常,關閉鏈路
}
}
3.4. 客戶端代碼
package netty.protocol.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.ReadTimeoutHandler;
import netty.protocol.NettyConstant;
import netty.protocol.codec.NettyMessageDecoder;
import netty.protocol.codec.NettyMessageEncoder;
/**
* created by LMR on 2020/5/23
*/
public class NettyClient {
EventLoopGroup group = new NioEventLoopGroup();
public void connect(int port, String host) throws Exception {
// 配置客戶端NIO線程組
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new NettyMessageDecoder(1024 * 1024, 4, 4));
ch.pipeline().addLast("MessageEncoder", new NettyMessageEncoder());
ch.pipeline().addLast("ClientHandler", new ClientHandler());
}
});
// 發起異步連接操作
ChannelFuture future = b.connect(
new InetSocketAddress(host, port),
new InetSocketAddress(NettyConstant.LOCALIP,
NettyConstant.LOCAL_PORT)).sync();
// 當對應的channel關閉的時候,就會返回對應的channel。
future.channel().closeFuture().sync();
} finally {
//釋放資源
}
}
public static void main(String[] args) throws Exception {
new NettyClient().connect(8080, "127.0.0.1");
}
}
客戶端的操作與服務端類似,都需要添加自定義編解碼器,然後加入客戶端操作實例,下面看看ClientHandler具體實現。
package netty.protocol.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import netty.protocol.MessageType;
import netty.protocol.struct.Header;
import netty.protocol.struct.NettyMessage;
/**
* created by LMR on 2020/5/23
*/
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 10; i++) {
ctx.write(buildMessage());
}
ctx.flush();
}
private NettyMessage buildMessage() {
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType(MessageType.LOGIN_REQ.value());
header.setLength(999);
header.setPriority((byte)8);
header.setSessionID(999l);
message.setHeader(header);
return message;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("Receive server response : [" + msg + "]");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
channelActive在初始化時執行,便向服務端發送消息,一共發送10條數據,以便於測試粘包,拆包現象是否能夠解決。
3.5. 運行結果
服務端:
客戶端:
參考博客及書籍:
https://www.jianshu.com/p/64dc7ee8c713
https://blog.csdn.net/qq_24871519/article/details/82668828
《Netty 權威指南》
如果喜歡的話希望點贊收藏,關注我,將不間斷更新博客。
希望熱愛技術的小夥伴私聊,一起學習進步
來自於熱愛編程的小白