Netty 应用-解决粘包/半包

基本使用

1. 创建客户端handler

  • 继承SimpleChannelInboundHandler

在这里插入图片描述

package org.example.echo;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
    

    //channel活跃后进行业务处理
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello Netty !", CharsetUtil.UTF_8));
    }

    //读取到网络数据后进行业务处理
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
    
    
        System.out.println("Client accept:" + byteBuf.toString(CharsetUtil.UTF_8));
    }
}

2. 创建客户端

  • 客户端主体流程基本不需要改变,程序员只需要根据自己的需要在pipeline中添加handler即可

在这里插入图片描述

package org.example.echo;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 java.net.InetSocketAddress;

public class NettyClient {
    
    
    private String host;
    private int port;

    public NettyClient(String host, int port) {
    
    
        this.host = host;
        this.port = port;
    }

    public void start() throws InterruptedException {
    
    
        //线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
            //客户端启动必备
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)//指定使用nio的通信模式
                    .remoteAddress(new InetSocketAddress(host, port))//指定服务器的ip和端口
                    .handler(new ChannelInitializer<SocketChannel>() {
    
    
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
    
    
                            socketChannel.pipeline().addLast(new NettyClientHandler());//添加handler
                        }
                    });
            //异步连接到服务器,sync()会阻塞到任务完成
            ChannelFuture future = bootstrap.connect().sync();
            //阻塞当前线程,直到服务器的ServerChannel被关闭
            future.channel().closeFuture().sync();
        }finally {
    
    
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) {
    
    
        NettyClient client = new NettyClient("127.0.0.1", 9001);
        try {
    
    
            client.start();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }

}

3. 创建服务端handler

  • 继承ChannelInboundHandlerAdapter

在这里插入图片描述

package org.example.echo;

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

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    
    //服务端读取到网络数据后的处理
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        ByteBuf in = (ByteBuf) msg;
        System.out.println("Server accept: " + in.toString(CharsetUtil.UTF_8));
        ctx.writeAndFlush(in);
    }

    //发生异常后的处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
        cause.printStackTrace();
        ctx.close();
    }
}

4. 创建服务端

在这里插入图片描述

package org.example.echo;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 java.net.InetSocketAddress;

//基于Netty的服务端
public final class NettyServer {
    
    
    private final int port;

    public NettyServer(int port) {
    
    
        this.port = port;
    }

    public void start() throws InterruptedException {
    
    
        //线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
        //服务端启动必备
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)//指定使用nio的通信模式
                .localAddress(new InetSocketAddress(port))//指定端口监听
                .childHandler(new ChannelInitializer<SocketChannel>() {
    
    
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
    
    
                        socketChannel.pipeline().addLast(new NettyServerHandler());//添加handler
                    }
                });
            //异步绑定到服务器,sync()会阻塞到任务完成
            ChannelFuture future = bootstrap.bind().sync();
            //阻塞当前线程,直到服务器的ServerChannel被关闭
            future.channel().closeFuture().sync();
        }finally {
    
    
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) {
    
    
        NettyServer server = new NettyServer(9001);
        System.out.println("服务器即将启动");
        try {
    
    
            server.start();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("服务器关闭");
    }
}

5. 先启动服务端,再启动客户端

  • 服务端打印,接收到客户端发送的消息

在这里插入图片描述

  • 客户端打印,接收到服务端的响应消息

在这里插入图片描述

粘包/半包问题

  • 修改客户端handler,NettyClient类不变

在这里插入图片描述

package org.example.sticky;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

import java.util.concurrent.atomic.AtomicInteger;

public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
    

    private AtomicInteger count = new AtomicInteger(0);
    //channel活跃后进行业务处理
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        for (int i = 0; i < 200; i++) {
    
    
            //line.separator换行符,分200次发送到服务端
            ctx.writeAndFlush(Unpooled.copiedBuffer("fisher" +
                    System.getProperty("line.separator"), CharsetUtil.UTF_8));
        }
    }

    //读取到网络数据后进行业务处理
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
    
    
        //读取到服务端响应的数据进行打印
        System.out.println("Client accept:" + byteBuf.toString(CharsetUtil.UTF_8) +
                ", count=" + count.incrementAndGet());
    }
}

  • 修改服务端handler,NettyServer类不变

