Netty服务部署
常用的部署方式有2中,一种是耦合在Web应用中(以Tomcat为例),使其伴随Tomcat的启动而启动,伴随Tomcat的关闭而关闭。另外一种则是将Netty独立打包部署,然后由单独的进程启动运行(可以使用shell或其他脚本进行启动),然后以数据库或者其他缓存为承接点,实现数据交互。Netty与其他程序进行交互,然后将获取到的数据进行处理插入数据库或者缓存,然后其他服务从中获取。获取在Netty中调用web应用的一些对外接口。
Netty编解码技术
编解码技术,说白了就是java序列化技术,序列化目的就两个,第一个进行网络传输,第二对象持久化。虽然我们可以使用java进行对象序列化,netty去传输,但是java序列化的硬伤太多,比如:java序列化没法跨语言、序列化后码流太大、序列化性能太低等等。。
主流的编解码框架:
- JBoss的Marshalling包
- google的Protobuf
- 基于Protobuf的Kyro (性能高于Protobuf,可以与Marshalling媲美)
- MessagePack框架
Netty结合JBoss Marshalling
JBoss Marshalling是一个java对象序列化包,对JDK默认的序列化框架进行了优化,但又保持跟java.io.Seriallzable接口兼容,同时增加了一些可调的参数和附加特性。
类库:jboss-marshalling1.3.0、jboss-marshalling-serial-1.3.0
这两个包一定要都导入,尤其是jboss-marshalling-serial-1.3.0,缺此包可能不会报错,但是server不能解析传输的数据对象,跳过channelRead()而直接执行channelComple()
下载地址:https://www.jboss.org/jbossmarshalling/downloads/
maven路径:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling</artifactId>
<version>1.3.0.GA</version>
</dependency>
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling-serial</artifactId>
<version>1.3.0.GA</version>
</dependency>
下面的代码我直接结合着长连接超时重连一起演示,即数据通信
数据通信
一般在项目中我们更改如何使用netty呢?大体上对于一些茶树配置都是根据服务器性能决定的,这个不是最主要的,我们需要考虑的问题是两天机器(甚至是多台)使用netty怎样进行通信,一般分为三种:
1. 第一种,使用长连接通道不断开的形式进行通信,也就是服务器和客户端一直处于开启状态,如果服务器性能足够好,并且我们客户端的数量也比较少的情况下,还是可以采用这种方式的。
2.第二种,一次性批量提交数据,采用短连接方式。也就是我们会把数据保存咋及本地临时缓冲区或者临时表里,当达到临界值时进行一次性批量提交,又或者根据定时任务轮询提交,这种情况弊端是做不到实时性传输,在对实施性不高的应用程序汇总可以推荐使用。
3.第三种,使用一种特殊的长连接,在指定某一时间内,服务器与某客户端没有任何通信则断开连接,下次连接则是客户端发起请求的时候再次建立连接,但是这种模式我们需要考虑两个因素:
(1)如何在超时(即服务器和客户端没有任何通信)后关闭通道,关闭通道后我们又如何再次建立连接?答:可以使用连接的时候添加判断,如果连接状态可用直接使用,如果断开则重新建立连接
(2)客户端宕机时,我们无需考虑,下次客户端重启之后,我们就可以与服务器建立连接,但是服务器宕机时,我们的客户端如何与服务器进行连接呢?答:可以在客户端使用定时任务去轮询连接,知道连接成功建立为止。
结合上述编解码技术和数据通信的第三种方式,做一个统一的演示
netty已经为我们提供了超时机制,只需要在bootstrap中初始化ChannelInitializer时增加超时处理器ReadTimeOutHandler就可以了
ch.pipeline().addLast(new ReadTimeoutHandler(5));单位默认我秒,理论上在一段增加就可以了,为了双重保险,建议客户端和服务端同时配置。另外注意在数据发送完毕后不能添加监听去关闭(writeFlush.addListenser(ChannelFutureListener.CLOSE),因为该监听会立即断开连接。
序列化工具Marshalling部分的封装:MarshallingCodeCFactory.java
package com.nettyCopyOk;
import io.netty.handler.codec.marshalling.*;
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;
public final class MarshallingCodeCFactory {
/**
* 创建Jboss Marshalling解码器MarshallingDecoder
*/
public static MarshallingDecoder buildMarshallingDecoder() {
//首先通过Marshalling工具类的getProvidedMarshallerFactory静态方法获取MarshallerFactory实例
//参数“serial”表示创建的是Java序列化工厂对象,它由jboss-marshalling-serial-1.3.0.CR9.jar提供。
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
//创建了MarshallingConfiguration对象
final MarshallingConfiguration configuration = new MarshallingConfiguration();
//将它的版本号设置为5
configuration.setVersion(5);
//然后根据MarshallerFactory和MarshallingConfiguration创建UnmarshallerProvider实例
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
//最后通过构造函数创建Netty的MarshallingDecoder对象
//它有两个参数,分别是UnmarshallerProvider和单个消息序列化后的最大长度。
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对象,它用于创建Netty提供的MarshallingEncoder实例
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
//MarshallingEncoder用于将实现序列化接口的POJO对象序列化为二进制数组。
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
请求数据载体类:SubscribeReq.java
package com.nettyCopyOk;
import java.io.Serializable;
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 static long getSerialVersionUID() {
return serialVersionUID;
}
public int getSubReqID() {
return subReqID;
}
public void setSubReqID(int subReqID) {
this.subReqID = subReqID;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "SubscribeReq [subReqID=" + subReqID + ", userName=" + userName
+ ", productName=" + productName + ", phoneNumber="
+ phoneNumber + ", address=" + address + "]";
}
}
响应数据载体类:SubscribeResp.java
package com.nettyCopyOk;
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 static long getSerialVersionUID() {
return serialVersionUID;
}
public int getSubReqID() {
return subReqID;
}
public void setSubReqID(int subReqID) {
this.subReqID = subReqID;
}
public int getRespCode() {
return respCode;
}
public void setRespCode(int respCode) {
this.respCode = respCode;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "SubscribeResp [subReqID=" + subReqID + ", respCode=" + respCode
+ ", desc=" + desc + "]";
}
}
服务端实际处理逻辑:SubReqServerHandler.java
package com.nettyCopyOk;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
@ChannelHandler.Sharable
public class SubReqServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
//经过解码器handler ObjectDecoder的解码,
//SubReqServerHandler接收到的请求消息已经被自动解码为SubscribeReq对象,可以直接使用。
SubscribeReq req = (SubscribeReq) msg;
if ("Lilinfeng".equalsIgnoreCase(req.getUserName())) {
System.out.println("Service accept client subscribe req : ["
+ req.toString() + "]");
//对订购者的用户名进行合法性校验,校验通过后打印订购请求消息,构造订购成功应答消息立即发送给客户端。
ctx.writeAndFlush(resp(req.getSubReqID()));
}
}
private SubscribeResp resp(int subReqID) {
SubscribeResp resp = new SubscribeResp();
resp.setSubReqID(subReqID);
resp.setRespCode(0);
resp.setDesc("Netty book order succeed, 3 days later, sent to the designated address");
return resp;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
System.out.println("发生异常");
ctx.close();// 发生异常,关闭链路
}
}
客户端实际处理逻辑:SubReqClientHandler.java
package com.nettyCopyOk;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
public class SubReqClientHandler extends ChannelHandlerAdapter {
public SubReqClientHandler() {
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
//由于对象解码器已经对订购请求应答消息进行了自动解码,
//因此,SubReqClientHandler接收到的消息已经是解码成功后的订购应答消息。
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();
}
}
服务端代码:server.java
package com.nettyCopyOk;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
/**
* Created by BaiTianShi on 2018/9/12.
*/
public class Server {
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)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer() {
@Override
public void initChannel(Channel ch) {
//通过工厂类创建MarshallingEncoder解码器,并添加到ChannelPipeline.
ch.pipeline().addLast(com.testMarshaLing.MarshallingCodeCFactory.buildMarshallingDecoder());
//通过工厂类创建MarshallingEncoder编码器,并添加到ChannelPipeline中。
ch.pipeline().addLast(com.testMarshaLing.MarshallingCodeCFactory.buildMarshallingEncoder());
ch.pipeline().addLast(new ReadTimeoutHandler(3));
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;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new Server().bind(port);
}
}
客户端代码SubReqClient.java
package com.nettyCopyOk;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.util.concurrent.TimeUnit;
public class SubReqClient {
private static class SingletonHolder {
static final SubReqClient instance = new SubReqClient();
}
public static SubReqClient getInstance(){
return SingletonHolder.instance;
}
private EventLoopGroup group;
private Bootstrap b;
private ChannelFuture cf ;
private SubReqClient(){
group = new NioEventLoopGroup();
b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
public void initChannel(Channel ch)
throws Exception {
ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
ch.pipeline().addLast(new ReadTimeoutHandler(5));
ch.pipeline().addLast(new SubReqClientHandler());
}
});
}
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() {
@Override
public void initChannel(Channel ch)
throws Exception {
ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
ch.pipeline().addLast(new ReadTimeoutHandler(3));
ch.pipeline().addLast(new SubReqClientHandler());
}
});
// 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync();
System.out.println("向服务端发送请求数据");
// 等待客户端链路关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}
public void connect(){
try {
this.cf = b.connect( "127.0.0.1", 8080).sync();
System.out.println("远程服务器已经连接, 可以进行数据交换..");
} catch (Exception e) {
e.printStackTrace();
}
}
public ChannelFuture getChannelFuture(){
if(this.cf == null){
this.connect();
}
if(!this.cf.channel().isActive()){
this.connect();
}
return this.cf;
}
public static void main(String[] args) throws Exception {
final SubReqClient c = SubReqClient.getInstance();
ChannelFuture cf = c.getChannelFuture();
for (int i = 0; i < 3; i++) {
SubscribeReq req = new SubscribeReq();
req.setAddress("南京市江宁区方山国家地质公园");
req.setPhoneNumber("138xxxxxxxxx");
req.setProductName("Netty For Marshalling");
req.setSubReqID(i);
req.setUserName("Lilinfeng");
cf.channel().writeAndFlush(req);
// TimeUnit.SECONDS.sleep(3);
}
// cf.channel().flush();
cf.channel().closeFuture().sync();
TimeUnit.SECONDS.sleep(4);
System.out.println(cf.channel().isActive());
System.out.println(cf.channel().isOpen());
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("进入子线程...");
ChannelFuture cf = c.getChannelFuture();
System.out.println(cf.channel().isActive());
System.out.println(cf.channel().isOpen());
//再次发送数据
SubscribeReq req = new SubscribeReq();
req.setAddress("南京市江宁区方山国家地质公园");
req.setPhoneNumber("138xxxxxxxxx");
req.setProductName("Netty For Marshalling");
req.setSubReqID(10);
req.setUserName("Lilinfeng");
cf.channel().writeAndFlush(req);
cf.channel().closeFuture().sync();
System.out.println("子线程结束.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
System.out.println("断开连接,主线程结束..");
}
}
udp通信和压缩传输和解压等可看下面的例子
udp通信:http://blog.youkuaiyun.com/mffandxx/article/details/53264172
Netty实现Websocket开发:http://www.cnblogs.com/wunaozai/p/5240006.html
netty实现压缩和解压传输文件:https://blog.youkuaiyun.com/zbw18297786698/article/details/53678133