面试-简述基础中剩下的部分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中的代码(部分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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


能力越强,责任越大。

实事求是,严谨细致。