Marshaling
首先我这里准备了一张图,通过Marshalling模拟客户端像服务端发送数据包的过程。(该图是随便找的,图片内容与本文探讨的内容无关)
1.添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.12.Final</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.1-jre</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.7.1</version>
</dependency>
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling</artifactId>
<version>1.3.0.CR9</version>
</dependency>
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling-serial</artifactId>
<version>1.3.0.CR9</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
</dependencies>
2.创建请求对象和响应对象
public class RequestData implements Serializable {
private String id;
private String name;
private String requestMessage;
private byte[] attachment;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRequestMessage() {
return requestMessage;
}
public void setRequestMessage(String requestMessage) {
this.requestMessage = requestMessage;
}
public byte[] getAttachment() {
return attachment;
}
public void setAttachment(byte[] attachment) {
this.attachment = attachment;
}
}
public class ResponseData implements Serializable {
private String id;
private String name;
private String responseMessage;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
}
3.创建Marshalling编解码器
public final class MarshallingCodeCFactory {
/**
* 创建Jboss Marshalling解码器
* @return MarshallingDecoder
*/
public static MarshallingDecoder buildMarshallingDecoder(){
//首先通过Marshalling工具类的方法获取Marshalling实例对象,参数serial标识创建的是java序列化工厂
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
//创建MarshallingConfiguration对象,配置版本号为5
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
//根据marshallerFactory和configuration创建provider
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
//构建Netty的MarshallingDecoder对象,俩参数分别为provider和单个消息序列化后的最大长度
return new MarshallingDecoder(provider, 1024 * 1024);
}
/**
* 创建Jboss Marshalling编码器MarshallingEncoder
* @return 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);
//构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组
return new MarshallingEncoder(provider);
}
}
4.编写Client与Server对应的Handler
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg){
try{
ResponseData rd = (ResponseData) msg;
System.out.println("输出服务器端响应内容:" + rd.getId());
}finally {
ReferenceCountUtil.release(msg);
}
}
}
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx,Object msg) throws IOException {
//接受request请求 并进行业务处理
RequestData rd = (RequestData) msg;
System.out.println("id:" + rd.getId()+
",name:"+ rd.getName() +
",msg"+rd.getRequestMessage());
//还原压缩后的字节数组
byte[] data = ZipUtil.unGZip(rd.getAttachment());
String path = System.getProperty("user.dir") +
File.separatorChar + "receive" +
File.separatorChar +
"prometheus.jpeg";
FileOutputStream fos = new FileOutputStream(path);
fos.write(data);
fos.close();
//回送响应数据 完成ack
ResponseData responseData = new ResponseData();
responseData.setId("response " + rd.getId());
responseData.setName("response " + rd.getName());
responseData.setResponseMessage("响应信息");
ctx.writeAndFlush(responseData);
}
}
5.客户端与服务端代码
public class Client {
public static void main(String[] args) throws InterruptedException, IOException {
NioEventLoopGroup wGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(wGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
sc.pipeline().addLast(new ClientHandler());
}
});
ChannelFuture cf = b.connect("127.0.0.1", 8765).sync();
Channel channel = cf.channel();
for (int i = 0; i < 100; i++) {
RequestData rd = new RequestData();
rd.setId(""+i);
rd.setName("我是消息"+i);
rd.setRequestMessage("内容"+i);
String path = System.getProperty("user.dir") +
File.separatorChar + "source" +
File.separatorChar +
"prometheus.jpeg";
File file = new File(path);
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[fis.available()];
fis.read(data);
fis.close();
rd.setAttachment(ZipUtil.gZip(data));
channel.writeAndFlush(rd);
}
cf.channel().closeFuture().sync();
wGroup.shutdownGracefully();
}
}
public class Server {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bGroup = new NioEventLoopGroup(1);
EventLoopGroup wGroup = new NioEventLoopGroup();//默认CPU核心数*2 感兴趣伙伴可以看看源码
ServerBootstrap sb = new ServerBootstrap();
sb.group(bGroup,wGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
sc.pipeline().addLast(new ServerHandler());
}
});
ChannelFuture cf = sb.bind(8765).sync();
cf.channel().closeFuture().sync();
bGroup.shutdownGracefully();
wGroup.shutdownGracefully();
}
}
6.涉及的工具类
public class ZipUtil {
/**
* 压缩GZip
*
* @param data
* @author wcl
* @return
*/
public static byte[] gZip(byte[] data) {
byte[] b = null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(bos);
gzip.write(data);
gzip.finish();
gzip.close();
b = bos.toByteArray();
bos.close();
} catch (Exception ex) {
ex.printStackTrace();
}
return b;
}
/**
* 解压GZip
*
* @param data
* @author taoyi
* @return
*/
public static byte[] unGZip(byte[] data) {
byte[] b = null;
try {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
GZIPInputStream gzip = new GZIPInputStream(bis);
byte[] buf = new byte[1024];
int num = -1;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((num = gzip.read(buf, 0, buf.length)) != -1) {
baos.write(buf, 0, num);
}
b = baos.toByteArray();
baos.flush();
baos.close();
gzip.close();
bis.close();
} catch (Exception ex) {
ex.printStackTrace();
}
return b;
}
}
7.查看结果
经过上述实验,我们不难发现,Client通过Marshalling顺利的将我们的数据包发送给了Server,并且Server也收到消息并完成了ACK响应。
并且图片也已经输出了。
总结
实现消息通信往往都是要经过两次处理,大致流程是这样的:
它的核心思路就是把Java对象转换成二进制数据以便网络传输,最后编解码器进行编解码的操作。