BIO

/*** BIO网络通信为每次socket连接会新建一个线程,进行业务逻辑处理。服务端线程数和客户端线程数是1:1的正比关系*/public static void main(String[] args) throws Exception {//创建一个缓存线程池ExecutorService cachedThreadPool = Executors.newCachedThreadPool();//serverSocketServerSocket serverSocket = new ServerSocket(6666);while (true){final Socket socket = serverSocket.accept();//等待客户端进来log.info("连接到一个线程");// 加载到线程池里cachedThreadPool.execute(new Runnable() {@Overridepublic void run() {try {handler(socket);} catch (IOException e) {e.printStackTrace();}}});}}
BIO网络通信为每次socket连接会新建一个线程,进行业务逻辑处理。服务端线程数和客户端线程数是1:1的正比关系
JAVA NIO


三大核心部分
- Channel(通道): 本质上就是 提供与Socket相关的方法
- Buffer(缓冲区):socket的收发数据的缓冲区
使用Buffer读写数据一般遵循以下四个步骤:
- 写入数据到Buffer
- 调用flip()方法:将Buffer从写模式切换到读模式。以便在读模式下,可以读取到刚写入到buffer的所有数据
- 从Buffer中读取数据
- 调用clear()方法或者compact()方法,读取完后,清空缓冲区,以便让它可以再次被写
ByteBuffer,有3个实现
- HeapByteBuffer:ByteBuffer.allocate()它保存在JVM的堆空间中
- DirectByteBuffer:ByteBuffer.allocateDirect() JVM将使用malloc()在堆空间之外分配内存空间,你需要像C程序员一样,自己管理这个内存,必须自己分配和释放内存来防止内存泄漏
- MappedByteBuffer:
。
- Selector(多路复用器):管理通道选择已经就绪的通道 ,并且检测是否有事件发生,使用一个线程来维护。多个Channel会以事件的方式注册到同一个Selector
零copy技术:
MappedByteBuffer:FileChannel.map()设备控制器是不能通过 DMA 直接存储到用户空间,但是利用虚拟内存一个以上的虚拟地址可指向同一个物理内存地址这个特点,则可以把内核空间地址与用户空间的虚拟地址映射到同一个物理地址,这样,DMA 硬件(只能访问物理内存地址)就可以填充对内核与用户空间进程同时可见的缓冲区,但前提条件是,内核与用户缓冲区必须使用相同的页对齐,缓冲区的大小还必须是磁盘控制器块大小的倍数时。内存映射 I/O 使用文件系统建立从用户空间直到可用文件系统页的虚拟内存映射,有以下好处:
- 用户进程把文件数据当作内存,所以无需发布read()或write()系统调用。
- 当用户进程碰触到映射内存空间,页错误会自动产生,从而将文件数据从磁盘读进内存。如果用户修改了映射内存空间,相关页会自动标记为脏,随后刷新到磁盘,文件得到更新。
- 操作系统的虚拟内存子系统会对页进行智能高速缓存,自动根据系统负载进行内存管理。
- 数据总是按页对齐的,无需执行缓冲区拷贝。
大型文件使用映射,无需耗费大量内存,即可进行数据拷贝
MappedByteBuffer主要用在对大文件的读写或对实时性要求比较高的程序当中
DirectByteBuffer:虚拟内存
FileChannel:transferFrom() 和 transferTo() 两个抽象方法,它通过在通道和通道之间建立连接实现数据传输的,内部实现依赖底层操作系统对零拷贝的支持,即底层sendFile方法
- (1)DMA从拷贝至内核缓冲区
- (2)将数据的位置和长度的信息的描述符增加至内核空间(socket缓冲区)
(3)DMA将数据从内核拷贝至协议引擎 ```java /**
- @author liujinkun
- @Title: NioServer
- @Description: NIO 服务端
@date 2019/11/24 2:55 PM */ public class NioServer { public static void main(String[] args) throws IOException { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 轮询器,不同的操作系统对应不同的实现类 Selector selector = Selector.open(); // 绑定端口 serverSocketChannel.bind(new InetSocketAddress(8080)); serverSocketChannel.configureBlocking(false); // 将服务端channel注册到轮询器上,并告诉轮询器,自己感兴趣的事件是ACCEPT事件 serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
while(true){ // 调用轮询器的select()方法,是让轮询器从操作系统上获取所有的事件(例如:新客户端的接入、数据的写入、数据的写出等事件) selector.select(200); // 调用select()方法后,轮询器将查询到的事件全部放入到了selectedKeys中 Set
selectionKeys = selector.selectedKeys(); // 遍历所有事件 Iterator iterator = selectionKeys.iterator(); while(iterator.hasNext()){ SelectionKey key = iterator.next();// 如果是新连接接入if(key.isAcceptable()){SocketChannel socketChannel = serverSocketChannel.accept();System.out.println("有新客户端来连接");socketChannel.configureBlocking(false);// 有新的客户端接入后,就样将客户端对应的channel所感兴趣的时间是可读事件socketChannel.register(selector,SelectionKey.OP_READ);}// 如果是可读事件if(key.isReadable()){// 从channel中读取数据SocketChannel channel = (SocketChannel) key.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(1024);channel.read(byteBuffer);byteBuffer.flip();System.out.println(Charset.defaultCharset().decode(byteBuffer));// 读完了以后,再次将channel所感兴趣的时间设置为读事件,方便下次继续读。当如果后面要想往客户端写数据,那就注册写时间:SelectionKey.OP_WRITEchannel.register(selector,SelectionKey.OP_READ);}// 将SelectionKey从集合中移除,// 这一步很重要,如果不移除,那么下次调用selectKeys()方法时,又会遍历到该SelectionKey,这就造成重复处理了,而且最终selectionKeys这个集合的大小会越来越大。iterator.remove();
} } } }
/**
- @author liujinkun
- @Title: NioClient
- @Description: NIO客户端
@date 2019/11/24 2:55 PM */ public class NioClient {
private static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
public static void main(String[] args) throws IOException {SocketChannel socketChannel = SocketChannel.open();socketChannel.configureBlocking(false);boolean connect = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));// 因为连接是一个异步操作,所以需要在下面一行判断连接有没有完成。如果连接还没有完成,就进行后面的操作,会出现异常if(!connect){// 如果连接未完成,就等待连接完成socketChannel.finishConnect();}// 每个3秒向服务端发送一条消息executorService.scheduleAtFixedRate(() -> {try {String message = socketChannel.getLocalAddress().toString() + " Hello World";// 使用ByteBuffer进行数据发送ByteBuffer byteBuffer = ByteBuffer.wrap(message.getBytes());socketChannel.write(byteBuffer);} catch (IOException e) {e.printStackTrace();}}, 0, 3, TimeUnit.SECONDS);
} }
```
