网络编程中BIO和NIO的区别

先上结论

BIO中,每个请求因为要阻塞直到结果返回,所以比较好的解决是每个请求都需要一个线程来处理,但是线程又是他的制约条件。

NIO中,每个请求进来都会绑定到一个channel上,然后channel注册给一个 selector(多路选择器),多路选择器可以在channel有消息的时候进行处理。
保证了只有selector在轮询查找。最开始的 selector 使用的是 select方法,jdk使用了优化后的epoll,避免了select有数量限制(1024/2048),在感兴趣的channel上看是否有该类型数据出现,如果有,则调用处理,没有则继续睡眠,等待唤醒。

selector 使用reactor模式,将事件监听分离(读就绪/写就绪/链接就绪),对每个事件都可以进行独立的操作。

reactor模式其实就是:注册所有感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。注册监听(每个请求来都做) + 轮询选择(单线程处理) + 事件处理。

BIO

缺点:每来一个请求就需要一个线程来处理,线程太多容易造成系统不可用.最开始的Tomcat使用的就是BIO
优化:通过线程池来管理线程,但是造成新的缺点:请求太多时不能被处理的请求就回阻塞,等待。不能被处理。正因为限制了线程数量,如果发生大量并发请求,超过最大数量的线程就只能等待,直到线程池中的有空闲的线程可以被复用。而对Socket的输入流就行读取时,会一直阻塞。

服务端代码

