传统IO
方式处理网络IO
数据:
服务端首先创建一个serverSocket
来监听8080
端口,然后创建一个线程,线程里不断调用阻塞方法serverSocket.accept()
获取新连接。
当获得新连接之后,为每一个新连接都创建一个新线程,这个线程负责从该连接中读取数据;然后以字节流方式读取数据。
下面是传统的IO
编程中的客户端实现。
// 创建一个传统网络IO(Socket),监听8080端口: ServerSocket serverSocket = new ServerSocket(8080); // 使用子线程获取连接: new Thread(() -> { while (true) { // 阻塞方法获取新连接: try { Socket socket = serverSocket.accept(); // 为每一个新连接都创建一个新线程,负责读取数据: new Thread(() -> { try { int len; byte[] data = new byte[1024]; // 采用字节流的方式读取数据: InputStream inputStream = socket.getInputStream(); while ((len = inputStream.read(data)) != -1) { System.out.println(new String(data, 0, len)); } } catch (IOException e) { System.out.println(e.getMessage()); } }).start(); } catch (IOException e) { System.out.println(e.getMessage()); } } }).start();
客户端的代码相对简单,连接上服务端8080
端口之后,每隔两秒,我们都向服务端写一个带有时间戳的hello!
。
IO
编程模型在客户端较少的情况下运行良好,但是对于客户端比较多的业务来说,单机服务端可能需要支撑成千上万个连接,IO
模型可能就不太合适了,我们来分析一下原因。
在上面的示例中,从服务端代码可以看到,在传统的IO
模型中,每个连接创建成功之后都需要由一个线程来维护,每个线程都包含一个while
死循环,那么1万个连接对应1万个线程,继而有1万个while
死循环,这就带来如下几个问题。
- 线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态,是非常严重的资源浪费,操作系统耗不起。
- 线程切换效率低下:单机
CPU
核数固定,线程爆炸之后操作系统频繁进行线程切换,应用性能急剧下降。 - 除了以上两个问题,在
IO
编程中,我们看到数据读写是以字节流为单位的。
为了解决这3
个问题,JDK 1.4
版本之后提出了NIO
。
new Thread(() -> { try { // 建立Socket连接: Socket socket = new Socket("127.0.0.1", 8080); while (true) { try { // 通过Socket向目标地址写数据: socket.getOutputStream().write((new Date() + ": hello !").getBytes()); Thread.sleep(2000); } catch (Exception e) { System.out.println(e.getMessage()); } } } catch (IOException e) { throw new RuntimeException(e); } }).start();