Java架构学习(十一)netty5实现的服务器端代码&客户端代码&TCP 粘包与拆包处理方案

Netty高级

一、使用netty5实现的服务器端代码和客户端代码

服务器端代码
package com.leeue.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
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;
import io.netty.handler.codec.string.StringDecoder;

/**
 * 
 * @classDesc: 功能描述:(使用netty5.0实现的服务端)
 * @author:<a href="leeue@foxmail.com">李月</a>
 * @Version:v1.0
 * @createTime:2018年7月31日 上午10:42:50
 */

class ServerHandler extends ChannelHandlerAdapter {
    /**
     * 当通道被调用的时候执行方法 拿到数据的时候就调用方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String value = (String) msg;
        System.out.println("服务期端收到客户端msg:" + value);
        // 回复客户端
        ctx.writeAndFlush("我是服务器端我收到了你的信息");
        // 必须要调用Flush方法才能放到通道里去发送。
    }
}

public class NettyServer {
    public static void main(String[] args) {

        try {
            System.out.println("服务器端启动......");
            // 1.创建两个线程池,一个负责接受客户端,一个进行传输
            NioEventLoopGroup pGroup = new NioEventLoopGroup();
            NioEventLoopGroup cGroup = new NioEventLoopGroup();
            // 2.创建辅助类
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(pGroup, cGroup).channel(NioServerSocketChannel.class)
                    // 3.设置缓冲区大小
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    // 4.设置发送缓冲区大小
                    .option(ChannelOption.SO_SNDBUF, 1024)
                    // 5.配置handler
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel sc) throws Exception {
                            // 让返回的是字符串类型 设置返回的结果是String类型
                            sc.pipeline().addLast(new StringDecoder());
                            sc.pipeline().addLast(new ServerHandler());
                        }
                    });
            // 启动netty
            ChannelFuture cFuture = serverBootstrap.bind(8080).sync();
            // 关闭
            cFuture.channel().closeFuture().sync();
            pGroup.shutdownGracefully();
            cGroup.shutdownGracefully();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
客户端代码
package com.leeue.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;

/**
 * 
 * @classDesc: 功能描述:(使用netty5实现的客户端)
 * @author:<a href="[email protected]">李月</a>
 * @Version:v1.0
 * @createTime:2018年7月31日 上午11:01:58
 */
class ClientHandler extends ChannelHandlerAdapter {
    /**
     * 当通道被调用时候执行改方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 接收数据
        String value = (String) msg;
        System.out.println("client msg:" + value);
    }
}

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("客户端已经被启动....");
        //创建负责接受客户端连接
        NioEventLoopGroup pGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(pGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel sc) throws Exception {
                sc.pipeline().addLast(new StringDecoder());
                sc.pipeline().addLast(new ClientHandler());
            }

        });

        ChannelFuture cFuture = bootstrap.connect("127.0.0.1",8080).sync();
        cFuture.channel().writeAndFlush(Unpooled.wrappedBuffer("leeue ".getBytes()));
        cFuture.channel().writeAndFlush(Unpooled.wrappedBuffer("leeue".getBytes()));
        //等待客户端端口号关闭
        cFuture.channel().closeFuture().sync();
        pGroup.shutdownGracefully();
    }
}
错误原因分析:客户端没有正常与服务器端关闭连接。

这里写图片描述

长连接:
    客户端与服务期端发送消息后还会保持一直连接,叫长连接。
    上面就是因为客户端与服务器端一直保持连接的。

    客户端没有正常与服务器端关闭连接。

    应用:移动端的消息推送、mq
短连接:
    就是不是一直与服务器端保持连接。
    应用:Http协议。

二、TCP 粘包与拆包

一个完整的业务,可能会被TCP拆分成多个包进行发送,
也可能把多个小的数据包装成一个大的数据包进行发送。
这个就是TCP的拆包和封包的问题。

粘包:将多个包合在一起
拆包:将一个包拆分成多个包

只有在TCP才有这两个概念

这里写图片描述

上图中明明发送了5次,为什么在服务器端显示只有两次? 上面就是粘包案例

因为TCP底层做了优化:Thread.sleep(1000)的时候使通道阻塞了
所以下面的两个发送数据没办法一起发送。

将Thread.sleep(1000)去掉。

这里写图片描述

解决粘包的方法:
    1、消息定长、报文固定长度、不够空格补全。
    2、发送方和接受方遵循相同的规则来传输数据,这样粘包了
    也能通过接收方编程来实现获取定长报文也能区分。

方法2实现代码

    在服务器端和客户端都加上一下代码
    //这两行代码表示 我发送方以 "_li" 来分割
ByteBuf buf = Unpooled.copiedBuffer("_li".getBytes());
sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
客户端:

这里写图片描述

上面就是通过“_li”来区分,有_li就发送,没有就不发送

这里写图片描述

只要识别到了"_li"就会发送
如果没有"_li"通道就会一直保持阻塞状态,直到有"_li"的时候才会发送

这里写图片描述

这里写图片描述

方法1的实现 定义长度来解决粘包问题

sc.pipeline().addLast(new FixedLengthFrameDecoder(10));

这里写图片描述

三、序列化

什么是序列化?
    序列化:将对象序列化成二进制文件,保存在硬盘上。
    反序列化:将二进制文件反序列化成对象。

序列化有哪些协议?
    json、xml

猜你喜欢

转载自blog.csdn.net/leeue/article/details/81296017
今日推荐