在这里插入图片描述

package org.example.sticky;

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

import java.util.concurrent.atomic.AtomicInteger;

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    
    private AtomicInteger counter = new AtomicInteger(0);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        ByteBuf in = (ByteBuf) msg;
        String request = in.toString(CharsetUtil.UTF_8);
        //打印接收到的消息,并计数
        System.out.println("Server accept: " + request + ", counter=" + counter.incrementAndGet());
        //发送响应消息到客户端
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello " + request+" !"+
                System.getProperty("line.separator"), CharsetUtil.UTF_8));
    }

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

  • 先启动服务端,再启动客户端
  • 服务端收到200条消息,只用了2次

在这里插入图片描述
在这里插入图片描述

扫描二维码关注公众号,回复: 13586960 查看本文章

1. 什么是TCP粘包半包

在这里插入图片描述

  • 假设客户端分别发送了两个数据包 D1 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下 4 种情况。
    (1)服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2,没有粘包和拆包;
    (2)服务端一次接收到了两个数据包,D1 和 D2 粘合在一起,被称为 TCP 粘包;
    (3)服务端分两次读取到了两个数据包,第一次读取到了完整的 D1 包和 D2 包的部分内容,第二次读取到了 D2 包的剩余内容,这被称为 TCP 拆包;
    (4)服务端分两次读取到了两个数据包,第一次读取到了 D1 包的部分内容 D1_1,第二次读取到了 D1 包的剩余内容 D1_2 和 D2 包的整包。
  • 如果此时服务端 TCP 接收滑窗非常小,而数据包 D1 和 D2 比较大,很有可能会发生第五种可能,即服务端分多次才能将 D1 和 D2 包接收完全,期间发生多次拆包。

2. TCP 粘包/半包发生的原因

  • 由于 TCP 协议本身的机制(面向连接的可靠地协议-三次握手机制)客户端与服务器会维持一个连接(Channel),数据在连接不断开的情况下,可以持续不断地将多个数据包发往服务器,但是如果发送的网络数据包太小,那么他本身会启用 Nagle 算法(可配置是否启用)对较小的数据包进行合并(基于此,TCP 的网络延迟要 UDP 的高些)然后再发送(超时或者包大小足够)。那么这样的话,服务器在接收到消息(数据流)的时候就无法区分哪些数据包是客户端自己分开发送的,这样产生了粘包;服务器在接收到数据后,放到缓冲区中,如果消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的情况,造成粘包现象。
  • UDP:本身作为无连接的不可靠的传输协议(适合频繁发送较小的数据包),他不会对数据包进行合并发送(也就没有 Nagle 算法之说了),他直接是一端发送什么数据,直接就发出去了,既然他不会对数据合并,每一个数据包都是完整的(数据+UDP 头+IP 头等,发一次数据封装一次)也就没有粘包问题了。
  • 分包产生的原因就简单的多:可能是 IP 分片传输导致的,也可能是传输过程中丢失部分包导致出现的半包,还有可能就是一个包可能被分成了两次传输,在取数据的时候,先取到了一部分(还可能与接收的缓冲区大小有关系),总之就是一个数据包被分成了多次接收。
  • 原因有三个
  1. 应用程序写入数据的字节大小大于套接字发送缓冲区的大小;
  2. 进行 MSS 大小的 TCP 分段。MSS 是最大报文段长度的缩写。MSS 是 TCP 报文段中的数据字段的最大长度。数据字段加上 TCP 首部才等于整个的 TCP 报文段。所以 MSS 并不是 TCP 报文段的最大长度,而是:MSS=TCP 报文段长度-TCP 首部长度;
  3. 以太网的 payload 大于 MTU 进行 IP 分片。MTU 指:一种通信协议的某一层上面所能通过的最大数据包大小。如果 IP 层有一个数据包要传,而且数据的长度比链路层的 MTU 大, 那么 IP 层就会进行分片,把数据包分成若干片,让每一片都不超过 MTU。注意,IP 分片可以发生在原始发送端主机上,也可以发生在中间路由器上。

