1、同步堵塞BIO
![](https://i-blog.csdnimg.cn/blog_migrate/2b21b3a60022dd366a2828b15412b438.png)
缺点:
package yc.fen.dou.nettyStu.bio;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8000);
while (true) {
System.out.println("等待连接。。");
Socket clientSocket = serverSocket.accept(); //堵塞位置1
System.out.println("有客户端连接了。。");
handler(clientSocket);
}
}
private static void handler(Socket clientSocket) throws IOException {
byte[] bytes = new byte[1024];
System.out.println("准备read。。");
//接收客户端的数据,阻塞方法,没有数据可读时就阻塞
int read = clientSocket.getInputStream().read(bytes);
System.out.println("read完毕。。");
// if (read != ‐1) {
System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
/// }
clientSocket.getOutputStream().write("HelloClient".getBytes());
clientSocket.getOutputStream().flush();
}
}
以上是传统的bio模型实现代码,
缺点:如果这个请求handler(clientSocket)没有处理完,serverSocket.accept()会一直处于堵塞状态
package yc.fen.dou.nettyStu.bio;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8000);
while (true) {
System.out.println("等待连接。。");
Socket clientSocket = serverSocket.accept(); //堵塞位置1
System.out.println("有客户端连接了。。");
// handler(clientSocket);
new Thread(new Runnable() {
@Override
public void run() {
try {
handler(clientSocket); //堵塞位置2
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
private static void handler(Socket clientSocket) throws IOException {
byte[] bytes = new byte[1024];
System.out.println("准备read。。");
//接收客户端的数据,阻塞方法,没有数据可读时就阻塞
int read = clientSocket.getInputStream().read(bytes);
System.out.println("read完毕。。");
// if (read != ‐1) {
System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
/// }
clientSocket.getOutputStream().write("HelloClient".getBytes());
clientSocket.getOutputStream().flush();
}
}
优点:handler(clientSocket)采用异步处理,serverSocket.accept()不会进行堵塞
缺点:当请求量很大的时候,频繁创建线程会消耗过多内存资源
2、同步非堵塞NIO
同步非阻塞,服务器实现模式为一个线程可以处理多个请求(连接),客户端发送的连接请求都会注册到多路复用器selector上,多路复用
package yc.fen.dou.nettyStu.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class NioServer {
// 保存客户端连接
static List<SocketChannel> channelList = new ArrayList<>();
public static void main(String[] args) throws IOException, InterruptedException {
// 创建NIO ServerSocketChannel,与BIO的serverSocket类似
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
// 设置ServerSocketChannel为非阻塞
serverSocket.configureBlocking(false); //false为接受客户请求不堵塞
System.out.println("服务启动成功");
while (true) {
// 非阻塞模式accept方法不会阻塞,否则会阻塞
// NIO的非阻塞是由操作系统内部实现的,底层调用了linux内核的accept函数
SocketChannel socketChannel = serverSocket.accept();
if (socketChannel != null) {
// 如果有客户端进行连接
System.out.println("连接成功");
// 设置SocketChannel为非阻塞
socketChannel.configureBlocking(false);//false为处理客户请求不堵塞
// 保存客户端连接在List中
channelList.add(socketChannel);
}
// 遍历连接进行数据读取
Iterator<SocketChannel> iterator = channelList.iterator();
while (iterator.hasNext()) {
SocketChannel sc = iterator.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
// 非阻塞模式read方法不会阻塞,否则会阻塞
int len = sc.read(byteBuffer);
// 如果有数据,把数据打印出来
if (len > 0) {
System.out.println("接收到消息:" + new String(byteBuffer.array()));
} else if (len == Integer.parseInt("-1")) {
// 如果客户端断开,把socket从集合中去掉
iterator.remove();
System.out.println("客户端断开连接");
}
}
}
}
}
优点:1、在接受客户端请求的时候,不会堵塞,在处理任务的时候也不会堵塞
缺点:在处理任务的时候也不会堵塞,但是因为是单线程,只能一个一个进行处理,比如有一万个请求, Iterator<SocketChannel> iterator = channelList.iterator(); channelList有1w个记录, 其中只有10个请求数据准备好了,那10个已经准备的请求可能被其他没有准备好的请求堵塞了,因为要一个一个执行
3、同步非堵塞nio采用多路复用器(selector)
![](https://i-blog.csdnimg.cn/blog_migrate/16553affd5ae4ef8cb947d10eb623cfb.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a70c375a95cf348c9f0aeae6090f6242.png)
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NioSelectorServer {
public static void main(String[] args) throws IOException, InterruptedException {
// 创建NIO ServerSocketChannel
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
// 设置ServerSocketChannel为非阻塞
serverSocket.configureBlocking(false);
// 打开Selector处理Channel,即创建epoll
Selector selector = Selector.open();
// 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务启动成功");
while (true) {
// 阻塞等待需要处理的事件发生
selector.select();
// 获取selector中注册的全部事件的 SelectionKey 实例
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 遍历SelectionKey对事件进行处理
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 如果是OP_ACCEPT事件,则进行连接获取和事件注册
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
// 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接成功");
} else if (key.isReadable()) {
// 如果是OP_READ事件,则进行读取和打印
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = socketChannel.read(byteBuffer);
// 如果有数据,把数据打印出来
if (len > 0) {
System.out.println("接收到消息:" + new String(byteBuffer.array()));
} else if (len == Integer.parseInt("-1")) {
// 如果客户端断开连接,关闭Socket
System.out.println("客户端断开连接");
socketChannel.close();
}
}
//从事件集合里删除本次处理的key,防止下次select重复处理
iterator.remove();
}
}
}
}
SelectionKey一共有四个事件:
客户端的SocketChannel支持 OP_CONNECT, OP_READ, OP_WRITE三个操作。服务端ServerSocketChannel只支持OP_ACCEPT操作,在服务端由ServerSocketChannel的accept()方法产生的SocketChannel只支持OP_READ, OP_WRITE操作。
client/Server | SocketChannel/ServerSocketChannel | OP_ACCEPT | OP_CONNECT | OP_WRITE | OP_READ |
---|---|---|---|---|---|
client | SocketChannel | Y | Y | Y | |
server | ServerSocketChannel | Y | |||
server | SocketChannel | Y | Y |
OP_ACCEPT就绪条件:当收到一个客户端的连接请求时,该操作就绪。这是ServerSocketChannel上唯一有效的操作。
OP_CONNECT就绪条件:只有客户端SocketChannel会注册该操作,当客户端调用SocketChannel.connect()时,该操作会就绪。
OP_READ就绪条件:该操作对客户端和服务端的SocketChannel都有效,当OS的读缓冲区中有数据可读时,该操作就绪。
OP_WRITE就绪条件:该操作对客户端和服务端的SocketChannel都有效,当OS的写缓冲区中有空闲的空间时(大部分时候都有),该操作就绪。
nio也是一个线程,但是不会堵塞,根据现在epoll模型会循环就绪的连接,读,取事件
假如现在有一万个socket连接:
package yc.fen.dou.nettyStu.nio.selectorStu; public class NioSelectorServer { public static void main(String[] args) throws IOException, InterruptedException { // 创建NIO ServerSocketChannel ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.socket().bind(new InetSocketAddress(9000)); // 设置ServerSocketChannel为非阻塞 serverSocket.configureBlocking(false); // 打开Selector处理Channel,即创建epoll Selector selector = Selector.open(); // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣图灵诸葛老师 serverSocket.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务启动成功"); while (true) { //轮询去就绪队列中获取所有的就绪事件 // 阻塞等待需要处理的事件发生 //轮询就绪的连接,读,取事件,如果有1w个连接,其中有1q个就绪事件,一次select调用会获取这些1q个就绪的事件 selector.select(); // 获取selector中注册的全部事件的 SelectionKey 实例 Set<SelectionKey> selectionKeys = selector.selectedKeys(); System.out.println("------keySize------"+selectionKeys.size()); Iterator<SelectionKey> iterator = selectionKeys.iterator(); // 遍历SelectionKey对事件进行处理 while (iterator.hasNext()) { SelectionKey key = iterator.next(); if (key.isAcceptable()) { //handAccptable() } else if (key.isReadable()) { //handRead() }else if (key.isWritable()) { //handWrite() } } } } }
现在来了解一下操作系统epoll模型
理解select,poll,epoll技术的演变:
select
|
poll
|
epoll(jdk 1.5及以上)
| |
操作方式
|
遍历
|
遍历
|
回调
|
底层实现
|
数组
|
链表
|
哈希表
|
IO效率
|
每次调用都进行线 性遍历,时间复杂 度为O(n)
|
每次调用都进行 线性遍历,时间
复杂度为O(n)
|
事件通知方式,每当有IO事件 就绪,系统注册的回调函数就
会被调用,时间复杂度O(1)
|
最大连接
|
有上限
|
无上限
|
无上限
|
4、异步非堵塞AIO(NIO2)
其底层也是用epoll模型
适用:由操作系统完成后回调通知服务端程序启动线程去处理, 一般适用于连接数较多且连接时间较长的应用
5、netty 底层使用NIO多路复用模型
为什么Netty使用NIO而不是AIO?
![](https://i-blog.csdnimg.cn/blog_migrate/4e52a615fa8c03a006364b91bf775e22.png)
server端:
package yc.fen.dou.nettyStu.nio.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;
public class NettyServer {
public static void main(String[] args) throws Exception {
//创建两个线程组bossGroup和workerGroup, 含有的子线程NioEventLoop的个数默认为cpu核数的两倍
// bossGroup只是处理连接请求 ,真正的和客户端业务处理,会交给workerGroup完成
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务器端的启动对象
ServerBootstrap bootstrap = new ServerBootstrap();
//使用链式编程来配置参数
bootstrap.group(bossGroup, workerGroup) //设置两个线程组
.channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器的通道实现
// 初始化服务器连接队列大小,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。
// 多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {//创建通道初始化对象,设置初始化参数
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//对workerGroup的SocketChannel设置处理器
ch.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("netty server start。。");
//绑定一个端口并且同步, 生成了一个ChannelFuture异步对象,通过isDone()等方法可以判断异步事件的执行情况
//启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕
ChannelFuture cf = bootstrap.bind(9000).sync();
//给cf注册监听器,监听我们关心的事件
/* cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (cf.isSuccess()) {
System.out.println("监听端口9000成功");
} else {
System.out.println("监听端口9000失败");
}
}
});*/
//对通道关闭进行监听,closeFuture是异步操作,监听通道关闭
// 通过sync方法同步等待通道关闭处理完毕,这里会阻塞等待通道关闭完成
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package yc.fen.dou.nettyStu.nio.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* 自定义Handler需要继承netty规定好的某个HandlerAdapter(规范)
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 读取客户端发送的数据
*
* @param ctx 上下文对象, 含有通道channel,管道pipeline
* @param msg 就是客户端发送的数据
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务器读取线程 " + Thread.currentThread().getName());
//Channel channel = ctx.channel();
//ChannelPipeline pipeline = ctx.pipeline(); //本质是一个双向链接, 出站入站
//将 msg 转成一个 ByteBuf,类似NIO 的 ByteBuffer
ByteBuf buf = (ByteBuf) msg;
System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
}
/**
* 数据读取完毕处理方法
*
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("HelloClient", CharsetUtil.UTF_8);
ctx.writeAndFlush(buf);
}
/**
* 处理异常, 一般是需要关闭通道
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
client端:
package yc.fen.dou.nettyStu.nio.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyClient {
public static void main(String[] args) throws Exception {
//客户端需要一个事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建客户端启动对象
//注意客户端使用的不是 ServerBootstrap 而是 Bootstrap
Bootstrap bootstrap = new Bootstrap();
//设置相关参数
bootstrap.group(group) //设置线程组
.channel(NioSocketChannel.class) // 使用 NioSocketChannel 作为客户端的通道实现
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
//加入处理器
channel.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("netty client start");
//启动客户端去连接服务器端
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9000).sync();
//对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
package yc.fen.dou.nettyStu.nio.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当客户端连接服务器完成就会触发该方法
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("HelloServer", CharsetUtil.UTF_8);
ctx.writeAndFlush(buf);
}
//当通道有读取事件时会触发,即服务端发送数据给客户端
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("收到服务端的消息:" + buf.toString(CharsetUtil.UTF_8));
System.out.println("服务端的地址: " + ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
同步跟堵塞的理解
1、同步和异步,在请求io的时候,客户线程是否会被堵塞 ,如果会被堵塞,则为同步,如果不会被堵塞,则为异步
2、堵塞跟非堵塞
堵塞:就是客户线程在传输数据的时候,服务器端在接收数据的过程是否会被堵塞
非堵塞:就是客户线程在传输数据的时候,服务器不会进行堵塞
1、非堵塞轮询的方式,就是轮询所有的请求,看哪个已经准备好数据了
2、非堵塞多路复用selector方式,就是操作系统帮我们准备好 已经准备好数据的事件,然后选择器就会去处理已经准备好的事件