#前言
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。由于jdk的nio使用比较复杂,并且自己之前看过netty的相关书籍,刚刚好拿来练手
public static void connect(int port){
//netty主从线程模型(建立2个线程组) 一个用于网络读写 一个用于和客户的进行连接
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workerGroup=new NioEventLoopGroup();
try {
//启动辅助类 用于配置各种参数
ServerBootstrap b=new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)//最大排列队数
.childHandler(new ChannelInitializer(){
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(8192));//以换行符为结束位置进行分包
socketChannel.pipeline().addLast(new StringDecoder());//将接收到的对象转为字符串
socketChannel.pipeline().addLast(new RPCResponseHandler());//处理类
}
});
//绑定端口 同步等待成功
ChannelFuture future=b.bind(port).sync();
System.out.println("netty server start on port:"+port);
//同步等待服务端监听端口关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放资源退出
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
1.创建了一个 ServerBootstrap
是启动辅助类
2.创建了2个EventLoopGroup
是一个主从的线程模型并将其放入启动类中 一个用于网络读写 一个用于和客户的进行连接
3.设置 NioServerSocketChannel
这个意思是用于服务端非阻塞地接收TCP连接。当然还有好几种类型包括阻塞的channel等。Netty的Channel在JDK NIO的Channel基础上做了一层封装,提供了更多的功能
4.设置 option
option(ChannelOption.SO_BACKLOG,1024) 意思是当很多客户的进行与服务器连接会进行排队,这里设置的1024指的是队列的最大排列数量
5.设置childHandler
LineBasedFrameDecoder 是一种是用于防止粘包和分包的解码器,以换行符为结尾会自动解析成一个包。
StringDecoder 将对象变成String的解码器
RPCResponseHandler 自己编写的处理类(接收客户端请求和发起应答)
6.绑定端口
7.同步等待服务端监听端口关闭
8.释放资源
TCP是一个"流"的协议,所以底层就是一串没有界限的数据。一个业务可能会发生一个包拆解成N个小包进行发送,也有可能会把N个包合并成一个包进行发送。
当客户端发送2个数据包给服务端的时候会出现好几种情况。(B1,B2 两个包)
1.服务器读到2个独立的包 B1,B2。没有问题
2.服务器读到1个包,B1,B2合并在一起,这个就是粘包
3.服务器读到2个包,一个完整的B1和部分B2,一个是剩下B2。这个就是拆包
…等其他情况
1.定长的数据包比如说 1024 。不到的用空格补起
2.以某种类型的符号进行分割。例如 换行符
3.更为复杂的协议 例如http webservice等
DelimiterBasedFrameDecoder 分隔解码器
LineBasedFrameDecoder 换行符分隔解码器
FixedLengthFrameDecoder 固定长度解码器,二进制
public class RPCResponseHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws IOException {
String requestJson= (String) msg;
RPCRequest request= RPC.requestDeocde(requestJson);
//通过反射调用服务
Object result=InvokeServiceUtil.invoke(request);
RPCResponse response=new RPCResponse();
response.setRequestID(request.getRequestID());
response.setResult(result);
String respStr=RPC.responseEncode(response);
ByteBuf responseBuf= Unpooled.copiedBuffer(respStr.getBytes());
//写入缓存数组
System.out.println("response : " + respStr);
System.out.println(respStr.getBytes().length);
ctx.writeAndFlush(responseBuf);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//flush方法再全部写到通道中
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
这个很简单 继承ChannelHandlerAdapter
channelRead() 方法用于读取到客户端发过来的数据 进行业务操作之后把响应数据写入缓存数组
channelReadComplete() 方法用于发送数据 将数据发送至channel通道
public static Object invoke(RPCRequest request){
Object result=null;
try {
Class implClass=Class.forName(request.getClassName());
Object[] parameters=request.getParameters();
int parameterNums=request.getParameters().length;
Class[] parameterTypes=new Class[parameterNums];
for (int i = 0; i
public void Initialized() {
//初始化方法 不开线程会和tomcat 冲突
new Thread() {
public void run() {
RPC.start();
}
}.start();//开启线程
}
/**
* 实现端启动RPC服务
*/
public static void start(){
RPCResponseNet.connect(port);
}
注意 这里在Spring中注入bean 初始化调换用start() 方法是不行的,
会和tomcat有冲突 导致tomcat 并没有启动 只启动了netty服务。所以必须启动一个线程了单独启动netty服务。