目录
一、Netty是什么呢?
Netty是一个异步、基于事件驱动的网络应用框架,用以快速开发高性能,高可靠的网络IO程序。其本质只一个NIO的框架,主要针对于TCP协议。
1.1什么是同步、什么是异步?
同步:指一个进程在执行某条请求时,需要获取到需要的数据之后,才能继续执行之后的操作。(在等待的这段时间内,该进程一直等待阻塞)
异步:指一条指令在执行某条请求时,如需要获取数据该请求不需要一直等待,而是先去执行后续的操作,等到消息体返回时,系统会通知进程进行处理。这样大大提高了效率。
1.2什么是基于事件驱动?
- 事件:在Netty中,事件是指各种网络操作的触发器,比如连接建立、数据读取和写入等。事件可以是系统产生的,也可以是外部输入的。
- 事件源:事件源是产生事件的实体,可以是客户端、服务端、连接、通道等。事件源负责触发事件并把事件传递给事件处理器。
- 事件处理器:事件处理器是对特定事件的处理逻辑的封装。当一个事件被触发,事件处理器负责执行相应的业务逻辑。
- 事件循环:事件循环是Netty的核心机制之一,负责将事件分发给相应的事件处理器进行处理。事件循环会不断地从事件队列中获取事件,并将事件分发给注册了该事件的事件处理器。
- 回调:Netty使用回调机制来实现事件的异步处理。当事件被处理完成后,会调用相应的回调方法,通知发起调用的代码。
Netty的事件驱动模型在底层利用了Java的NIO(New I/O)机制,通过选择器(Selector)监听通道上的不同事件,并进行相应的处理。
1.3什么是IO、什么是NIO?
IO(Input/Output)的缩写是计算机或者程序一外部世界交互的一种方式。
传统的IO模型是阻塞的。当执行数据的读写操作,程序都会进行阻塞等待读写操作完成之后才能进行后续操作;并且这种模型在处理大量并发连接时效率低下。
NIO是Java提供的一种基于通道和缓冲区的IO模型。可以实现连接多个IO请求。
通道类似于IO中的流,但通道可以同时连接IO请求、实现异步非阻塞操作;
缓冲区直接在内存中进行数据的操作,减少了数据的拷贝。
二、自定义一个Netty服务端与客户端
服务端:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
public class SomeServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup parentGroup = new NioEventLoopGroup();//处理客户端连接请求
EventLoopGroup childGroup = new NioEventLoopGroup();//处理客户端服务请求
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
//给引导类配置两大线程组
.group(parentGroup, childGroup)
//设置Channel(通道)的类型为NIO
.channel(NioServerSocketChannel.class)
// 设置连接配置参数
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
/**
* 配置通道事件的处理器
*
* 注:handler()与childHandler()方法的区别
*/
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
/**
* 配置通道处理器:如编解码器、自定义处理器
*/
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new SomeServerHandler());
}
});
/**
* sync()方法是一个用于将异步操作转换为同步操作的方法
*/
ChannelFuture future = bootstrap.bind(8888).sync();
System.out.println("服务端启动成功:端口8888");
future.channel().closeFuture().sync();
} finally {
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
}
注:sync()方法 是ChannelFuture接口提供的一个同步方法。它用于将异步操作转换为同步操作,即等待操作完成并阻塞当前线程,直到操作完成或者发生异常。 当调用Netty中的一些异步操作方法时,通常会返回一个ChannelFuture对象,这个对象代表了未来可能发生的某个事件(如连接建立、消息发送等)。为了等待这个异步操作的结果,在需要同步等待的地方可以调用sync()方法。 使用sync()方法的代码会阻塞当前线程,直到对应的异步操作完成或发生异常。如果操作成功完成,即操作的状态为成功,sync()方法会立即返回。 如果操作发生异常,sync()方法会抛出对应的异常。 注:option()方法与childOption()方法区别 option()方法 1.用于配置服务器通道(ServerChannel)的一些选项。 2.可以设置一些影响服务器整体行为的参数,比如SO_BACKLOG(连接请求的最大队列长度)和SO_REUSEADDR(是否启用地址复用)等。 3.这些选项将应用于服务器监听Channel(通常是ServerSocketChannel)。 childOption()方法 1.用于配置每个客户端连接通道(Channel)的一些选项。 2.可以设置一些影响每个客户端连接行为的参数,比如TCP_NODELAY(是否启用Nagle算法)和SO_KEEPALIVE(是否开启TCP keep-alive)等。 3.这些选项将应用于每个客户端连接的Channel。 注:handler()与childHandler()方法的区别: handler()方法 1.用于配置服务器通道(ServerChannel)的处理器,这个处理器是对整个服务器通道的处理逻辑。 2.该方法在服务端启动时被调用,并且只会被调用一次。 3.通常用于配置一些全局的处理器,例如日志、编解码器等,对于每个连接到服务器的客户端,这些处理器都是共享的。 childHandler()方法 1.用于配置每个客户端连接的通道(Channel)的处理器,这个处理器是对每个客户端连接的独立处理逻辑。 2.该方法在每个客户端连接被接受后被调用,并且会为每个客户端连接创建一个新的处理器实例。 3.通常用于配置客户端连接独有的处理器,例如业务逻辑处理器、请求解析器等。 4.每次有新的客户端连接接入时,都会自动调用childHandler()方法来创建新的处理器实例,并将该实例添加到客户端连接的通道中。
客户端:
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.StringEncoder;
public class SomeClient {
public static void main(String[] args) {
NioEventLoopGroup loopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(loopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringEncoder());
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8888).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
loopGroup.shutdownGracefully();
}
}
}
三、什么是TCP的粘包和拆包?如何解决?
粘包:发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
拆包:拆包问题是指接收端只收到了部分数据,而非完整的数据。
解决办法:
使用Netty自带的解码器:
- LineBasedFrameDecoder : 发送端发送数据包的时候,每个数据包之间以换行符作为分隔,LineBasedFrameDecoder 的工作原理是它依次遍历 ByteBuf 中的可读字节,判断是否有换行符,然后进行相应的截取。
- DelimiterBasedFrameDecoder : 可以自定义分隔符解码器,LineBasedFrameDecoder 实际上是一种特殊的 DelimiterBasedFrameDecoder 解码器。
- FixedLengthFrameDecoder: 固定长度解码器,它能够按照指定的长度对消息进行相应的拆包。
- LengthFieldBasedFrameDecoder:按照指定长度分割。