手写BIO-NIO-AIO以及结构图
2021-12-13 21:57:07 0 举报
看图链接结构,结合总结和debug代码了解流程,最终自己手写一遍。BIO/NIO/AIO你就懂了
作者其他创作
大纲/内容
client
通道
每过来一个就创建一个线程来处理这个请求
服务端
架构模型
server
AIO(NIO 2.0)异步非阻塞 由操作系统完成后回调通知服务端程序启动线程去处理, 一般适用于连接数较多且连接时间较长的应用。AIO方式适用于连接数目多且连接比较长(重操作) 的架构,JDK7 开始支持
I/O多路复用底层一般用的Linux API(select,poll,epoll逐步升级)来实现。
客户端2
while循环一直监听链接事件
Selector多路复用器
也可以写给客户端来交互。
1、服务端初始化时,绑定IP端口,创建服务端的ServerSocketChannel,指定监听事件(只监听accept事件),注册到selector。2、客户端初始化时,绑定ip端口,创建客户端的SocketChannel,请求链接。3、selector的selector.select();获取符合selectionKeys集合的事件selectionKey,依次处理。4、判断是否是accept事件,是accept事件,会从selectionKey获取到服务端绑定的ServerSocketChannel,ServerSocketChannel进行accept,链接上请求链接的那个客户端。然后注册读事件到selector(用来下次读取客户端的请求内容)。结束本次处理。5、客户端与服务端进行了链接后,发出信息,创建SocketChannel,指定事件,并将对应的selectionKey注册到selector,让服务端读取。6、selector第一次循环获取到的事件全部处理完了,触发第二次循环时,获取到客户端的READ事件,服务端从selectionKey中获取到socketchannel,通过SocketChannel来进行READ。根据读取到的信息,服务端准备回复。因此又给这个socketChannel绑定个写事件,注册到selector上。
服务端单线程架构模型jdk1.4-select版本
jdk1.4
NIO(同步非阻塞io)多路复用器selector的select方法底层调用的Linux API:
1、服务端启动,绑定ip端口号,通道设置非阻塞,注册监听accept。selector.select();阻塞查询2、客户端启动,设置通道非阻塞,链接指定ip和端口,注册监听链接事件。3、客户端selector.select();阻塞查询,查询到连接事件,调用 socketChannel.finishConnect();完成链接。4、服务端监听到连接事件,通过accept获取通道SocketChannel socketChannel = serverSocketChannel.accept();配置非阻塞,注册读事件。5、客户端写消息给服务端:socketChannel.write(ByteBuffer.wrap(\"连接后,客户端写的消息\".getBytes()));设置通道为非阻塞,注册读事件,监听服务端的回复。6、服务端监听到读事件,读取客户端写的消息,socketChannel.read(byteBuffer)。服务端写回复socketChannel.write(ByteBuffer.wrap((\" server read :\"+s+\"client write:\").getBytes()));将读事件删除并新增selectionKey.interestOps(SelectionKey.OP_READ);7、服务端再次循环,调用selector.select();阻塞查询,等待新事件出现
debug跟踪记录
ServerSocket
accept(链接事件)
服务端处理线程2
NioClient
SelectionKeysSelectionKey集合,SelectionKey存放的就是channel和他感兴趣需要监听的事件的绑定关系。
运行server,telnet
package com.io.nio;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.SocketChannel;import java.util.Iterator;import java.util.Set;/** * @program: study_IO * @ClassName NioClient * @description:NIO客户端$ * @author: 李杰 * @create: 2021-12-10 16:41 * @Version 1.0 **/public class NioClient { //客户端的selector private Selector clientSelector; public static void main(String[] args) throws IOException { NioClient nioClient=new NioClient(); nioClient.init(\"127.0.0.1\
package com.io.nio;import java.io.IOException;import java.net.InetAddress;import java.net.InetSocketAddress;import java.net.ServerSocket;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;/** * @program: study_IO * @ClassName MyNioServer * @description:手写noi$ 自己再默写一遍 * @author: 李杰 * @create: 2021-12-11 22:25 * @Version 1.0 **/public class MyNioServer {// public final static ExecutorService pool= Executors.newFixedThreadPool(100); public static void main(String[] args) { try { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); InetAddress inetAddress=InetAddress.getByName(\"localhost\
注册
服务端处理线程1
accept方法是阻塞方法
逻辑图
客户端socket
ServerSocketChannel
每个客户端对应服务端的一个处理线程。
一次请求的流程总结
write()flush()
具体的代码逻辑
升级
MyNioServer
selector处理事件方式:1、如果全部都没有事件发生,则堵塞。(此时没有事件,不阻塞就是浪费资源)2、如果有事件发生,依次处理。处理事件都是单事件处理的,每次循环获取到的事件集合,依次遍历处理每一个事件,但不会处理那个事件触发的后面的事件。比如获取到的SelectionKeys里有三个客户端的事件,分别是accept,READ,处理完第一个客户端的链接事件后,会根据情况去注册读写事件的selectionKey到selector,等下次selector循环时去处理。然后处理第二个客户端的READ。等第二次循环时,获取到所有事件,才会处理上一轮那个客户端连接事件后面的读写。3、由于selector这样的处理方式,我们在用redis时,就要注意,不要一次读太大的数据,这样会导致堵塞。比如第一轮的读太别耗时,第一轮不结束,第二轮的循环是不会进行的。
socket
小知识点:这里可以自己点进行看一下,new String,String.valueOf的区别。因为new String方法才有对byte数组进行转换的实现方式。而String.valueOf只有对int、double、float、long、Boolean、char、char[]进行转换,如果不是上述的类型,都会当做Object来处理,Object类型的底层是直接进行了toString。
jdk1.5
package com.io.bio;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;/** * @program: study_IO * @ClassName BioClient * @description:bio的客户端$ * @author: 李杰 * @create: 2021-11-25 22:21 * @Version 1.0 **/public class BioClient { public static void main(String[] args) throws IOException { OutputStream outputStream=null; Socket socket=null; try { //指定IP和端口的socket监听 socket=new Socket(\"localhost\
read()
package com.io.bio;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.ServerSocket;import java.net.Socket;/** * @program: study_IO * @ClassName BioServer * @description:BIO服务端$ * @author: 李杰 * @create: 2021-11-25 23:02 * @Version 1.0 **/public class BioServer { public static void main(String[] args) { try { //启动服务,端口10086 ServerSocket serverSocket=new ServerSocket(10086); //循环获取连接 while (true){ System.out.println(\"等待连接\"); //获取到客户端请求acceptSocket Socket acceptSocket = serverSocket.accept(); System.out.println(\"与客户端成功建立连接\
非阻塞体现在哪?1、accept和read只有在有这个事件发生时才会调用。阻塞的方法也就不阻塞了。2、selector的select方法只会在无任何事件发生时才会阻塞,同时也是不浪费资源。3、处理事件,每次只处理当前的事件,不会等当前事件后续的事件,后续事件等下一轮再处理。
1、服务端和客户端都是通过channel进行直接沟通,但是channel每次都是从selector那里获取到的,这里selector相当于一个注册中心调度中心。2、服务端启动时监听accept事件,注册到selector。3、当selector获取并处理这个accept事件时,就可以在服务端通过ServerSocketChannel与客户端建立连接。4、建立连接后。客户端才会创建SocketChannel并绑定事件封装到selectionKey,注册到selector上。5、等下次再循环获取到这个注册的新事件时,服务端就可以通过这个事件selectionKey获取到客户端的SocketChannel,并进行相应的读写操作。如果还有后面的读写,再次将这个SocketChannel绑定相应的事件封装成selectionKey,注册到selector上,等待下次循环到处理即可。
再通过inputStream读取服务端返回的信息
epoll:无最大链接。底层是hash表。获取事件是事件通知的方式,当有事件准备发生,会触发系统的钩子方法,主动通知selector来获取事件,时间复杂度O(1)
SocketChannel
select:最大连接数有上限底层是数组,获取时是依次线性遍历获取事件,时间复杂度O(n)
BIO(同步阻塞io)
serverSocket
buffer
发送后阻塞等待消息。
客户端1
poll:无最大链接数。底层是链表。获取事件也是依次线性遍历,时间复杂度O(n)
获取到通道最好先将其设置为非阻塞,如果在调用connect或者register后设置就会出现问题。
创建socket,并指定端口号进行连接
在win系统,可以打开telnet,通过telnet与服务端进行交互。
package com.io.aio;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.AsynchronousSocketChannel;import java.util.concurrent.Future;/** * @program: study_IO * @ClassName AIOClient * @description:AIO客户端$ * @author: 李杰 * @create: 2021-12-13 20:53 * @Version 1.0 **/public class AIOClient { public static void main(String[] args) { try { AsynchronousSocketChannel asySocketChannel=AsynchronousSocketChannel.open(); //链接后,要调get方法:get方法的作用是等待连接完成再往下走 asySocketChannel.connect(new InetSocketAddress(\"127.0.0.1\
创建服务端socket,绑定监听的端口号
客户端
NIO服务端代码
channel注册到selector的是:channel和他感兴趣的事件
连接上后,通过outputStream向服务端写数据,通过flush刷到内存
注:架构模型、逻辑图这些名词只是我本人的学习总结方便理解定的。
0 条评论
回复 删除
下一页