天天看点

java 那个流可以装resultset发给socket_「015期」JavaSE面试题(十五):网络IO流

java 那个流可以装resultset发给socket_「015期」JavaSE面试题(十五):网络IO流
java 那个流可以装resultset发给socket_「015期」JavaSE面试题(十五):网络IO流
java 那个流可以装resultset发给socket_「015期」JavaSE面试题(十五):网络IO流

2020年百日百更原创Java面试题库之往期回顾

【000期】Java最全面试题库思维导图

【001期】JavaSE面试题(一):面向对象

【002期】JavaSE面试题(二):基本数据类型与访问修饰符

【003期】JavaSE面试题(三):JavaSE语法(1)

【004期】JavaSE面试题(四):JavaSE语法(3)

【005期】JavaSE面试题(五):String类

【006期】JavaSE面试题(六):泛型

【007期】JavaSE面试题(七):异常

【008期】JavaSE面试题(八):集合之List

【009期】JavaSE面试题(九):集合之Set

【010期】JavaSE面试题(十):集合之Map

【011期】JavaSE面试题(十一):多线程(1)

【012期】JavaSE面试题(十二):多线程(2)

【013期】JavaSE面试题(十三):多线程(3)

【014期】JavaSE面试题(十四):基本IO流

开篇介绍

大家好,我是Java面试题库的提裤姐,今天这篇是JavaSE系列的第十五篇,主要总结了Java中的IO流的问题,IO流分为两篇来讲,这篇是第二篇,主要是网络IO流,在后续,会沿着第一篇开篇的知识线路一直总结下去,做到日更!如果我能做到百日百更,希望你也可以跟着百日百刷,一百天养成一个好习惯。

Q:

什么是BIO?

同步阻塞

式IO,服务端创建一个ServerSocket,然后客户端用一个Socket去连接那个ServerSocket,然后ServerSocket接收到一个Socket的连接请求就创建一个Socket和一个线程去跟那个Socket进行通信。

Q:

什么是NIO?

同步非阻塞

包括Selector,这是多路复用器,selector会不断轮询注册的channel,如果某个channel上发生了读写事件,selector就会将这些channel获取出来,我们通过SelectionKey获取有读写事件的channel,就可以进行IO操作。一个Selector就通过一个线程,就可以轮询成千上万的channel,这就意味着你的服务端可以接入成千上万的客户端。

public class NioDemo implements Runnable {
public int id = 100001;
public int bufferSize = 2048;

@Override
public void run {
init;
}

public void init {
try {
// 创建通道和选择器
ServerSocketChannel socketChannel = ServerSocketChannel.open;
Selector selector = Selector.open;
InetSocketAddress inetSocketAddress = new InetSocketAddress(
InetAddress.getLocalHost, 4700);
socketChannel.socket.bind(inetSocketAddress);
// 设置通道非阻塞 绑定选择器
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_ACCEPT).attach(
id++);
System.out.println("Server started .... port:4700");
listener(selector);

} catch (Exception e) {

}
}

public void listener(Selector in_selector) {
try {
while (true) {
Thread.sleep(1 * 1000);
in_selector.select; // 阻塞 直到有就绪事件为止
Set readySelectionKey = in_selector
.selectedKeys;
Iterator it = readySelectionKey.iterator;
while (it.hasNext) {
SelectionKey selectionKey = it.next;
// 判断是哪个事件
if (selectionKey.isAcceptable) {// 客户请求连接
System.out.println(selectionKey.attachment
+ " - 接受请求事件");
// 获取通道 接受连接,
// 设置非阻塞模式(必须),同时需要注册 读写数据的事件,这样有消息触发时才能捕获
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey
.channel;
serverSocketChannel
.accept
.configureBlocking(false)
.register(
in_selector,
SelectionKey.OP_READ
| SelectionKey.OP_WRITE).attach(id++);
System.out
.println(selectionKey.attachment + " - 已连接");
// 下面这种写法是有问题的 不应该在serverSocketChannel上面注册
/*
* serverSocketChannel.configureBlocking(false);
* serverSocketChannel.register(in_selector,
* SelectionKey.OP_READ);
* serverSocketChannel.register(in_selector,
* SelectionKey.OP_WRITE);
*/
}
if (selectionKey.isReadable) {// 读数据
System.out.println(selectionKey.attachment
+ " - 读数据事件");
SocketChannel clientChannel = (SocketChannel) selectionKey.channel;
ByteBuffer receiveBuf = ByteBuffer.allocate(bufferSize);
clientChannel.read(receiveBuf);
System.out.println(selectionKey.attachment
+ " - 读取数据:" + getString(receiveBuf));
}
if (selectionKey.isWritable) {// 写数据
System.out.println(selectionKey.attachment
+ " - 写数据事件");
SocketChannel clientChannel = (SocketChannel) selectionKey.channel;
ByteBuffer sendBuf = ByteBuffer.allocate(bufferSize);
String sendText = "hello";
sendBuf.put(sendText.getBytes);
sendBuf.flip; //写完数据后调用此方法
clientChannel.write(sendBuf);
}
if (selectionKey.isConnectable) {
System.out.println(selectionKey.attachment
+ " - 连接事件");
}
// 必须removed 否则会继续存在,下一次循环还会进来,
// 注意removed 的位置,针对一个.next remove一次
it.remove;
}
}
} catch (Exception e) {
System.out.println("Error - " + e.getMessage);
e.printStackTrace;
}
}
/**
* ByteBuffer 转换 String
*
* @param buffer
* @return
*/
public static String getString(ByteBuffer buffer) {
String string = "";
try {
for (int i = 0; i < buffer.position; i++) {
string += (char) buffer.get(i);
}
return string;
} catch (Exception ex) {
ex.printStackTrace;
return "";
}
}
}
           

