Socket 网络编程(二)

Netty

Netty 实现通信的步骤:
1)创建两个 NIO 线程组,一个专门用于网络事件处理(接受客户端的连接),另一个则进行网络通信读写。
2)创建一个 ServerBootstrap 对象,配置 Netty 的一系列参数,例如接受传出数据的缓存大小等等。
3)创建一个实际处理数据的类 ChannelInitializer,进行初始化的准备工作,比如设置接受传出数据的字符集、格式、以及实际处理数据的接口。
4)绑定端口,执行同步阻塞方法等待服务器端启动即可。

示例代码:

Server.java

public class Server {

    public static void main(String[] args) throws Exception {
        //1 创建线两个程组 
        //一个是用于处理服务器端接收客户端连接的
        //一个是进行网络通信的(网络读写的)
        EventLoopGroup pGroup = new NioEventLoopGroup();
        EventLoopGroup cGroup = new NioEventLoopGroup();
        
        //2 创建辅助工具类,用于服务器通道的一系列配置
        ServerBootstrap b = new ServerBootstrap();
        b.group(pGroup, cGroup)     //绑定俩个线程组
        .channel(NioServerSocketChannel.class)      //指定NIO的模式
        .option(ChannelOption.SO_BACKLOG, 1024)     //设置tcp缓冲区
        .option(ChannelOption.SO_SNDBUF, 32*1024)   //设置发送缓冲大小
        .option(ChannelOption.SO_RCVBUF, 32*1024)   //这是接收缓冲大小
        .option(ChannelOption.SO_KEEPALIVE, true)   //保持连接
        .childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(SocketChannel sc) throws Exception {
                //3 在这里配置具体数据接收方法的处理
                sc.pipeline().addLast(new ServerHandler());
            }
        });
        
        //4 进行绑定 
        ChannelFuture cf1 = b.bind(8765).sync();
        //ChannelFuture cf2 = b.bind(8764).sync();
        //5 等待关闭
        cf1.channel().closeFuture().sync();
        //cf2.channel().closeFuture().sync();
        pGroup.shutdownGracefully();
        cGroup.shutdownGracefully();
    }
}

ServerHandler.java

public class ServerHandler extends ChannelHandlerAdapter {


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("server channel active... ");
    }


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
            ByteBuf buf = (ByteBuf) msg;
            byte[] req = new byte[buf.readableBytes()];
            buf.readBytes(req);
            String body = new String(req, "utf-8");
            System.out.println("Server :" + body );
            String response = "进行返回给客户端的响应:" + body ;
            ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
            //.addListener(ChannelFutureListener.CLOSE);
    }

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

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

Client.java

public class Client {

    public static void main(String[] args) throws Exception{
        
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group)
        .channel(NioSocketChannel.class)
        .handler(new ChannelInitializer() {
            @Override
            protected void initChannel(SocketChannel sc) throws Exception {
                sc.pipeline().addLast(new ClientHandler());
            }
        });
        
        ChannelFuture cf1 = b.connect("127.0.0.1", 8765).sync();
        //ChannelFuture cf2 = b.connect("127.0.0.1", 8764).sync();
        //发送消息
        Thread.sleep(1000);
        cf1.channel().writeAndFlush(Unpooled.copiedBuffer("777".getBytes()));
        cf1.channel().writeAndFlush(Unpooled.copiedBuffer("666".getBytes()));
        //cf2.channel().writeAndFlush(Unpooled.copiedBuffer("888".getBytes()));
        Thread.sleep(2000);
        cf1.channel().writeAndFlush(Unpooled.copiedBuffer("888".getBytes()));
        //cf2.channel().writeAndFlush(Unpooled.copiedBuffer("666".getBytes()));
        
        cf1.channel().closeFuture().sync();
        //cf2.channel().closeFuture().sync();
        group.shutdownGracefully();
        
        
        
    }
}

ClientHandler.java

public class ClientHandler extends ChannelHandlerAdapter{


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            ByteBuf buf = (ByteBuf) msg;
            
            byte[] req = new byte[buf.readableBytes()];
            buf.readBytes(req);
            
            String body = new String(req, "utf-8");
            System.out.println("Client :" + body );
            String response = "收到服务器端的返回信息:" + body;
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

    }

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

TCP 粘包、拆包问题

产生原因:

1)应用程序 write 写入的字节大小套接口发送缓冲区的大小。
2)进行 MSS 大小的 TCP 分段。
3)以太网帧的 payload 大于 MTU 进行 IP 分片。

解决方案:

1)消息定长,例如每个报文的大小固定为 200 个字节,如果不够,空位补空格。
2)在包尾部增加特殊字符进行分割,例如加回车等。
3)将消息分为消息头和消息体,在消息头中包含表示消息总长度的字段,然后进行业务逻辑的处理。

Netty 如何解决粘包、拆包问题:

1)分隔符类 DelimiterBasedFrameDecoder(自定义分隔符)
2)FixedLengthFrameDecoder(定长)

你可能感兴趣的:(Socket 网络编程(二))