目录
1. BIO
1.1 bio的服务端:
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket();
ss.bind(new InetSocketAddress("127.0.0.1", 8888));
while (true) {
Socket s = ss.accept(); // 阻塞
new Thread(() -> handle(s)).start();
}
}
static void handle(Socket s) {
try {
byte[] bytes = new byte[1024];
int len = s.getInputStream().read(bytes); // 阻塞
System.out.println(new String(bytes, 0, len));
s.getOutputStream().write(bytes, 0, len); // 阻塞
s.getOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.2 bio的客户端:
public class Client {
public static void main(String[] args) throws IOException {
for (int i = 0; i < 500; i++) {
sendRequest("这是第【" + i + "】个请求");
}
}
public static void sendRequest(String str) throws IOException {
Socket s = new Socket("127.0.0.1", 8888);
s.getOutputStream().write(str.getBytes());
s.getOutputStream().flush();
System.out.println("等待server");
byte[] bytes = new byte[1024];
int len = s.getInputStream().read(bytes);
System.out.println(new String(bytes, 0, len));
s.close();
}
}
1.3 bio总结:
一个客户端连接到服务器端,服务器端就要新建立一个线程来处理数据的输入和输出
- 客户端连上来可能会阻塞;服务器端和客户端的读写过程也可能阻塞。
2. 单线程NIO
2.1 服务器端代码:
public class Server {
public static void main(String[] args) throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8888));
ssc.configureBlocking(false);
// 已经绑定了本机的 8888端口
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // 阻塞,如果有注册事情发生了,往下走。
Set<SelectionKey> keys = selector.selectedKeys();//获取所有的 发生的事情的key
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
handle(key);
}
}
}
private static void handle(SelectionKey key) {
if (key.isAcceptable()) {
// 如果是客户端 想连接。
try {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); // 获取这个key对应的channel
SocketChannel sc = ssc.accept(); // accept,接收连接。
sc.configureBlocking(false); // 设置非阻塞
} catch (Exception e) {
e.printStackTrace();
}
} else if (key.isReadable()) {
// 如果客户端想向你发送数据。你需要读数据。
SocketChannel sc = null;
try {
sc = (SocketChannel) key.channel(); // 获取这个key对应的channel
ByteBuffer buffer = ByteBuffer.allocate(512);
buffer.clear();
int len = sc.read(buffer);//读。
if (len != -1) {
System.out.println(new String(buffer.array(), 0, len));
}
ByteBuffer bufferToWrite = ByteBuffer.wrap("hello client".getBytes());
sc.write(bufferToWrite); // 写
} catch (IOException e) {
e.printStackTrace();
} finally {
if (sc != null) {
try {
sc.close(); //关闭channel
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
2.2 总结单线程NIO
selector像一个大管家,设置一些他感兴趣的事情:客户端连接、读数据、写数据。进行轮询,如果有感兴趣的事情发生了,获取这些事情的key,挨个获取channel亲自去处理。然后再次看看有没有感兴趣的事情发生…
- 跟BIO有什么差别呢?BIO是一个客户端连接,就创建一个线程去处理,然而读数据,写数据是阻塞的,所以大部分时间,各个线程是没事干的状态。然而用NIO,只用一个线程,需要读或者写数据的时候,这个线程采取处理。就相当于,一个人干了很多人的活。【避免了很多人在那里傻等着活的情况】
3. 多线程NIO
3.1 概念
- 单线程NIO的大管家必须事事躬亲,但是如果与一个客户的读写时间很长的话,其他客户端的操作就在等待。
- 所以引出了NIO的多线程模型。设置了一个线程池,一个大管家,领着一群工人,大管家轮询,看看有没有事情需要处理,如果有的话,就交给闲着的工人。如果所有工人都在忙,可以等待或者创建一个新的工人。
- 这个模型和BIO不一样。BIO是一个工人服务一个客户。而这里,工人都是螺丝钉,哪里需要哪里搬。
4. AIO
4.1 代码
- aio的accept方法不是阻塞的了,accept代码运行了,就往下走,也不轮询了。
- 和NIO的区别在于,需不需要轮询。NIO需要轮询,有感兴趣的事件,就一起处理,然后再次轮询… 而AIO直接让OS持有处理代码的引用,有事件发生,OS直接去运行那段代码。
- accept方法==将一段代码给OS,告诉他,如果有客户端要连,你就运行这段代码就行。==这是观察者的设计模式/回调函数/钩子函数。
- OS有了这段方法的引用,当事件发生,回调一下就行了。不用大管家去轮询了。
4.2 AIO理解
我有一个主线程,然后运行代码。给OS勾上一段代码,如果有客户端需要运行,就运行那段代码。然后再将一段代码勾到建立的通道上,如果有数据读写,就运行第二个勾上的程序。主程序接着往下运行,该干嘛干嘛去。
4.3 NIO AIO Netty 三者的关系
- netty封装了NIO。
- java运行在Linux上时,NIO和AIO的操作底层都用的是epoll(轮询模型),所以直接用NIO就行,AIO多封装了一下,结果到底层都一样
- 而Netty就是封装了NIO,因为NIO的Api不好用。
- Netty的API类似AIO的API
- windows上面NIO和AIO的底层实现不同。AIO底层用的是事件模型。
- 所以在windows上的AIO效率最高。但是服务器一般都是linux,所以Netty封装了NIO。
5. Netty
6. 同步 异步 阻塞 非阻塞
- 同步和异步是消息通信机制中的概念。一直需要你处理,就是同步。否则就是异步。
- 阻塞和非阻塞是等待消息时的状态:需不需要傻等着。
- BIO是同步阻塞
- NIO是同步非阻塞
- AIO是异步非阻塞(直接给OS钩子函数,不用再关心了,该干啥干啥)
7. 参考
- 马士兵老师的公开课
- https://www.bilibili.com/video/BV1i4411p7kk?t=8521