互联网技术22——netty编解码技术与数据通信

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

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值