解决粘包半包问题

  • 由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重 组的,这个问题只能通过上层的应用协议栈设计来解决

1. 增加分割符

回车换行符进行分割

  • NettyClientHandler

在这里插入图片描述

package org.example.linebase;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

import java.util.concurrent.atomic.AtomicInteger;

public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
    

    private AtomicInteger count = new AtomicInteger(0);
    //channel活跃后进行业务处理
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        for (int i = 0; i < 200; i++) {
    
    
            ctx.writeAndFlush(Unpooled.copiedBuffer("fisher"
                    + System.getProperty("line.separator"), CharsetUtil.UTF_8));
        }
    }

    //读取到网络数据后进行业务处理
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
    
    
        System.out.println("Client accept:" + byteBuf.toString(CharsetUtil.UTF_8) + ", count=" + count.incrementAndGet());
    }
}

  • NettyClient

在这里插入图片描述

package org.example.linebase;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;

import java.net.InetSocketAddress;

public class NettyClient {
    
    
    private String host;
    private int port;

    public NettyClient(String host, int port) {
    
    
        this.host = host;
        this.port = port;
    }

    public void start() throws InterruptedException {
    
    
        //线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
            //客户端启动必备
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)//指定使用nio的通信模式
                    .remoteAddress(new InetSocketAddress(host, port))//指定服务器的ip和端口
                    .handler(new ChannelInitializer<Channel>() {
    
    
                        @Override
                        protected void initChannel(Channel channel) throws Exception {
    
    
                            //添加以换行符为标志的解码器
                            channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
                            channel.pipeline().addLast(new NettyClientHandler());//添加handler
                        }
                    });
            //异步连接到服务器,sync()会阻塞到任务完成
            ChannelFuture future = bootstrap.connect().sync();
            //阻塞当前线程,直到服务器的ServerChannel被关闭
            future.channel().closeFuture().sync();
        }finally {
    
    
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) {
    
    
        NettyClient client = new NettyClient("127.0.0.1", 9001);
        try {
    
    
            client.start();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }

}

  • NettyServerHandler
package org.example.linebase;

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

import java.util.concurrent.atomic.AtomicInteger;

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    
    private AtomicInteger counter = new AtomicInteger(0);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        ByteBuf in = (ByteBuf) msg;
        String request = in.toString(CharsetUtil.UTF_8);
        System.out.println("Server accept: " + request + ", counter=" + counter.incrementAndGet());
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello " + request+" !"+ System.getProperty("line.separator"), CharsetUtil.UTF_8));
    }

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

  • NettyServer

在这里插入图片描述

package org.example.linebase;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;

import java.net.InetSocketAddress;

//基于Netty的服务端
public final class NettyServer {
    
    
    private final int port;

    public NettyServer(int port) {
    
    
        this.port = port;
    }

    public void start() throws InterruptedException {
    
    
        //线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
        //服务端启动必备
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)//指定使用nio的通信模式
                .localAddress(new InetSocketAddress(port))//指定端口监听
                .childHandler(new ChannelInitializer<Channel>() {
    
    
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
    
    
                        //添加以换行符为标志的解码器
                        channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        channel.pipeline().addLast(new NettyServerHandler());//添加handler
                    }

                });
            //异步绑定到服务器,sync()会阻塞到任务完成
            ChannelFuture future = bootstrap.bind().sync();
            //阻塞当前线程,直到服务器的ServerChannel被关闭
            future.channel().closeFuture().sync();
        }finally {
    
    
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) {
    
    
        NettyServer server = new NettyServer(9001);
        System.out.println("服务器即将启动");
        try {
    
    
            server.start();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("服务器关闭");
    }
}

  • 先启动服务端,再启动客户端,查看打印
  • 服务端

