1.netty
是一种网络传输框架,是对NIO的一个封装,一般用于游戏开发,与此相媲美的是MINA。作者都是同一个人。
2.netty的简单原理
从上图可以很清晰的看到客户端要与服务通信,必须要一个通道与一个端口才能使其相互通信,boos线程池接收数据分配任务给work线程池进行处理业务逻辑
3.客户端与服务器的通道的粘包与拆包的解决方案,
什么是粘包:
一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据。TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU(指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位))的往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在。处理粘包的唯一方法就是制定应用层的数据通讯协议,通过协议来规范现有接收的数据是否满足消息数据的需要。
解决办法
1、消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。
2、包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。
3、将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段
4、更复杂的自定义应用层协议
下面的则为3的分析
实例如下:
服务器:
package cn.horace.netty.server;
import io.netty.bootstrap.ServerBootstrap;
/**
* Netty服务器
*
* @author Administrator
*
*/
public class NettyServer {
// 服务器监听的端口
private static final int PORT = 1588;
public static void main(String[] args) {
// 创建服务器引导对象
ServerBootstrap bootstrap = new ServerBootstrap();
// 创建“线程池”
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
// 设置“线程池”
bootstrap.group(bossGroup, workGroup);
// 告诉Netty它也使用NioServerSocketChannel作为连接通道
bootstrap.channel(NioServerSocketChannel.class);
// 当客户端向服务器发送数据的时候都会经过这个方法
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
/**
* 解码器
*
* 1、maxFrameLength:最大帧长度值,数据包的最大长度
* 2、lengthFieldOffset:长度字段的偏移量
* 3、lengthFieldLength:长度子字段的长度值 4、lengthAdjustment:长度调节值
* 5、initialBytesToStrip:跳过长度字节值
* 6、failFast:当数据包超过maxFrameLength值时
* ,是否立即抛出异常,true:立即抛出异常,false:把数据接收完毕后在抛出异常
*/
socketChannel.pipeline().addLast(
new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4, false));
/**
* 编码器
*
* 1、lengthFieldLength:长度字段的长度值
* 2、lengthIncludesLengthFieldLength:如果为true,那么数据包的长度 =
* 长度字段的长度 + 实际数据的长度
*/
socketChannel.pipeline().addLast(new LengthFieldPrepender(4, 0, false));
// 数据包的处理器对象
socketChannel.pipeline().addLast(new ServerMessageHandler());
}
});
// 同步绑定端口,如果绑定端口失败,则会抛出异常
ChannelFuture future = bootstrap.bind(PORT).sync();
System.out.println("Server start at " + PORT + ".");
// 同步监听服务器端口的关闭
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 优雅的释放线程资源,并关闭服务器
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
客户端:
package cn.horace.netty.client;
import io.netty.bootstrap.Bootstrap;
/**
* Netty客户端
*
* @author Administrator
*
*/
public class NettyClient {
// 服务器监听的端口
private static final int PORT = 1588;
private static final String HOST = "127.0.0.1";
public static void main(String[] args) {
// 创建客户端引导对象
Bootstrap bootstrap = new Bootstrap();
// 创建“线程池”
NioEventLoopGroup group = new NioEventLoopGroup();
try {
// 设置“线程池”
bootstrap.group(group);
// 告诉Netty它也使用NioSocketChannel作为连接通道
bootstrap.channel(NioSocketChannel.class);
// 当客户端向服务器发送数据的时候都会经过这个方法
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
// 这个方法一般是用于做数据包的解码与编码操作
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
/**
* 解码器
*
* 1、maxFrameLength:最大帧长度值,数据包的最大长度
* 2、lengthFieldOffset:长度字段的偏移量
* 3、lengthFieldLength:长度子字段的长度值 4、lengthAdjustment:长度调节值
* 5、initialBytesToStrip:跳过长度字节值
* 6、failFast:当数据包超过maxFrameLength值时
* ,是否立即抛出异常,true:立即抛出异常,false:把数据接收完毕后在抛出异常
*/
socketChannel.pipeline().addLast(
new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4, false));
/**
* 编码器
*
* 1、lengthFieldLength:长度字段的长度值
* 2、lengthIncludesLengthFieldLength:如果为true,那么数据包的长度 =
* 长度字段的长度 + 实际数据的长度
*/
socketChannel.pipeline().addLast(new LengthFieldPrepender(4, 0, false));
// 数据包的处理器对象
socketChannel.pipeline().addLast(new ClientMessageHandler());
}
});
// 连接服务器
ChannelFuture future = bootstrap.connect(new InetSocketAddress(HOST, PORT)).sync();
System.out.println("Conennect server success.");
// 同步监听服务器端口的关闭
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 优雅的释放线程资源,并关闭客户端
group.shutdownGracefully();
}
}
}
注意:别忘了添加netty-all-4.0.31.Final.jar哦,没有jar可到netty官网http://netty.io/下载,或者前往http://mvnrepository.com/search?q=netty下载