package order.core.common;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.concurrent.ThreadPoolExecutor;import javax.sound.sampled.Line;public class BIOServer {private static final int PORT = 8000;public static void main(String[] args) throws IOException {ServerSocket serverSocket = null;Socket socket = null;ThreadPoolExecutor threadPoolExecutor = null;try {serverSocket = new ServerSocket(PORT);System.out.println("serverSocket 启动了...");while (true) {socket = serverSocket.accept();System.out.println("接受到socket...");new Thread(new MyThread(socket)).start();}} catch (Exception e) {// TODO: handle exception} finally {socket.close();serverSocket.close();}}
}class MyThread implements Runnable {private Socket socket;public MyThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {BufferedReader in = null;PrintWriter out = null;try {in = new BufferedReader(new InputStreamReader(socket.getInputStream()));out = new PrintWriter(socket.getOutputStream(), true);String expression;String result;// 通过BufferedReader读取一行// 如果已经读到输入流尾部,返回null,退出循环// 如果得到非空值,就尝试计算结果并返回if ((expression = in.readLine()) != null) {System.out.println("服务器收到消息:" + expression);out.print(5678978);}System.out.println("=========");} catch (Exception e) {e.printStackTrace();} finally {// 一些必要的清理工作if (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}in = null;}if (out != null) {out.close();out = null;}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}socket = null;}}}}

客户端代码

package order.core.common;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;public class BIOClient {private static final String IP = "127.0.0.1";// 默认的端口号private static int DEFAULT_SERVER_PORT = 8000;private static String DEFAULT_SERVER_IP = "127.0.0.1";public static void send(String expression) {send(DEFAULT_SERVER_PORT, expression);}public static void main(String[] args) {send("12345");}public static void send(int port, String expression) {System.out.println("发送消息为:" + expression);Socket socket = null;BufferedReader in = null;PrintWriter out = null;try {socket = new Socket(DEFAULT_SERVER_IP, port);in = new BufferedReader(new InputStreamReader(socket.getInputStream()));out = new PrintWriter(socket.getOutputStream(), true);out.println(expression);System.out.println("___结果为:" + in.readLine());} catch (Exception e) {e.printStackTrace();} finally {// 一下必要的清理工作if (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}in = null;}if (out != null) {out.close();out = null;}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}socket = null;}}}
}

NIO

优点:只需要打开Selector和 ServerSocketChannel 并开启非阻塞模式。然后只需要将ServerSocketChannel注册给selector即可。Tomcat后续默认全部使用NIO

监听的selector有四种:

public static final int OP_READ = 1 << 0;public static final int OP_WRITE = 1 << 2;public static final int OP_CONNECT = 1 << 3;public static final int OP_ACCEPT = 1 << 4;

服务端监听的是OP_ACCEPT。

客户端监听的是OP_CONNECT。

发送消息类的监听OP_READ。

2.1、简介

NIO我们一般认为是New I/O(也是官方的叫法),因为它是相对于老的I/O类库新增的(其实在JDK 1.4中就已经被引入了,但这个名词还会继续用很久,即使它们在现在看来已经是“旧”的了,所以也提示我们在命名时,需要好好考虑),做了很大的改变。但民间跟多人称之为Non-block I/O,即非阻塞I/O,因为这样叫,更能体现它的特点。而下文中的NIO,不是指整个新的I/O库,而是非阻塞I/O。

NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现。

新增的着两种通道都支持阻塞和非阻塞两种模式。

阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。

对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用NIO的非阻塞模式来开发。

下面会先对基础知识进行介绍。

2.2、缓冲区 Buffer

Buffer是一个对象,包含一些要写入或者读出的数据。

在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。

缓冲区实际上是一个数组,并提供了对数据结构化访问以及维护读写位置等信息。

具体的缓存区有这些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他们实现了相同的接口:Buffer。

2.3、通道 Channel

我们对数据的读取和写入要通过Channel,它就像水管一样,是一个通道。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作。

底层的操作系统的通道一般都是全双工的,所以全双工的Channel比流能更好的映射底层操作系统的API。

Channel主要分两大类:

SelectableChannel:用户网络读写 FileChannel:用于文件操作
后面代码会涉及的ServerSocketChannel和SocketChannel都是SelectableChannel的子类。

2.4、多路复用器 Selector

Selector是Java NIO 编程的基础。

Selector提供选择已经就绪的任务的能力:Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

一个Selector可以同时轮询多个Channel,因为JDK使用了epoll()代替传统的select实现,所以没有最大连接句柄1024/2048的限制。所以,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。

服务端代码

package order.core.common;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;public class Server {private static int DEFAULT_PORT = 8001;private static ServerHandle serverHandle;public static void start() {start(DEFAULT_PORT);}public static synchronized void start(int port) {if (serverHandle != null)serverHandle.stop();serverHandle = new ServerHandle(port);new Thread(serverHandle, "Server").start();}public static void main(String[] args) {start();}
}class ServerHandle implements Runnable {private Selector selector;private ServerSocketChannel serverChannel;private volatile boolean started;/*** 构造方法* * @param port*            指定要监听的端口号*/public ServerHandle(int port) {try {// 创建选择器selector = Selector.open();// 打开监听通道serverChannel = ServerSocketChannel.open();// 如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式serverChannel.configureBlocking(false);// 开启非阻塞模式// 绑定端口 backlog设为1024serverChannel.bind(new InetSocketAddress("localhost",port));// 监听客户端连接请求serverChannel.register(selector, SelectionKey.OP_ACCEPT);// 标记服务器已开启started = true;System.out.println("服务器已启动,端口号:" + port);} catch (IOException e) {e.printStackTrace();System.exit(1);}}public void stop() {started = false;}@Overridepublic void run() {// 循环遍历selectorwhile (started) {try {// 无论是否有读写事件发生,selector每隔1s被唤醒一次selector.select(1000);// 阻塞,只有当至少一个注册的事件发生的时候才会继续.// selector.select();Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> it = keys.iterator();SelectionKey key = null;while (it.hasNext()) {key = it.next();it.remove();try {handleInput(key);} catch (Exception e) {if (key != null) {key.cancel();if (key.channel() != null) {key.channel().close();}}}}} catch (Throwable t) {t.printStackTrace();}}// selector关闭后会自动释放里面管理的资源if (selector != null)try {selector.close();} catch (Exception e) {e.printStackTrace();}}private void handleInput(SelectionKey key) throws IOException {if (key.isValid()) {// 处理新接入的请求消息if (key.isAcceptable()) {ServerSocketChannel ssc = (ServerSocketChannel) key.channel();// 通过ServerSocketChannel的accept创建SocketChannel实例// 完成该操作意味着完成TCP三次握手,TCP物理链路正式建立SocketChannel sc = ssc.accept();// 设置为非阻塞的sc.configureBlocking(false);// 注册为读sc.register(selector, SelectionKey.OP_READ);}// 读消息if (key.isReadable()) {SocketChannel sc = (SocketChannel) key.channel();// 创建ByteBuffer,并开辟一个1M的缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);// 读取请求码流,返回读取到的字节数int readBytes = sc.read(buffer);// 读取到字节,对字节进行编解码if (readBytes > 0) {// 将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作buffer.flip();// 根据缓冲区可读字节数创建字节数组byte[] bytes = new byte[buffer.remaining()];// 将缓冲区可读字节数组复制到新建的数组中buffer.get(bytes);String expression = new String(bytes, "UTF-8");System.out.println("服务器收到消息:" + expression);// 处理数据String result = null;try {result = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa";} catch (Exception e) {result = "计算错误:" + e.getMessage();}// 发送应答消息doWrite(sc, result);}// 没有读取到字节 忽略// else if(readBytes==0);// 链路已经关闭,释放资源else if (readBytes < 0) {key.cancel();sc.close();}}}}// 异步发送应答消息private void doWrite(SocketChannel channel, String response) throws IOException {// 将消息编码为字节数组byte[] bytes = response.getBytes();// 根据数组容量创建ByteBufferByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);// 将字节数组复制到缓冲区writeBuffer.put(bytes);// flip操作writeBuffer.flip();// 发送缓冲区的字节数组channel.write(writeBuffer);// ****此处不含处理“写半包”的代码}
}

客户端代码

package order.core.common;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;public class Client {private static String DEFAULT_HOST = "127.0.0.1";private static int DEFAULT_PORT = 8001;private static ClientHandler clientHandle;public static void start(){start(DEFAULT_HOST,DEFAULT_PORT);}public static synchronized void start(String ip,int port){if(clientHandle!=null)clientHandle.stop();clientHandle = new ClientHandler(ip,port);new Thread(clientHandle,"Server").start();}//向服务器发送消息public static boolean sendMsg(String msg) throws Exception{clientHandle.sendMsg(msg);return true;}public static void main(String[] args) throws Exception{start();sendMsg("aaaaaaaaa");}
}class ClientHandler implements Runnable{private String host;private int port;private Selector selector;private static SocketChannel socketChannel;private static volatile boolean started;public ClientHandler(String ip,int port) {this.host = ip;this.port = port;try{//创建选择器selector = Selector.open();//打开监听通道socketChannel = SocketChannel.open();//如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式socketChannel.configureBlocking(false);//开启非阻塞模式socketChannel.connect(new InetSocketAddress("127.0.0.1", port));System.out.println(socketChannel.finishConnect());socketChannel.register(selector, SelectionKey.OP_CONNECT);started = true;}catch(IOException e){e.printStackTrace();System.exit(1);}}public void stop(){started = false;}@Overridepublic void run() {//循环遍历selectorwhile(started){try{//无论是否有读写事件发生,selector每隔1s被唤醒一次selector.select(1000);//阻塞,只有当至少一个注册的事件发生的时候才会继续.
//              selector.select();Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> it = keys.iterator();SelectionKey key = null;while(it.hasNext()){key = it.next();it.remove();try{handleInput(key);}catch(Exception e){if(key != null){key.cancel();if(key.channel() != null){key.channel().close();}}}}}catch(Exception e){e.printStackTrace();System.exit(1);}}}private void handleInput(SelectionKey key) throws IOException{if(key.isValid()){SocketChannel sc = (SocketChannel) key.channel();if(key.isConnectable()){if(sc.finishConnect());else System.exit(1);}//读消息if(key.isReadable()){//创建ByteBuffer,并开辟一个1M的缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);//读取请求码流,返回读取到的字节数int readBytes = sc.read(buffer);//读取到字节,对字节进行编解码if(readBytes>0){//将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作buffer.flip();//根据缓冲区可读字节数创建字节数组byte[] bytes = new byte[buffer.remaining()];//将缓冲区可读字节数组复制到新建的数组中buffer.get(bytes);String result = new String(bytes,"UTF-8");System.out.println("客户端收到消息:" + result);}//没有读取到字节 忽略
//              else if(readBytes==0);//链路已经关闭,释放资源else if(readBytes<0){key.cancel();sc.close();}}}}//异步发送消息private void doWrite(SocketChannel channel,String request) throws IOException{//将消息编码为字节数组byte[] bytes = request.getBytes();//根据数组容量创建ByteBufferByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);//将字节数组复制到缓冲区writeBuffer.put(bytes);//flip操作writeBuffer.flip();//发送缓冲区的字节数组channel.write(writeBuffer);//****此处不含处理“写半包”的代码}public void doConnect() throws IOException{System.out.println("==");try {System.out.println(socketChannel.isOpen());System.out.println(socketChannel.isConnected());} catch (Exception e) {// TODO: handle exception}}public void sendMsg(String msg) throws Exception{socketChannel.register(selector, SelectionKey.OP_READ);doWrite(socketChannel, msg);}
}

网络编程中BIO和NIO的区别相关推荐

  1. hadoop---Java 网络IO编程总结BIO、NIO、AIO

    转载请注明出处:http://blog.csdn.net/anxpp/article/details/51512200,谢谢! 本文会从传统的BIO到NIO再到AIO自浅至深介绍,并附上完整的代码讲解 ...

  2. java nio 李林峰_浅谈Java中BIO、NIO和AIO的区别和应用场景

    最近一直在准备面试,为了使自己的Java水平更上一个档次,拜读了李林峰老师的<Netty权威指南>,了解了Java关于IO的发展和最新的技术,真是受益匪浅,现在把我总结的关于BIO.NIO ...

  3. Java网络编程(6)NIO - Channel详解

    前言 NIO的三个核心组件:Buffer.Channel.Selector Java网络编程(4)NIO的理解与NIO的三个组件完成了大概的了解 Java网络编程(5)NIO - Buffer详解详细 ...

  4. Java的中BIO、NIO、AIO-1

    Java的中BIO.NIO.AIO-1 java  最近在项目中用到TCP通信来完成命令和运行结果的交互,用的是典型的TCP通信中的C/S架构,原因很简单:在业务需求低的环境下,这种架构简单.稳定还容 ...

  5. python网络通信传输的数据类型_Python网络编程中的网络数据和网络错误。

    上一个章节我们说的是套接字名和DNS.这篇文章我们主要解决下面问题. 我们在两台主机之间建立与关闭TCP流连接以及UDP数据报连接后.我们应该怎么准备我们需要传输的数据,该怎么对数据进行编码与格式化. ...

  6. 关于网络编程中MTU、TCP、UDP优化配置的一些总结

    首先要看TCP/IP协议,涉及到四层:链路层,网络层,传输层,应用层.  其中以太网(Ethernet)的数据帧在链路层 IP包在网络层 TCP或UDP包在传输层 TCP或UDP中的数据(Data)在 ...

  7. 关于网络编程中的一些小问题研究总结

    关于网络编程中的一些小问题研究总结 前言 一.关于"惊群问题" 二.关于socket网络编程中的reuseport 三.关于select.poll.epoll的原理探究 3.1 关 ...

  8. 网络编程中的关键问题总结

    网络编程中的关键问题总结 总结下网络编程中关键的细节问题,包含连接建立.连接断开.消息到达.发送消息等等: 连接建立 包括服务端接受 (accept) 新连接和客户端成功发起 (connect) 连接 ...

  9. python的功能模块_Python的功能模块[1] - struct - struct 在网络编程中的使用

    struct模块/ struct Module 在网络编程中,利用 socket 进行通信时,常常会用到 struct 模块,在网络通信中,大多数传递的数据以二进制流(binary data)存在.传 ...

最新文章

  1. R语言ggplot2可视化树状图、层次聚类系统树图、树状图根据给定的距离度量将相似点分组在一起、并根据点的相似性将它们组织成树状图链接起来(Hierarchical Dendrogram)
  2. 编译原理学习笔记一(待续)
  3. java 数据库提交,java.sql.Statement向数据库提交的语句不可以是SQL语句的()。
  4. CSS中使用flex弹性布局实现上下左右垂直居中排列并设置子元素之间的间距
  5. 你还在使用fastjson,可以尝试jsoncode
  6. 笔记本电脑电源已接通未充电_dell xps15 电源已接通 未充电 维修方法
  7. 类与对象的定义与使用小结 1114
  8. 运算符重载——关系运算符重载
  9. 计算机故障排除原则和方法
  10. html特殊符号的例子,CSS/HTML 开发中常用 特殊符号(常用字符实体) 相关扩展(琐碎知识点整理)...
  11. 高效扫频水处理器原理介绍
  12. Linux下用五笔输入法
  13. 前端踩坑(八)前端使用Moment 时间格式化错误
  14. vue子组件修改props传进来的值、回调函数
  15. Hadoop 命令操作大全
  16. HDU3085 Nightmare Ⅱ —— 双向BFS + 曼哈顿距离
  17. VMware内CentOS-7-Minimal的安装与配置(详细图文教程)
  18. mbedTLS(PolarSSL)简单思路和函数笔记(Client端)
  19. Isight与MATLAB联合仿真时出现:无法定位或初始化类(unsupported major minor version 52.0)
  20. 任正非谈鸿蒙系统失误,任正非谈鸿蒙:华为鸿蒙将比安卓快 60%

热门文章

  1. 八十六、从拓扑排序探究有向图
  2. 五十六、从高中碾转相除法、更相减损术算法谈起
  3. 十六、深入Java的数组(下篇)
  4. 三十七、细说Scrapy中的settings设置
  5. 隐马尔科夫模型 概念(上)
  6. 直播 | ICLR 2021论文解读:兼听则明,信而有征:可信多模态分类
  7. 东南大学周张泉:基于知识图谱的推理技术 | 实录·Guru Talk
  8. CVPR 2019 论文和开源项目合集(Papers with Code)
  9. HDU2091 空心三角形 水题
  10. 吉林省计算机等级二级,吉林省2019年9月计算机等级二级考试教程:二级MSOffice高级应用上机指导...