在这里插入图片描述

  • 客户端

在这里插入图片描述

自定义分割符

  • NettyClientHandler,使用“!@#”作为分隔符

在这里插入图片描述

package org.example.delimiter;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

import java.util.concurrent.atomic.AtomicInteger;

public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
    

    private AtomicInteger count = new AtomicInteger(0);
    //channel活跃后进行业务处理
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        for (int i = 0; i < 200; i++) {
    
    
            ctx.writeAndFlush(Unpooled.copiedBuffer("fisher" + "!@#", CharsetUtil.UTF_8));
        }
    }

    //读取到网络数据后进行业务处理
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
    
    
        System.out.println("Client accept:" + byteBuf.toString(CharsetUtil.UTF_8) + ", count=" + count.incrementAndGet());
    }

}

  • NettyClient

在这里插入图片描述

package org.example.delimiter;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;

import java.net.InetSocketAddress;

public class NettyClient {
    
    
    private String host;
    private int port;

    public NettyClient(String host, int port) {
    
    
        this.host = host;
        this.port = port;
    }

    public void start() throws InterruptedException {
    
    
        //线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
            //客户端启动必备
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)//指定使用nio的通信模式
                    .remoteAddress(new InetSocketAddress(host, port))//指定服务器的ip和端口
                    .handler(new ChannelInitializer<Channel>() {
    
    
                        @Override
                        protected void initChannel(Channel channel) throws Exception {
    
    
                            ByteBuf buf = Unpooled.copiedBuffer("!@#".getBytes());
                            //添加分隔符解码器
                            channel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,buf));
                            channel.pipeline().addLast(new NettyClientHandler());//添加handler
                        }
                    });
            //异步连接到服务器,sync()会阻塞到任务完成
            ChannelFuture future = bootstrap.connect().sync();
            //阻塞当前线程,直到服务器的ServerChannel被关闭
            future.channel().closeFuture().sync();
        }finally {
    
    
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) {
    
    
        NettyClient client = new NettyClient("127.0.0.1", 9001);
        try {
    
    
            client.start();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }

}

  • NettyServerHandler

在这里插入图片描述

package org.example.delimiter;

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

import java.util.concurrent.atomic.AtomicInteger;

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    
    private AtomicInteger counter = new AtomicInteger(0);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        ByteBuf in = (ByteBuf) msg;
        String request = in.toString(CharsetUtil.UTF_8);
        System.out.println("Server accept: " + request + ", counter=" + counter.incrementAndGet());
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello " + request+" !"+
                "!@#", CharsetUtil.UTF_8));
    }

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

  • NettyServer

在这里插入图片描述

package org.example.delimiter;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;

import java.net.InetSocketAddress;

//基于Netty的服务端
public final class NettyServer {
    
    
    private final int port;

    public NettyServer(int port) {
    
    
        this.port = port;
    }

    public void start() throws InterruptedException {
    
    
        //线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
        //服务端启动必备
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)//指定使用nio的通信模式
                .localAddress(new InetSocketAddress(port))//指定端口监听
                .childHandler(new ChannelInitializer<Channel>() {
    
    
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
    
    
                        ByteBuf buf = Unpooled.copiedBuffer("!@#".getBytes());
                        //添加分隔符解码器
                        channel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,buf));
                        channel.pipeline().addLast(new NettyServerHandler());//添加handler
                    }

                });
            //异步绑定到服务器,sync()会阻塞到任务完成
            ChannelFuture future = bootstrap.bind().sync();
            //阻塞当前线程,直到服务器的ServerChannel被关闭
            future.channel().closeFuture().sync();
        }finally {
    
    
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) {
    
    
        NettyServer server = new NettyServer(9001);
        System.out.println("服务器即将启动");
        try {
    
    
            server.start();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("服务器关闭");
    }
}

  • 先启动服务端,再启动客户端,查看打印
  • 服务端

在这里插入图片描述

  • 客户端

在这里插入图片描述

