Netty开发入门示例

服务端代码

服务端启动代码

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class DiscardServer {

    private int port ;
    public DiscardServer(int port) {
        this.port = port;
    }

    public void run() throws InterruptedException {
        //事件循环处理io操作,这个用于处理连接请求
        NioEventLoopGroup bossGroup  = new NioEventLoopGroup();
        //事件循环处理:用于处理已经连接的请求,由bossGroup组处理后分发过来
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            //创建服务启动类
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    //指定使用实例化NioServerSocketChannel来处理进来的连接请求
                    .channel(NioServerSocketChannel.class)
                    //指定 ChannelInitializer去添加channel的流水线处理器,可以添加很多个
                    .childHandler(new ChannelInitializer<SocketChannel>() {					
                    	//这里可以添加多个Handler到流水线进行分步处理
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new DiscardServerHandler())
                            .addLast(new SecondHandler());
                        }
                    })
                    //设置参数:使用 ChannelOption的常量或者 ChannelConfig的实现类
                    .option(ChannelOption.SO_BACKLOG, 128)          // (5)针对接收连接的事件循环
                    .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)针对工作线程的循环

            //绑定端口,同步等待连接
            ChannelFuture f = bootstrap.bind(port).sync(); // (7)

            // 等待服务端监听端口关闭
            f.channel().closeFuture().sync();
        }finally {
            //优雅关闭释放相关所有资源
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new DiscardServer(8080).run();
    }
}

服务端逻辑处理代码 Handler

DiscardServerHandler
注意重写的方法中需要最后调用父类的方法才能够调用到下一个Hander的方法

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @Description 通过基础ChannelInboundHandlerAdapter来处理读取到的消息,这是一个适配器,可以覆盖想要重写的方法
 * @Date 2020/5/2 11:43
 * @Version V1.0
 */
public class DiscardServerHandler extends ChannelInboundHandlerAdapter {

    //事件处理方法,当收到消息的时候会调用,msg消息是ByteBuf类型的
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("DiscardServerHandler channelRead ~~~");
        ByteBuf  buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        System.out.println("receive msg:"+new String(req, "UTF-8"));

        //继续调用下一个handler的channelRead方法,将将消息解码后传递个下一个handler处理
        super.channelRead(ctx, new String(req, "UTF-8"));
    }

    //回调完成的方法将消息发出,flush方法在这里调用,性能更好,在channelRead中调用会频繁唤醒Selector
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("DiscardServerHandler channelReadComplete ~~");
        ctx.flush();
        //继续调用下一个handler的该方法
        super.channelReadComplete(ctx);
    }

    //发生异常的时候调用的方法,可以记录日志,关系相关的channel
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        System.out.println("发生异常了。。");
    }
}

SecondHandler

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @Date 2020/5/14 13:53
 * @Version V1.0
 */
public class SecondHandler extends ChannelInboundHandlerAdapter {

    //事件处理方法,当收到消息的时候会调用,msg消息是ByteBuf类型的
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("SecondHandler "+msg);
        String resp = "server time is "+System.currentTimeMillis();
        ByteBuf response = Unpooled.copiedBuffer(resp.getBytes());
//        将响应消息写入缓冲区
        ctx.write(response);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("SecondHandler channelReadComplete ");
        ctx.flush();
        super.channelReadComplete(ctx);
    }

    //发生异常的时候调用的方法,可以记录日志,关系相关的channel
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx,cause);
    }
}

客户端代码

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class DiscardClient {

    public static void main(String[] args) {
        new DiscardClient().connect(8080,"127.0.0.1");
    }

    public void connect(int port , String host){
        EventLoopGroup eventExecutors = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventExecutors).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new TimeClientHandler());
                        }
                    });
            //发起异步连接操作
            ChannelFuture sync = bootstrap.connect(host, port).sync();
            //等待客户端链路关闭
            sync.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (eventExecutors != null)
                eventExecutors.shutdownGracefully();
        }
    }
}

TimeClientHandler

public class TimeClientHandler extends ChannelInboundHandlerAdapter {
        ByteBuf firstMsg ;
        public TimeClientHandler() {
            byte[] bytes = "client hander rquest time".getBytes();
            firstMsg = Unpooled.copiedBuffer(bytes);
        }

        //客户端与服务端链路建立成功的时候调用这个方法
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ctx.writeAndFlush(firstMsg);
        }

        //处理服务端应答的消息
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf req = (ByteBuf)msg;
            byte[] by = new byte[req.readableBytes()];
            req.readBytes(by);
            System.out.println("clinet get time form server"+new String(by,"UTF-8"));
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }
    }

使用解码器

上面的没有解决由于粘包拆包导致的半包读写问题,可以使用netty自带的解码器。

导致拆包粘包问题原因:
1.应用程序写入的字节数大于套接字缓冲区的大小
2.进行MMS(最大报文段长度)大小的TCP分段
3.以太网帧的payload大于MTU进行IP分片

常用解决方案:
1.消息定长 ,例如固定数据报文的长度为200字节,不够长度则空格补齐
2.在包尾增加回车换行符进行分割, 例如FTP协议
3.将消息分为消息头和消息体,消息头中包含消息总长度(或者消息体长度)字段,通常的设计思路是为消息头的第一个字段使用int32来表示消息的总长度
4.使用更复杂的应用层协议

常用解码器如下:

protected void initChannel(SocketChannel ch) throws Exception {
	ByteBuf byteBuf = Unpooled.copiedBuffer("$".getBytes());
	ch.pipeline()
	//.addLast(new LineBasedFrameDecoder(4096))  //添加换行符解码器,解决拆包粘包问题
	//.addLast(new DelimiterBasedFrameDecoder(4096,byteBuf)) //使用自定义的符合作为分隔的解码器
	.addLast(new FixedLengthFrameDecoder(100))  //固定长度解码器
	.addLast(new StringDecoder(CharsetUtil.UTF_8))   //增加字符串解码器,返回的直接就是字符串
	.addLast(new NettyClient.ClientHandler());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值