我们需要了解下在真正项目应用中如何去考虚Netty的使用,大体上对于一参数设置都是根据服务器性能决定的。这个不是最主要的。
我们要考虑的问题是两台机器(甚至多台)使用Netty的怎样进行通信,我把他分为三种:
第一种,使用长连接通道不断开的形式进行通信,也就是服务器和客户端的通道一直处于开启状态,如果服务器性能足够好,并且我们的客户端数量也比较少的情况下,我还是推荐这种方式的。
第二种,一次性批量提交数据,采用短连接方式。也就是我们会把数据保存在本地临时缓冲区或者临时表里,当达到临界值时进行一次性批量提交。又或者根据定时任务轮询提交,这种情况弊端是做不到实时性传输,在对实时性不高的应用程序中可以推荐使用。
第三种,我们可以使用一种特殊的长连接,在指定某一时间之内,服务器与某台客户端没有任何通信,则断开连接。下次连接则是客户端向服务器发送请求的时候,再次立连接。但是这种模式我们要考虑2个因素:
1、如何在超时(即服务器和客户端没有任何通信)后关闭通道?关闭通道后我们又如何再次建立连接?
2、客户端宕机时,我们无需考虑,下次客户端重启之后我们就可以与服务器建立连接,但是服务器宕机时,我们的客户端如何与服务器进行连接呢?
好到这这三种场景都描述了一个遍,再简单的来说一下。第一种,主要你的服务器性能够好,ok你可以采取第一种完全没问题;第二种,感觉也没什么毛病,但是做不到实时性;第三种,个人强烈建议使用第三种,我会用一个小小案例来说明,需要考虑的因素,等我把案例讲完了也许你就懂如何解决了,当然如果你会了第三个,第一个也就不算什么难事了,就相当于我们刚开始学的HelloWorld案例一样,好说了那么多废话,在这就不BB了,直接上案例,注意这里只拿第三种来说明:
在讲解案例之前先说一下,这个案例用到JBoss Marshalling编解码框架,不了解编解码技术的可以看我上一篇文章。说实话,如果你看过上一篇文章,这个案例就相当于上一篇文章的更改版,只是在客户端这里做了一些修改。
需要的jar包有 1、netty 2、log4j 3、jboss-marshalling jboss-marshalling-serial 需要jar包的可以去我的github下载或者自己去官网下载。https://github.com/hfbin/Thread_Socket/tree/master/Socket/runtime/jar%E5%8C%85
还是老规矩有实体类先上实体类
首先,需要两个实体类,实体类都需要实现Serializable接口,Request类是用来做客户端的请求的, Response类是用来做服务端返回给客户端的响应。
Request
“`java
public class Request implements Serializable{
private static final long SerialVersionUID = 1L;
private String id ;
private String name ;
private String requestMessage ;
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;
}
}
Response
```java
public class Response implements Serializable{
private static final long serialVersionUID = 1L;
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;
}
}
"se-preview-section-delimiter">
这里代码没什么好说的
public final class MarshallingCodeCFactory {
/**
* 创建Jboss Marshalling解码器MarshallingDecoder
* @return MarshallingDecoder
*/
public static MarshallingDecoder buildMarshallingDecoder() {
//1
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
//2
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
//3
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
//4
MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024 * 1024 * 1);
return decoder;
}
/**
* 创建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);
//5
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
"se-preview-section-delimiter">
1、首先通过Marshalling工具类的精通方法获取Marshalling实例对象 参数serial标识创建的是java序列化工厂对象。
2、创建了MarshallingConfiguration对象,配置了版本号为5
3、根据marshallerFactory和configuration创建provider
4、构建Netty的MarshallingDecoder对象,俩个参数分别为provider和单个消息序列化后的最大长度
5、构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组
个人感觉不难都是一些固定的写法,记住每一步要做什么就好。
public class Server {
public static void main(String[] args) throws Exception{
EventLoopGroup pGroup = new NioEventLoopGroup();
EventLoopGroup cGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
//1
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer() {
protected void initChannel(SocketChannel sc) throws Exception {
//2
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
//3
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
//4
sc.pipeline().addLast(new ReadTimeoutHandler(5));
sc.pipeline().addLast(new ServerHandler());
}
});
ChannelFuture cf = b.bind(8765).sync();
cf.channel().closeFuture().sync();
pGroup.shutdownGracefully();
cGroup.shutdownGracefully();
}
}
"se-preview-section-delimiter">
1、设置日志(这里需要自己导入log4j.jar) 当然你也可以不设置打印日记
2、这是我们写的Jboss Marshalling工具类的解码器MarshallingDecoder
3、这是我们写的Jboss Marshalling工具类的编码器MarshallingEncoder
4、超时handler(当服务器端与客户端在指定时间以上没有任何进行通信,则会关闭响应的通道,主要为减小服务端资源占用)单位是秒
其他不懂的去看我Netty的第一篇文章
public class ServerHandler extends ChannelHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Request request = (Request)msg;
System.out.println("Server : " + request.getId() + ", " + request.getName() + ", " + request.getRequestMessage());
Response response = new Response();
response.setId(request.getId());
response.setName("response" + request.getId());
response.setResponseMessage("响应内容" + request.getId());
ctx.writeAndFlush(response);//.addListener(ChannelFutureListener.CLOSE);
}
}
"se-preview-section-delimiter">
这里业务逻辑主要是将客户端传过来的信息进行打印,然后将数据传入返回的实体类Response,将实体类Response返回给客户端
上面的那些代码之前案例我都写过,基本上没什么变化,只有客户端的代码变化最大,下面来一一说明。
public class Client {
// 1
private static class SingletonHolder {
static final Client instance = new Client();
}
public static Client getInstance(){
return SingletonHolder.instance;
}
private EventLoopGroup group;
private Bootstrap b;
private ChannelFuture cf ;
private Client(){
group = new NioEventLoopGroup();
b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
//2
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
//3
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
//4
sc.pipeline().addLast(new ReadTimeoutHandler(5));
sc.pipeline().addLast(new ClientHandler());
}
});
}
//5
public void connect(){
try {
this.cf = b.connect("127.0.0.1", 8765).sync();
System.out.println("远程服务器已经连接, 可以进行数据交换..");
} catch (Exception e) {
e.printStackTrace();
}
}
//6
public ChannelFuture getChannelFuture(){
//如果没有连接先链接
if(this.cf == null){
this.connect();
}
//this.cf.channel().isActive() 这里得到的是链接状态 在重新连接时候使用的 返回true/false
if(!this.cf.channel().isActive()){
this.connect();
}
return this.cf;
}
public static void main(String[] args) throws Exception{
final Client c = Client.getInstance();
//c.connect();
ChannelFuture cf = c.getChannelFuture();
//7
for(int i = 1; i <= 3; i++ ){
Request request = new Request();
request.setId("" + i);
request.setName("pro" + i);
request.setRequestMessage("数据信息" + i);
cf.channel().writeAndFlush(request);
TimeUnit.SECONDS.sleep(4);
}
//8
cf.channel().closeFuture().sync();
//9 重连
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());
//再次发送数据
Request request = new Request();
request.setId("" + 4);
request.setName("pro" + 4);
request.setRequestMessage("数据信息" + 4);
cf.channel().writeAndFlush(request);
cf.channel().closeFuture().sync();
System.out.println("子线程结束.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
System.out.println("断开连接,主线程结束..");
}
}
"se-preview-section-delimiter">
还是老规矩,在这我只对一些重要的做说明:
1、为了保证线安全我采用了单例模式,构造静态内部类实现单例模式,在这不懂的可以看我的这篇文章
2、这是我们写的Jboss Marshalling工具类的解码器MarshallingDecoder
3、这是我们写的Jboss Marshalling工具类的编码器MarshallingEncoder
4、超时handler(当服务器端与客户端在指定时间以上没有任何进行通信,则会关闭响应的通道,主要为减小服务端资源占用)单位是秒
5、connect()这里我单独抽出一个方法来做服务器连接的
6、getChannelFuture()这个方法主要是为了超时时候做的,也就简单的调用了连接的方法
7、每隔四秒发送一条数据给服务端,注意这里插完三条数据后超过五秒(看4)后没有给服务端发送数据就会进入重连状态
8、等待关闭
9、重连,里面的代码很简单,我就不做说明了
public class ClientHandler extends ChannelHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
Response resp = (Response)msg;
System.out.println("Client : " + resp.getId() + ", " + resp.getName() + ", " + resp.getResponseMessage());
} finally {
ReferenceCountUtil.release(msg);
}
}
}
这里很简单,没什么好说的了。
好!到这目前所有代码都编写完了来测试一下效果。
服务端启动打印如图:
客户端启动打印如图:
客户端启动后服务端的打印:
这里只看几张照片是看不出什么效果的,建议下载源码,自己测试,注意看打印结果。
好代码说完了,运行结果也看了,刚刚留的两个问题现在可以说一下了,为了大家方便我重新说一下要解决的两个问题。
1、如何在超时(即服务器和客户端没有任何通信)后关闭通道?关闭通道后我们又如何再次建立连接?
2、客户端宕机时,我们无需考虑,下次客户端重启之后我们就可以与服务器建立连接,但是服务器宕机时,我们的客户端如何与服务器进行连接呢?
第一个问题,相信大家都知道如何解决的了,在客户端跟服务端都使用了Netty提供的超时方法ReadTimeoutHandler(5),参数是以秒来算的。关闭通道后我们重新启动一个线程调用getChannelFuture()方法。重新连接。
第二个问题,首先我们肯定的是在liunx环境下做部署的,我们可以在liunx下编写启动服务端的脚本(.sh),要是服务端真出问题只需要启动脚本就可以启动服务器了。
好!到这这个案例就讲解完了,下一篇文章是Netty实战的第二个使用场景。
源代码:
还是老规矩,在这我只对一些重要的做说明:
1、为了保证线安全我采用了单例模式,构造静态内部类实现单例模式,在这不懂的可以看我的这篇[文章](http://blog.csdn.net/qq_33524158/article/details/78566403)
2、这是我们写的Jboss Marshalling工具类的解码器MarshallingDecoder
3、这是我们写的Jboss Marshalling工具类的编码器MarshallingEncoder
4、超时handler(当服务器端与客户端在指定时间以上没有任何进行通信,则会关闭响应的通道,主要为减小服务端资源占用)单位是秒
5、connect()这里我单独抽出一个方法来做服务器连接的
6、getChannelFuture()这个方法主要是为了超时时候做的,也就简单的调用了连接的方法
7、每隔四秒发送一条数据给服务端,注意这里插完三条数据后超过五秒(看4)后没有给服务端发送数据就会进入重连状态
8、等待关闭
9、重连,里面的代码很简单,我就不做说明了
##### **2.6 客户端业务逻辑**
```java
public class ClientHandler extends ChannelHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
Response resp = (Response)msg;
System.out.println("Client : " + resp.getId() + ", " + resp.getName() + ", " + resp.getResponseMessage());
} finally {
ReferenceCountUtil.release(msg);
}
}
}
这里很简单,没什么好说的了。
好!到这目前所有代码都编写完了来测试一下效果。
服务端启动打印如图:
客户端启动打印如图:
客户端启动后服务端的打印:
这里只看几张照片是看不出什么效果的,建议下载源码,自己测试,注意看打印结果。
好代码说完了,运行结果也看了,刚刚留的两个问题现在可以说一下了,为了大家方便我重新说一下要解决的两个问题。
1、如何在超时(即服务器和客户端没有任何通信)后关闭通道?关闭通道后我们又如何再次建立连接?
2、客户端宕机时,我们无需考虑,下次客户端重启之后我们就可以与服务器建立连接,但是服务器宕机时,我们的客户端如何与服务器进行连接呢?
第一个问题,相信大家都知道如何解决的了,在客户端跟服务端都使用了Netty提供的超时方法ReadTimeoutHandler(5),参数是以秒来算的。关闭通道后我们重新启动一个线程调用getChannelFuture()方法。重新连接。
第二个问题,首先我们肯定的是在liunx环境下做部署的,我们可以在liunx下编写启动服务端的脚本(.sh),要是服务端真出问题只需要启动脚本就可以启动服务器了。
好!到这这个案例就讲解完了,下一篇文章是Netty实战的第二个使用场景。
源代码:https://github.com/hfbin/Thread_Socket/tree/master/Socket/runtime