1. NIO的核心组件
管道 选择器 缓冲区
一个线程处理多个不同的io操作
通道(Channel)
通常我们nio所有的操作都是通过通道开始的,所有的通道都会注册到统一个选择器(Selector)上实现管理,在通过选择器将数据统一写入到 buffer中。
缓冲区(Buffer)
Buffer本质上就是一块内存区,可以用来读取数据,也就先将数据写入到缓冲区中、在统一的写入到硬盘上。
选择器(Selector)
Selector可以称做为选择器,也可以把它叫做多路复用器,可以在单线程的情况下可以去维护多个Channel,也可以去维护多个连接;
核心思想: io多路复用
nio核心设计思想
非阻塞式
选择器 io多路复用原则
缓存区:提高读写效率
2.Redis是如何单线程能够非常好支持并发
首先Redis官方是没有windows版本的,只有redis版本
Redis的底层采用Nio中的多路IO复用的机制,能够非常好的支持这样的并发,从而保证线程安全问题;
但是Nio在不同的操作系统上实现的方式有所不同,在我们windows操作系统使用select实现轮训时间复杂度是为o(n),而且还存在空轮训的情况,效率非常低, 其次是默认对我们轮训的数据有一定限制,所以支持上万的tcp连接是非常难。
所以在linux操作系统采用epoll实现事件驱动回调,不会存在空轮训的情况,只对活跃的 socket连接实现主动回调这样在性能上有大大的提升,所以时间复杂度是为o(1)
注意:windows操作系统是没有epoll,只有linux系统才有epoll
所以为什么nginx、redis都能够非常高支持高并发,最终都是linux中的IO多路复用机制epoll
3.在nio中,服务器和客户端建立通道连接时,服务器端server socket channel、客户端socket channel是两个不同的概念,客户端是多个是吗?
在nio通讯中所有的客户端的数据必须通过channel管道发送到selector选择器统一管理和维护,
服务器端selector
选择器管理多个不同的客户端的socketchannel
,
服务器
接受channel就是serversocketchannel
。可以这么理解serversocketchannel就是管理所有客户端
的socketchannel
。
4. 为什么选择Netty框架 不选择Java原生NIO编程的原因
1.nio原生api非常复杂 学习成本很高
2.netty框架基于nio实现包装
5. Netty优点
API使用简单,开发门槛低;
◎ 功能强大,预置了多种编解码功能,支持多种主流协议;
◎ 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
◎ 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;
◎ 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
◎ 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入;
◎ 经历了大规模的商业应用考验,质量得到验证。Netty在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它已经完全能够满足不同行业的商业应用了。
正是因为这些优点,Netty逐渐成为了Java NIO编程的首选框架。
6. 为什么 netty创建我们服务器端时,要有 boss线程池 和 工作线程池 2个?
分工明确
boss线程池: 接受 客户端连接 接受请求
工作线程池: 处理客户端的连接
处理我们请求读写操作
7. 客户端 服务端code
public class NettyServer {
private static int port = 8080;
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
// NioServerSocketChannel标记当前属于服务器端 NioSocketChannel客户端
serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ServerHandler());
}
});
try {
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
System.out.println("服务器端启动成功" + port);
// 等待监听我们的请求
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
public class ServerHandler extends SimpleChannelInboundHandler {
// 获取数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {
// 接受我们的数据
ByteBuf byteBuf = (ByteBuf) o;
String request = byteBuf.toString(CharsetUtil.UTF_8);
System.out.println("request:" + request);
// 响应内容:
ctx.writeAndFlush(Unpooled.copiedBuffer("平均突破3万月薪", CharsetUtil.UTF_8));
}
}
短链接 客户端一请求完就关闭
public class socketClinet {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8080);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("mayi2".getBytes());
// while (true) {
//
// }
outputStream.close();
socket.close();
}
}
while 长链接
public class socketClinet {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8080);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("mayi2".getBytes());
while (true) {
}
// outputStream.close();
// socket.close();
}
}
public class NettyClient {
public static void main(String[] args) {
//创建nioEventLoopGroup
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress("127.0.0.1", 8080))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientHandler());
}
});
try {
// 发起同步连接
ChannelFuture sync = bootstrap.connect().sync();
sync.channel().closeFuture().sync();
} catch (Exception e) {
} finally {
group.shutdownGracefully();
}
}
}
8.什么 是粘包、拆包
原因的造成:
因为我们现在的tcp连接默认为长连接的形式实现通讯,发送请求之后不会立马关闭连接
客户端与服务器端建立连接,客户端发送一条消息,服务器端马上关闭 客户端与服务器端关闭连接
客户端与服务器端建立连接,客户端发送多条消息,服务器端马上关闭 客户端与服务器端关闭连接
什么是粘包:多次发送的消息,客户端一次合并读取 msg+msg
什么是拆包:第一次完整消息+第二次部分消息组合 、第二次缺失的消息
Msg Msg=msgmsg
Msg Msg=MsgM sg
9. 为什么会造成 粘包 拆包
tcp 和 缓冲区
tcp为了能够高性能传输,发送和接收都采用缓冲区
1.当我们的客户端发送的数据消息 < 服务器端缓冲区大小 会发生粘包
2.当我们的客户端发送的数据消息 > 服务器端缓冲区大小 会发生拆包
3.接受端不够及时的获取缓冲区的数据,也会产生粘包的问题
4.进行mss(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>mss的时候将发生拆包。
10. 解决粘包 拆包思路:
1.以固定的长度发送数据,到缓冲区
2.可以在数据之间设置一些边界(\n或者\r\n)
3.利用编码器LineBaseDFrameDecoder解决tcp粘包的问题 (自动会识别\n \r)
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringEncoder());
public class NettyClient {
public static void main(String[] args) {
//创建nioEventLoopGroup
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress("127.0.0.1", 8080))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new ClientHandler());
}
});
try {
// 发起同步连接
ChannelFuture sync = bootstrap.connect().sync();
sync.channel().closeFuture().sync();
} catch (Exception e) {
} finally {
group.shutdownGracefully();
}
}
}
public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
// 读取消息
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("resp12143243:" + msg.toString(CharsetUtil.UTF_8));
}
// 活跃通道可以发送消息
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 发送数据
for (int i = 0; i < 10; i++) {
ctx.writeAndFlush(Unpooled.copiedBuffer("mayikt\n", CharsetUtil.UTF_8));
}
}
}
public class NettyServer {
private static int port = 8080;
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
// NioServerSocketChannel标记当前属于服务器端 NioSocketChannel客户端
serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new ServerHandler());
}
});
try {
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
System.out.println("服务器端启动成功" + port);
// 等待监听我们的请求
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
public class ServerHandler extends SimpleChannelInboundHandler {
// 获取数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {
// 接受我们的数据
ByteBuf byteBuf = (ByteBuf) o;
String request = byteBuf.toString(CharsetUtil.UTF_8);
System.out.println("request:" + request);
// String[] split = request.split("\n");
// for (int i = 0; i <split.length ; i++) {
// ctx.writeAndFlush(Unpooled.copiedBuffer("平均突破3万月薪", CharsetUtil.UTF_8));
// }
// 响应内容:
ctx.writeAndFlush(Unpooled.copiedBuffer("好好学习\n", CharsetUtil.UTF_8));
}
}