2. 消息定长

  • 例如每个报文的大小为固定长度 200 字节,如果不够,空位需要补空格;
  • NettyClientHandler

在这里插入图片描述

package org.example.fix;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

import java.util.concurrent.atomic.AtomicInteger;

public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
    

    private AtomicInteger count = new AtomicInteger(0);
    //channel活跃后进行业务处理
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        for (int i = 0; i < 200; i++) {
    
    
            //发送固定长度的消息
            ctx.writeAndFlush(Unpooled.copiedBuffer("fisher", CharsetUtil.UTF_8));
        }
    }

    //读取到网络数据后进行业务处理
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
    
    
        System.out.println("Client accept:" + byteBuf.toString(CharsetUtil.UTF_8) + ", count=" + count.incrementAndGet());
    }
}

  • NettyClient

在这里插入图片描述

package org.example.fix;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;

import java.net.InetSocketAddress;

public class NettyClient {
    
    
    private String host;
    private int port;

    public NettyClient(String host, int port) {
    
    
        this.host = host;
        this.port = port;
    }

    public void start() throws InterruptedException {
    
    
        //线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
            //客户端启动必备
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)//指定使用nio的通信模式
                    .remoteAddress(new InetSocketAddress(host, port))//指定服务器的ip和端口
                    .handler(new ChannelInitializer<Channel>() {
    
    
                        @Override
                        protected void initChannel(Channel channel) throws Exception {
    
    
                            //添加固定长度解码器
                            channel.pipeline().addLast(new FixedLengthFrameDecoder("fisher".length()));
                            channel.pipeline().addLast(new NettyClientHandler());//添加handler
                        }
                    });
            //异步连接到服务器,sync()会阻塞到任务完成
            ChannelFuture future = bootstrap.connect().sync();
            //阻塞当前线程,直到服务器的ServerChannel被关闭
            future.channel().closeFuture().sync();
        }finally {
    
    
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) {
    
    
        NettyClient client = new NettyClient("127.0.0.1", 9001);
        try {
    
    
            client.start();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }

}

  • NettyServerHandler

在这里插入图片描述

package org.example.fix;

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

import java.util.concurrent.atomic.AtomicInteger;

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    
    private AtomicInteger counter = new AtomicInteger(0);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        ByteBuf in = (ByteBuf) msg;
        String request = in.toString(CharsetUtil.UTF_8);
        System.out.println("Server accept: " + request + ", counter=" + counter.incrementAndGet());
        //这里为了保证消息长度,直接把接收的消息返回
        ctx.writeAndFlush(in);
    }

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

  • NettyServer

在这里插入图片描述

package org.example.fix;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;

import java.net.InetSocketAddress;

//基于Netty的服务端
public final class NettyServer {
    
    
    private final int port;

    public NettyServer(int port) {
    
    
        this.port = port;
    }

    public void start() throws InterruptedException {
    
    
        //线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
        //服务端启动必备
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)//指定使用nio的通信模式
                .localAddress(new InetSocketAddress(port))//指定端口监听
                .childHandler(new ChannelInitializer<Channel>() {
    
    
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
    
    
                        //添加固定长度解码器,指定消息长度
                        channel.pipeline().addLast(new FixedLengthFrameDecoder("fisher".length()));
                        channel.pipeline().addLast(new NettyServerHandler());//添加handler
                    }
                });
            //异步绑定到服务器,sync()会阻塞到任务完成
            ChannelFuture future = bootstrap.bind().sync();
            //阻塞当前线程,直到服务器的ServerChannel被关闭
            future.channel().closeFuture().sync();
        }finally {
    
    
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) {
    
    
        NettyServer server = new NettyServer(9001);
        System.out.println("服务器即将启动");
        try {
    
    
            server.start();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("服务器关闭");
    }
}

  • 先启动服务端,再启动客户端
  • 服务端

在这里插入图片描述

  • 客户端

在这里插入图片描述

代码下载地址

https://gitee.com/fisher3652/netWork.git

猜你喜欢

转载自blog.csdn.net/qq_40977118/article/details/109513433