Q:

什么是AIO?

异步非阻塞

每个连接发送过来的请求,都会绑定一个buffer,然后通知操作系统去异步完成读,此时你的程序是会去干别的事儿的,等操作系统完成数据读取之后,就会回调你的接口,给你操作系统异步读完的数据。

public class AIOServer {

public final static int PORT = 9888;
private AsynchronousServerSocketChannel server;

public AIOServer throws IOException {
server = AsynchronousServerSocketChannel.open.bind(
new InetSocketAddress(PORT));
}

public void startWithFuture throws InterruptedException,
ExecutionException, TimeoutException {
while (true) {// 循环接收客户端请求
Future future = server.accept;
AsynchronousSocketChannel socket = future.get;// get 是为了确保 accept 到一个连接
handleWithFuture(socket);
}
}

public void handleWithFuture(AsynchronousSocketChannel channel) throws InterruptedException, ExecutionException, TimeoutException {
ByteBuffer readBuf = ByteBuffer.allocate(2);
readBuf.clear;

while (true) {// 一次可能读不完
//get 是为了确保 read 完成,超时时间可以有效避免DOS攻击,如果客户端一直不发送数据,则进行超时处理
Integer integer = channel.read(readBuf).get(10, TimeUnit.SECONDS);
System.out.println("read: " + integer);
if (integer == -1) {
break;
}
readBuf.flip;
System.out.println("received: " + Charset.forName("UTF-8").decode(readBuf));
readBuf.clear;
}
}

public void startWithCompletionHandler throws InterruptedException,
ExecutionException, TimeoutException {
server.accept(,
new CompletionHandler {
public voidcompleted(AsynchronousSocketChannel result, Object attachment) {
server.accept(, this);// 再此接收客户端连接
handleWithCompletionHandler(result);
}

@Override
public voidfailed(Throwable exc, Object attachment) {
exc.printStackTrace;
}
});
}

public voidhandleWithCompletionHandler(final AsynchronousSocketChannel channel) {
try {
final ByteBuffer buffer = ByteBuffer.allocate(4);
final long timeout = 10L;
channel.read(buffer, timeout, TimeUnit.SECONDS, , new CompletionHandler {
@Override
public voidcompleted(Integer result, Object attachment) {
System.out.println("read:" + result);
if (result == -1) {
try {
channel.close;
} catch (IOException e) {
e.printStackTrace;
}
return;
}
buffer.flip;
System.out.println("received message:" + Charset.forName("UTF-8").decode(buffer));
buffer.clear;
channel.read(buffer, timeout, TimeUnit.SECONDS, , this);
}
@Override
public voidfailed(Throwable exc, Object attachment) {
exc.printStackTrace;
}
});
} catch (Exception e) {
e.printStackTrace;
}
}
public static void main(String args[]) throws Exception {
// new AIOServer.startWithFuture;
new AIOServer.startWithCompletionHandler;
Thread.sleep(100000);
}
}
           

Q:

什么是epoll?

把一个磁盘文件映射到内存里来,然后把映射到内存里来的数据通过socket发送出去 。

有一种mmap技术,也就是内存映射,直接将磁盘文件数据映射到内核缓冲区,这个映射的过程是基于DMA引擎拷贝的,同时用户缓冲区是跟内核缓冲区共享一块映射数据的,建立共享映射之后,就不需要从内核缓冲区拷贝到用户缓冲区了。

光是这一点,就可以避免一次拷贝,但是这个过程中还是会用户态切换到内核态去进行映射拷贝,接着再次从内核态切换到用户态, 建立用户缓冲区和内核缓冲区的映射 ,

接着把数据通过Socket发送出去,还是要再次切换到内核态 ,

接着直接把内核缓冲区里的数据拷贝到Socket缓冲区里去,然后再拷贝到网络协议引擎里,发送出去就可以了,最后切换回用户态 。

减少一次拷贝,但是并不减少切换次数,一共是4次切换,3次拷贝

Q:

什么是零拷贝技术?

linux提供了sendfile,也就是零拷贝技术

这个零拷贝技术,就是先从用户态切换到内核态,在内核态的状态下,把磁盘上的数据拷贝到内核缓冲区,同时从内核缓冲区拷贝一些 offset和length到Socket缓冲区;接着从内核态切换到用户态,从内核缓冲区直接把数据拷贝到网络协议引擎里去

同时从Socket缓冲区里拷贝一些offset和length到网络协议引擎里去,但是这个offset和length的量很少,几乎可以忽略

只要2次切换,2次拷贝

Q:

说一下select,poll,epoll的区别?

select

poll

实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。

epoll

也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

select

poll

每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而

epoll

只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

祝大家都能拿到心仪的offer!

java 那个流可以装resultset发给socket_「015期」JavaSE面试题(十五):网络IO流
java 那个流可以装resultset发给socket_「015期」JavaSE面试题(十五):网络IO流

等风也等你

文章都看完了

java 那个流可以装resultset发给socket_「015期」JavaSE面试题(十五):网络IO流

不点个

java 那个流可以装resultset发给socket_「015期」JavaSE面试题(十五):网络IO流