java网络编程之Netty实战数据通信(七)

Netty最佳实战数据通信

1 分析

      我们需要了解下在真正项目应用中如何去考虚Netty的使用,大体上对于一参数设置都是根据服务器性能决定的。这个不是最主要的。

      我们要考虑的问题是两台机器(甚至多台)使用Netty的怎样进行通信,我把他分为三种:

      第一种,使用长连接通道不断开的形式进行通信,也就是服务器和客户端的通道一直处于开启状态,如果服务器性能足够好,并且我们的客户端数量也比较少的情况下,我还是推荐这种方式的。

      第二种,一次性批量提交数据,采用短连接方式。也就是我们会把数据保存在本地临时缓冲区或者临时表里,当达到临界值时进行一次性批量提交。又或者根据定时任务轮询提交,这种情况弊端是做不到实时性传输,在对实时性不高的应用程序中可以推荐使用。

      第三种,我们可以使用一种特殊的长连接,在指定某一时间之内,服务器与某台客户端没有任何通信,则断开连接。下次连接则是客户端向服务器发送请求的时候,再次立连接。但是这种模式我们要考虑2个因素:

1、如何在超时(即服务器和客户端没有任何通信)后关闭通道?关闭通道后我们又如何再次建立连接?

2、客户端宕机时,我们无需考虑,下次客户端重启之后我们就可以与服务器建立连接,但是服务器宕机时,我们的客户端如何与服务器进行连接呢?

      好到这这三种场景都描述了一个遍,再简单的来说一下。第一种,主要你的服务器性能够好,ok你可以采取第一种完全没问题;第二种,感觉也没什么毛病,但是做不到实时性;第三种,个人强烈建议使用第三种,我会用一个小小案例来说明,需要考虑的因素,等我把案例讲完了也许你就懂如何解决了,当然如果你会了第三个,第一个也就不算什么难事了,就相当于我们刚开始学的HelloWorld案例一样,好说了那么多废话,在这就不BB了,直接上案例,注意这里只拿第三种来说明:

2 案例

      在讲解案例之前先说一下,这个案例用到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

还是老规矩有实体类先上实体类

2.1 实体类

首先,需要两个实体类,实体类都需要实现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">

这里代码没什么好说的

2.2 Jboss Marshalling 编解码工具类

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对象序列化为二进制数组

个人感觉不难都是一些固定的写法,记住每一步要做什么就好。

2.3 服务端
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的第一篇文章

2.4 服务端业务逻辑
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返回给客户端

2.5 客户端

上面的那些代码之前案例我都写过,基本上没什么变化,只有客户端的代码变化最大,下面来一一说明。

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、重连,里面的代码很简单,我就不做说明了

2.6 客户端业务逻辑
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);
        }
    }
}

这里很简单,没什么好说的了。

好!到这目前所有代码都编写完了来测试一下效果。

服务端启动打印如图:

这里写图片描述

客户端启动打印如图:

java网络编程之Netty实战数据通信(七)_第1张图片

客户端启动后服务端的打印:

java网络编程之Netty实战数据通信(七)_第2张图片

      这里只看几张照片是看不出什么效果的,建议下载源码,自己测试,注意看打印结果。

      好代码说完了,运行结果也看了,刚刚留的两个问题现在可以说一下了,为了大家方便我重新说一下要解决的两个问题。

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);
        }
    }
}

这里很简单,没什么好说的了。

好!到这目前所有代码都编写完了来测试一下效果。

服务端启动打印如图:

这里写图片描述

客户端启动打印如图:

java网络编程之Netty实战数据通信(七)_第3张图片

客户端启动后服务端的打印:

java网络编程之Netty实战数据通信(七)_第4张图片

      这里只看几张照片是看不出什么效果的,建议下载源码,自己测试,注意看打印结果。

      好代码说完了,运行结果也看了,刚刚留的两个问题现在可以说一下了,为了大家方便我重新说一下要解决的两个问题。

1、如何在超时(即服务器和客户端没有任何通信)后关闭通道?关闭通道后我们又如何再次建立连接?

2、客户端宕机时,我们无需考虑,下次客户端重启之后我们就可以与服务器建立连接,但是服务器宕机时,我们的客户端如何与服务器进行连接呢?

      第一个问题,相信大家都知道如何解决的了,在客户端跟服务端都使用了Netty提供的超时方法ReadTimeoutHandler(5),参数是以秒来算的。关闭通道后我们重新启动一个线程调用getChannelFuture()方法。重新连接。

      第二个问题,首先我们肯定的是在liunx环境下做部署的,我们可以在liunx下编写启动服务端的脚本(.sh),要是服务端真出问题只需要启动脚本就可以启动服务器了。

好!到这这个案例就讲解完了,下一篇文章是Netty实战的第二个使用场景。

源代码:https://github.com/hfbin/Thread_Socket/tree/master/Socket/runtime

你可能感兴趣的:(java,Netty,【网络编程】Netty,入门到实战)