java nio 异步读取网络数据_Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO

Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO

Java 非阻塞 IO 和异步 IO

上一篇文章介绍了 Java NIO 中 Buffer、Channel 和 Selector 的基本操作,主要是一些接口操作,比较简单。

本文将介绍非阻塞 IO 和异步 IO,也就是大家耳熟能详的 NIO 和 AIO。很多初学者可能分不清楚异步和非阻塞的区别,只是在各种场合能听到异步非阻塞这个词。

本文会先介绍并演示阻塞模式,然后引入非阻塞模式来对阻塞模式进行优化,最后再介绍 JDK7 引入的异步 IO,由于网上关于异步 IO 的介绍相对较少,所以这部分内容我会介绍得具体一些。

希望看完本文,读者可以对非阻塞 IO 和异步 IO 的迷雾看得更清晰些,或者为初学者解开一丝丝疑惑也是好的。

阻塞模式 IO

我们已经介绍过使用 Java NIO 包组成一个简单的客户端-服务端网络通讯所需要的 ServerSocketChannel、SocketChannel 和 Buffer,我们这里整合一下它们,给出一个完整的可运行的例子(利用多线程伪阻塞IO):

public class Server {

public static void main(String[] args) throws IOException {

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

// 监听 8080 端口进来的 TCP 链接

serverSocketChannel.socket().bind(new InetSocketAddress(8080));

while (true) {

// 这里会阻塞,直到有一个请求的连接进来

SocketChannel socketChannel = serverSocketChannel.accept();

// 开启一个新的线程来处理这个请求,然后在 while 循环中继续监听 8080 端口

SocketHandler handler = new SocketHandler(socketChannel);

new Thread(handler).start();

}

}

}

这里看一下新的线程需要做什么,SocketHandler:

public class SocketHandler implements Runnable {

private SocketChannel socketChannel;

public SocketHandler(SocketChannel socketChannel) {

this.socketChannel = socketChannel;

}

@Override

public void run() {

ByteBuffer buffer = ByteBuffer.allocate(1024);

try {

// 将请求数据读入 Buffer 中

int num;

while ((num = socketChannel.read(buffer)) > 0) {

// 读取 Buffer 内容之前先 flip 一下

buffer.flip();

// 提取 Buffer 中的数据

byte[] bytes = new byte[num];

buffer.get(bytes);

String re = new String(bytes, "UTF-8");

System.out.println("收到请求:" + re);

// 回应客户端

ByteBuffer writeBuffer = ByteBuffer.wrap(("我已经收到你的请求,你的请求内容是:" + re).getBytes());

socketChannel.write(writeBuffer);

buffer.clear();

}

} catch (IOException e) {

IOUtils.closeQuietly(socketChannel);

}

}

}

最后,贴一下客户端 SocketChannel 的使用,客户端比较简单:

public class SocketChannelTest {

public static void main(String[] args) throws IOException {

SocketChannel socketChannel = SocketChannel.open();

socketChannel.connect(new InetSocketAddress("localhost", 8080));

// 发送请求

ByteBuffer buffer = ByteBuffer.wrap("1234567890".getBytes());

socketChannel.write(buffer);

// 读取响应

ByteBuffer readBuffer = ByteBuffer.allocate(1024);

int num;

if ((num = socketChannel.read(readBuffer)) > 0) {

readBuffer.flip();

byte[] re = new byte[num];

readBuffer.get(re);

String result = new String(re, "UTF-8");

System.out.println("返回值: " + result);

}

}

}

上面介绍的阻塞模式的代码应该很好理解:来一个新的连接,我们就新开一个线程来处理这个连接,之后的操作全部由那个线程来完成。

那么,这个模式下的性能瓶颈在哪里呢?

首先,每次来一个连接都开一个新的线程这肯定是不合适的。当活跃连接数在几十几百的时候当然是可以这样做的,但如果活跃连接数是几万几十万的时候,这么多线程明显就不行了。每个线程都需要一部分内存,内存会被迅速消耗,同时,线程切换的开销非常大。

其次,阻塞操作在这里也是一个问题。首先,accept() 是一个阻塞操作,当 accept() 返回的时候,代表有一个连接可以使用了,我们这里是马上就新建线程来处理这个 SocketChannel 了,但是,但是这里不代表对方就将数据传输过来了。所以,SocketChannel#read 方法将阻塞,等待数据,明显这个等待是不值得的。同理,write 方法也需要等待通道可写才能执行写入操作,这边的阻塞等待也是不值得的。

非阻塞 IO

说完了阻塞模式的使用及其缺点以后,我们这里就可以介绍非阻塞 IO 了。

非阻塞 IO 的核心在于使用一个 Selector 来管理多个通道,可以是 SocketChannel,也可以是 ServerSocketChannel,将各个通道注册到 Selector 上,指定监听的事件。

之后可以只用一个线程来轮询这个 Selector,看看上面是否有通道是准备好的,当通道准备好可读或可写,然后才去开始真正的读写,这样速

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值