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