惊蛰
在线0
文章17
标签23
分类5
面试-简述基础中剩下的部分BIO,NIO,粘包/拆包,多路复用等

面试-简述基础中剩下的部分BIO,NIO,粘包/拆包,多路复用等

简述基础中剩下的部分BIO,NIO,粘包/拆包,多路复用等

BIO,NIO和AIO

简介:

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

AIO(NIO.2):异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

BIO


阻塞IO,一个请求对应一个线程,线程是阻塞的,面向流的,只能单向读写

缺点:很显而易见,阻塞式IO、弹性伸缩能力强、多线程耗费资源

场景:BIO方式适用于连接数目比较小且固定的场景,这种方式对服务器资源要求比较高,并发局限于应用中。

NIO


同步非阻塞,服务器实现一个连接一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4之后开始支持。每个客户端请求都会注册到Selector(多路复用器),是面向缓冲的, 可以双向读写

相对于BIO来说优点就是非阻塞节约资源了

缺点:NIO类库繁琐,复杂,可靠性不高,空轮询,会导致cpu100%

场景:NIO适合处理连接数目特别多,但是连接比较短(轻操作)的场景Jetty,Mina,ZooKeeper等都是基于java nio实现

NIO在JAVA中的代码(部分)

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * 阻塞点this.selector.select();
 * 轮询器虽然是一个线程内部也是线程池
 */
public class NioSocket {
    private Selector selector;   //通道管理器(管理器)

    /**
     * 初始化Channel并绑定端口
     * @param port
     * @throws IOException
     */
    public void initServer(int port) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);  //非阻塞
        serverChannel.socket().bind(new InetSocketAddress(port));

        this.selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器已启动...");
    }

    /**
     * 监听轮询器
     * @throws IOException
     */
    public void listenSelector() throws IOException {
        //轮询监听selector
        while (true){
            //等待客户连接
            //select模型,多路复用
            //this.selector.select();   //在这里会阻塞,无论是连接还是客户端发送数据还是客户端关闭,这里都会触发
            this.selector.selectNow();   //这里不阻塞会立即执行
            Iterator<SelectionKey> iteKey = this.selector.selectedKeys().iterator();
            while (iteKey.hasNext()){
                SelectionKey key = iteKey.next();
                iteKey.remove();  //移除,防止重复处理
                //处理请求
                handler(key);
            }
        }
    }

    /**
     * 处理客户端请求
     * @param key
     */
    private void handler(SelectionKey key) throws IOException {
        if (key.isAcceptable()){  //处理连接请求
            //处理客户端连接请求事件
            ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
            SocketChannel socketChannel = serverChannel.accept();
            //接受客户端发送的信息时,需要给通道设置读权限
            socketChannel.configureBlocking(false);
            socketChannel.register(selector,SelectionKey.OP_READ);
        }else if(key.isReadable()){   //处理读请求
            //处理读事件
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int readData = socketChannel.read(buffer);
            if (readData>0){
                String info  = new String(buffer.array(),"GBK").trim();
                System.out.println("服务端收到数据: "+Thread.currentThread()+info);
            }else {
                System.out.println("客户端关闭了...");
                key.cancel();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        NioSocket nio = new NioSocket();
        nio.initServer(8888);
        nio.listenSelector();
    }
}

AIO(NIO.2)

异步非阻塞IO,在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。
https://blog.csdn.net/weixin_43122090/article/details/105462088

多路复用(Channel)

  • Channel是一个对象,可以通过它读取和写入数据。通常我们都是将数据写入包含一个或者多个字节的缓冲区,然后再将缓存区的数据写入到通道中,将数据从通道读入缓冲区,再从缓冲区获取数据。
  • Channel 类似于原I/O中的流(Stream),但有所区别:
  • 流是单向的,通道是双向的,可读可写。
  • 流读写是阻塞的,通道可以异步读写。

NIO是JDK1.4提供的操作,他的流还是流,没有改变,服务器实现的还是一个连接一个线程,当是:客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4之后开始支持。


上图清晰的描述了他们之间的关系

拆包/粘包

为什么会出现粘包

分两个方面解释,一方面是发送方原因
我们都知道,TCP默认用的是Nagle算法,只做两件事:
1)只有上一分组得到确认,才会发送下一分组
2)收集多个小分组,在一个确认到来时一起发送
所以,正是这个算法造成了粘包现象

另一方面就是接收方原因,
TCP接收到分组时,并不会立即发送至应用层进行处理,实际上,TCP会将分组放入缓存里,然后应用程序主动从缓存中读取,这样,如果TCP接受的速度远大于应用读取缓存的速度,多个包就会被存至缓存中,应用程序读的时候,就会读到好几个包首尾粘在一起

什么时候需要处理粘包现象

1)如果发送方发送的是同一数据的不同部分,比如一个比较大的文件分成多个碎片传递过来,这时候不需要处理粘包
2)但是如果多个分组并不相干,甚至是并列关系,我们就一定要处理粘包问题了,不然就会丢弃分组或者分组错误的情况出现了

如果处理粘包现象
1)发送方我们可以通过关闭Nagle算法来解决,使用TCP_NODELAY来关闭该算法
2)接收方的,接收方没办法处理粘包现象,我们只能通过应用层面来解决改问题
3)应用层面,可以在发送分组的时候,将数据的长度一并发送,或者通过规定固定符号起始固定符号结束(该方法简单好用)

以上关于粘包内容摘抄整理自https://blog.csdn.net/prefect_boy/article/details/100035865


能力越强,责任越大。

实事求是,严谨细致。

惊蛰新博客

本文作者:惊蛰
本文链接:https://jingzhe.xyz/2020/11/30/%E7%AE%80%E8%BF%B0%E5%9F%BA%E7%A1%80%E4%B8%AD%E5%89%A9%E4%B8%8B%E7%9A%84%E9%83%A8%E5%88%86BIONIO%E7%B2%98%E5%8C%85%E6%8B%86%E5%8C%85%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E7%AD%89/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可