JavaSE面试题(十五):网络IO流
Q:什么是BIO?
同步阻塞式IO,服务端创建一个ServerSocket,然后客户端用一个Socket去连接那个ServerSocket,然后ServerSocket接收到一个Socket的连接请求就创建一个Socket和一个线程去跟那个Socket进行通信。
public class BioServer { public static void main(String[] args) {
// 服务端开启一个端口进行监听 int port = 8080; ServerSocket serverSocket = null;
//服务端 Socket socket;
//客户端 InputStream in = null; OutputStream out = null; try { serverSocket = ew ServerSocket(port);
//通过构造函数创建ServerSocket,指定监听端口,如果端口合法且空闲,服务器就会监听成功 // 通过无限循环监听客户端连接,如果没有客户端接入,则会阻塞在accept操作 while (true) { System.out.println("Waiting for a new Socket to establish" + " ," + new Date().toString()); socket = serverSocket.accept();//阻塞 三次握手 in = socket.getInputStream(); byte[] buffer = ew byte[1024]; int length = 0; while ((length = in.read(buffer)) > 0)
{//阻塞 System.out.println("input is:" + new String(buffer, 0, length) + " ," + new Date().toString()); out = socket.getOutputStream(); out.write("success".getBytes()); System.out.println("Server end" + " ," + new Date().toString()); } } } catch (Exception e) { e.printStackTrace(); } finally { // 必要的清理活动 if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
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<SelectionKey> readySelectionKey = in_selector .selectedKeys(); Iterator<SelectionKey> 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\n"; 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<AsynchronousSocketChannel> 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(null, ew CompletionHandler<AsynchronousSocketChannel, Object>() { public void completed(AsynchronousSocketChannel result, Object attachment) { server.accept(null, this);// 再此接收客户端连接 handleWithCompletionHandler(result); } @Override public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); } }); } public void handleWithCompletionHandler(final AsynchronousSocketChannel channel) { try { final ByteBuffer buffer = ByteBuffer.allocate(4); final long timeout = 10L; channel.read(buffer, timeout, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() { @Override public void completed(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, null, this); } @Override public void failed(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内部定义的等待队列)。这也能节省不少的开销。
免责声明:内容来源于公开网络,若涉及侵权联系尽快删除!
【免责声明】本文部分系转载,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,如涉及作品内容、版权和其它问题,请在30日内与我们联系,我们会予以重改或删除相关文章,以保证您的权益!
Java开发高端课程免费试学
大咖讲师+项目实战全面提升你的职场竞争力
- 海量实战教程
- 1V1答疑解惑
- 行业动态分析
- 大神学习路径图
相关推荐
更多2024-04-08
2024-04-02
达内就业喜报
更多>Java开班时间
-
北京 丨 11月27日
火速抢座 -
上海 丨 11月27日
火速抢座 -
广州 丨 11月27日
火速抢座 -
兰州 丨 11月27日
火速抢座 -
杭州 丨 11月27日
火速抢座 -
南京 丨 11月27日
火速抢座 -
沈阳 丨 11月27日
火速抢座 -
大连 丨 11月27日
火速抢座 -
长春 丨 11月27日
火速抢座 -
哈尔滨 丨 11月27日
火速抢座 -
济南 丨 11月27日
火速抢座 -
青岛 丨 11月27日
火速抢座 -
烟台 丨 11月27日
火速抢座 -
西安 丨 11月27日
火速抢座 -
天津 丨 11月27日
火速抢座 -
石家庄 丨 11月27日
火速抢座 -
保定 丨 11月27日
火速抢座 -
郑州 丨 11月27日
火速抢座 -
合肥 丨 11月27日
火速抢座 -
太原 丨 11月27日
火速抢座 -
苏州 丨 11月27日
火速抢座 -
武汉 丨 11月27日
火速抢座 -
成都 丨 11月27日
火速抢座 -
重庆 丨 11月27日
火速抢座 -
厦门 丨 11月27日
火速抢座 -
福州 丨 11月27日
火速抢座 -
珠海 丨 11月27日
火速抢座 -
南宁 丨 11月27日
火速抢座 -
东莞 丨 11月27日
火速抢座 -
贵阳 丨 11月27日
火速抢座 -
昆明 丨 11月27日
火速抢座 -
洛阳 丨 11月27日
火速抢座 -
临沂 丨 11月27日
火速抢座 -
潍坊 丨 11月27日
火速抢座 -
运城 丨 11月27日
火速抢座 -
呼和浩特丨11月27日
火速抢座 -
长沙 丨 11月27日
火速抢座 -
南昌 丨 11月27日
火速抢座 -
宁波 丨 11月27日
火速抢座 -
深圳 丨 11月27日
火速抢座 -
大庆 丨 11月27日
火速抢座