读完本篇文章将会了解以下问题
1. BIO模型概述
2. NIO模型概述
3. AIO模型概述
4. Netty
5.同步、异步、阻塞、非阻塞
---------------------------------------------------------------------------------------------------------------------------
阻塞IO网络模型:服务器启动后会进入阻塞状态,等待client连接,每一个client端连接上服务器后,服务器会为每一个客户端起一个线程来处理客户端的需求。服务器的accept()方法、服务器新起的thread中,Socket的read()和write()方法都是阻塞的。
BIO为什么要为每一个客户端起一个新线程处理,用一个线程不行么?
不行,因为如果server用一个线程来处理的话,假设当前client端传输一个特别大的文件,服务器端一直进入阻塞状态,等待文件传输结束,会导致其他client端无法连接到服务器。
NIO单线程模型:采用selector管理的轮询查询模式,selector每隔一段时间都去看一下client端有没有产生需要处理的消息(客户端连接请求、客户端发送数据请求、客户端下载数据请求),也就是说selector会同时管理client端的连接和通道内client端的读、写请求。
public class Server{
public static void main(String[] args) {
ServerSocketChannel ssc = ServerSocketChannel.open(); //ServerSocketChannel可支持同时读写
ssc.socket().bind(new InetSocketAddress("127.0.0.1",8888));
ssc.configureBlocking(false);//设定为非阻塞模型
Selector selector = Selector.open();//开启Selector
ssc.register(selector,SelectionKey.OP_ACCEPT);//注册对client端连接感兴趣
while(true){
selector.select();//等待有事件发生
Set Keys = selector.selectedKeys();
Iterator it =keys.iterator();
while(it.hasNext()){
SelectionKey key = it.next();
it.remove();
handle(key);//
}
}
}
}
在NIO单线程模型中有以下几点和BIO不同:
在NIO单线程模式中,只有一个线程负责处理client端的连接和通道内client端的读、写请求。在reactor模式中,将server端的单线程换成线程池(单线程管家+线程池工人模式)。
AIO模型不再需要轮询,每当有client端发出连接服务器请求的时候,由操作系统通知selector(大管家)去处理连接请求,selector为client和工人线程池中的一个线程创建通道。
Netty实现了对NIO、NIO(很少用)的封装,封装成了AIO API的样子。代码较NIO通俗易懂且好用,代码样例:
public class SimpleChatServer {
private int port;
public SimpleChatServer(int port){
this.port = port;
}
public void run() throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();//bossGroup用来接收进来的连接 parent接收者
EventLoopGroup workerGroup = new NioEventLoopGroup();//workerGroup用来处理已经被接收的连接 child客户端:WorkGroup
try{
ServerBootstrap sBootstrap = new ServerBootstrap(); //是一个启动NIO服务的辅助启动类
sBootstrap.group(bossGroup, workerGroup)//初始化线程池
.channel(NioServerSocketChannel.class)//指定通道channel的类型 NioServerSocketChannel:客户端
.childHandler(new SimpleChatServerInitializer())//设置自己编写的处理器
.option(ChannelOption.SO_BACKLOG, 128)//设置通道的选项参数
.childOption(ChannelOption.SO_KEEPALIVE, true);//设置通道的选项参数
System.out.println("远程认证服务器:Server start");
ChannelFuture future = sBootstrap.bind(port).sync();//绑定端口,开启连接
future.channel().closeFuture().sync();//等待服务器socket关闭
} finally {
workerGroup.shutdownGracefully();//Netty优雅退出机制,使线程池退出。
bossGroup.shutdownGracefully();//Netty优雅退出机制,使线程池退出。
System.out.println("远程认证服务器:Server end");
}
}
public static void main(String[] args) throws Exception {
new SimpleChatServer(8080).run();
}
}
public class SimpleChatClient {
private final int port;
private final String host;
public SimpleChatClient(String host, int port) {
this.host = host;
this.port = port;
}
public void run(String username,String password) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();//用来处理IO操作的多线程事件循环器
try {
Bootstrap bootstrap = new Bootstrap();// 是一个启动NIO服务的辅助启动类
bootstrap.group(group).channel(NioSocketChannel.class)//创建连接通道,加入线程池
.handler(new SimpleChatClientInitializer());
Channel channel = bootstrap.connect(host, port).sync().channel();//调用连接,返回通道
System.out.println("=============================================================================");
System.out.println("远程认证客户端:已创建远程认证客户端线程");
channel.writeAndFlush("username|"+username+"|password|"+password+"\r\n");//输入数据并将数据推栈
System.out.println("QQ远程认证客户端:发送至服务器的消息为 username="+username+" AND password="+password);
Thread.sleep(1500);//睡眠1500ms保证CSDN客户端响应完毕
System.out.println("远程认证客户端:已销毁当前认证客户端线程");
group.shutdownGracefully();//Netty优雅退出机制,使线程池退出。
} catch (Exception e) {
e.printStackTrace();//捕获并输出异常
}
}
}
对同一件事的观点不同,有时候可能表现为同步异步、也可能表现为阻塞非阻塞。
同步异步关注的是消息通信机制
阻塞非阻塞关注的是等待消息时的状态
什么意思呢?例:
有一个人和一个水壶,这个人首先点火(发消息),点完火后搬个板凳坐在旁边等着,还是同一个人处理水是否烧开这条消息(同步),不等到水开不去做别的事情(阻塞),这就是同步阻塞。
还是这个人,还是这个壶。点火(发消息),点完火这个人去打游戏了,每隔五分钟来看一眼水是否烧开(非阻塞),烧水期间还是由同一个人处理水是否烧开这条消息(同步),这就是同步非阻塞。
还是这个人,这个壶,这次不一样的是,这个人给壶进行了改装,烧开后壶会自动触发响铃处理。开始点火(发消息),烧水期间这个人一直盯着这个铃当(阻塞),水烧开后触发响铃操作,水是否烧开这条消息不是由这个人处理的,而是由铃铛处理的(异步),这就是异步阻塞。
这个人,这个壶,点火(发消息),烧水期间这个人该干嘛就干嘛(非阻塞),水是否烧开这条消息由铃铛处理(异步),这就是异步非阻塞。