netty粘包(一)消息定长 实践

 

服务端:

package com.jds.test.pack1;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * Created by sunyuming on 18/8/1.
 */
public class Server4 {
    public static void main(String[] args)  {

        //boss线程监听端口,worker线程负责数据读写
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();

        try{
            //辅助启动类
            ServerBootstrap bootstrap = new ServerBootstrap();
            //设置线程池
            bootstrap.group(boss,worker);

            //设置socket工厂
            bootstrap.channel(NioServerSocketChannel.class);

            //设置管道工厂
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    //获取管道
                    ChannelPipeline pipeline = socketChannel.pipeline();
                    //定长解码类
                    pipeline.addLast(new FixedLengthFrameDecoder(14));
                    //字符串解码类
                    pipeline.addLast(new StringDecoder());
                    pipeline.addLast(new StringEncoder());
                    //处理类
                    pipeline.addLast(new ServerHandler4());
                }
            });

            //绑定端口
            ChannelFuture future = bootstrap.bind(8866).sync();
            System.out.println("server start ...... ");

            //等待服务端监听端口关闭
            future.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //优雅退出,释放线程池资源
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

}

  

package com.jds.test.pack1;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.net.InetAddress;

/**
 * Created by sunyuming on 18/8/1.
 */
class ServerHandler4 extends SimpleChannelInboundHandler<String> {

    //用于记录次数
    private int count = 0;
    //读取客户端发送的数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("RESPONSE--------"+msg+";"+"   @ "+ ++count);
        ctx.writeAndFlush(msg);
    }

    //新客户端接入
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelActive");
    }

    //客户端断开
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelInactive");
    }

    //异常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //关闭通道
        ctx.channel().close();
        //打印异常
        cause.printStackTrace();
    }
}

  

客户端:

package com.jds.test.pack1;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.net.InetSocketAddress;

/**
 * Created by sunyuming on 18/8/1.
 */
public class Client4 {

    public static void main(String[] args) {

        //worker负责读写数据
        EventLoopGroup worker = new NioEventLoopGroup();

        try {
            //辅助启动类
            Bootstrap bootstrap = new Bootstrap();

            //设置线程池
            bootstrap.group(worker);

            //设置socket工厂
            bootstrap.channel(NioSocketChannel.class);

            //设置管道
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    //获取管道
                    ChannelPipeline pipeline = socketChannel.pipeline();
                    //定长解码类
                    pipeline.addLast(new FixedLengthFrameDecoder(14));
                    //字符串编码类
                    pipeline.addLast(new StringEncoder());
                    pipeline.addLast(new StringDecoder());
                    //处理类
                    pipeline.addLast(new ClientHandler4());
                }
            });

            //发起异步连接操作
            ChannelFuture futrue = bootstrap.connect(new InetSocketAddress("127.0.0.1",8866)).sync();

            //等待客户端链路关闭
            futrue.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //优雅的退出,释放NIO线程组
            worker.shutdownGracefully();
        }
    }

}

  

package com.jds.test.pack1;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * Created by sunyuming on 18/8/1.
 */
class ClientHandler4 extends SimpleChannelInboundHandler<String> {

    //接受服务端发来的消息
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("server response : "+msg);
    }

    //与服务器建立连接
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //给服务器发消息
        String s = "--";
        //发送50次消息
        for (int i = 0; i < 5; i++) {
            ctx.channel().writeAndFlush("I am client,"+s);
        }
    }

    //与服务器断开连接
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelInactive");
    }

    //异常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //关闭管道
        ctx.channel().close();
        //打印异常信息
        cause.printStackTrace();
    }

}

  

(一) 都14

服务端输出:

server start ......
channelActive
RESPONSE--------I am client,--; @ 1
RESPONSE--------I am client,--; @ 2
RESPONSE--------I am client,--; @ 3
RESPONSE--------I am client,--; @ 4
RESPONSE--------I am client,--; @ 5

客户端输出:

server response : I am client,--
server response : I am client,--
server response : I am client,--
server response : I am client,--
server response : I am client,--

 

(二)服务端13,客户端14

服务端:

server start ......
channelActive
RESPONSE--------I am client,-; @ 1
RESPONSE---------I am client,; @ 2
RESPONSE----------I am client; @ 3
RESPONSE--------,--I am clien; @ 4
RESPONSE--------t,--I am clie; @ 5

总向客户端发行字节13*5

客户端:

server response : I am client,--
server response : I am client,--
server response : I am client,--
server response : I am client,--

客户端收到13*5=65,65/14=4.6

 

(三)都13

服务端:

channelActive
RESPONSE--------I am client,-; @ 1
RESPONSE---------I am client,; @ 2
RESPONSE----------I am client; @ 3
RESPONSE--------,--I am clien; @ 4
RESPONSE--------t,--I am clie; @ 5

客户端:

server response : I am client,-
server response : -I am client,
server response : --I am client
server response : ,--I am clien
server response : t,--I am clie

 

(四)服务端去除,客户端13

服务端:

server start ......
channelActive
RESPONSE--------I am client,--I am client,--I am client,--I am client,--I am client,--; @ 1

总向客户端发行字节14*5=70

客户端:

server response : I am client,-
server response : -I am client,
server response : --I am client
server response : ,--I am clien
server response : t,--I am clie

客户端收到14*5=70,70/13=5.4

 

(五)都去除

服务端:

server start ...... 
channelActive
RESPONSE--------I am client,--I am client,--I am client,--I am client,--I am client,--; @ 1

 

客户端:

server response : I am client,--I am client,--I am client,--I am client,--I am client,--

 

参考:

https://blog.csdn.net/baiye_xing/article/details/73189191  

【Netty入门】解决TCP粘包/分包的实例

 ================================================================================

补充:

 

源码中为单向发送,服务端只有StringDecoder,客户端只有StringEncoder,本例测试双向发包拆包,故两端都要加上编码器和解码器

8.22之所以未在exceptionCaught中报错,是因为write和writeAndFlush为出站操作,且为异步,不会立即抛出异常,捕获异常方式有二:

1)

        ChannelFuture channelFuture = ctx.writeAndFlush(newMsg);
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if(!future.isSuccess()) {
                    future.cause().printStackTrace();
                    future.channel().close();
                }
            }
        });

  

可见抛出异常:

java.lang.UnsupportedOperationException: unsupported message type: String

 

2)

 

 

 

8.20,再补充:

serverhandler的channelRead0函数中直接将msg

ctx.writeAndFlush(msg);

write是个异步操作,而SimpleChannelInboundHandler的channelRead在其channelRead0后会释放msg:

ReferenceCountUtil.release(msg);

故该句应改为:

StringBuilder stringBuilder = new StringBuilder().append(msg);
String newMsg = stringBuilder.toString();
ctx.writeAndFlush(newMsg);

小结:

如果使用SimpleChannelInboundHandler,注意msg的复制;

如果使用ChannelInboundHandlerAdapter,注意msg的释放和转发;

 

2019.12.5 注意,此时的msg已经是java对象,所以如果要往下传递ServerHandler5,不需要复制

posted on 2018-08-01 14:14  silyvin  阅读(703)  评论(0编辑  收藏  举报