IO编程
我们简化下场景:客户端每隔两秒发送一个带有时间戳的"hello world"给服务端,服务端收到之后打印。
为了方便演示,下面例子中,服务端和客户端各一个类,把这两个类拷贝到你的IDE中,先后运行 IOServer.java
和IOClient.java
可看到效果。
下面是传统的IO编程中服务端实现
IOServer.java
/**
* @author 闪电侠
*/
public class IOServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8000);
// (1) 接收新连接线程
new Thread(() -> {
while (true) {
try {
// (1) 阻塞方法获取新的连接
Socket socket = serverSocket.accept();
// (2) 每一个新的连接都创建一个线程,负责读取数据
new Thread(() -> {
try {
byte[] data = new byte[1024];
InputStream inputStream = socket.getInputStream();
while (true) {
int len;
// (3) 按字节流方式读取数据
while ((len = inputStream.read(data)) != -1) {
System.out.println(new String(data, 0, len));
}
}
} catch (IOException e) {
}
}).start();
} catch (IOException e) {
}
}
}).start();
}
}
server端首先创建了一个serverSocket
来监听8000端口,然后创建一个线程,线程里面不断调用阻塞方法 serversocket.accept();
获取新的连接,见(1),当获取到新的连接之后,给每条连接创建一个新的线程,这个线程负责从该连接中读取数据,见(2),然后读取数据是以字节流的方式,见(3)。
下面是传统的IO编程中客户端实现
IOClient.java
/**
* @author 闪电侠
*/
public class IOClient {
public static void main(String[] args) {
new Thread(() -> {
try {
Socket socket = new Socket("127.0.0.1", 8000);
while (true) {
try {
socket.getOutputStream().write((new Date() + ": hello world").getBytes());
socket.getOutputStream().flush();
Thread.sleep(2000);
} catch (Exception e) {
}
}
} catch (IOException e) {
}
}).start();
}
}
客户端的代码相对简单,连接上服务端8000端口之后,每隔2秒,我们向服务端写一个带有时间戳的 "hello world"。
上面的demo,从服务端代码中我们可以看到,在传统的IO模型中,每个连接创建成功之后都需要一个线程来维护,每个线程包含一个while死循环,那么1w个连接对应1w个线程,继而1w个while死循环,这就带来如下几个问题:
- 线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费,操作系统耗不起
- 线程切换效率低下:单机cpu核数固定,线程爆炸之后操作系统频繁进行线程切换,应用性能急剧下降。
- 除了以上两个问题,IO编程中,我们看到数据读写是以字节流为单位,效率不高。
为了解决这三个问题,JDK在1.4之后提出了NIO。