知识点
-
nio 下 I/O 阻塞与非阻塞实现
-
SocketChannel 介绍
-
I/O 多路复用的原理
-
事件选择器与 SocketChannel 的关系
-
事件监听类型
-
字节缓冲 ByteBuffer 数据结构
场景
接着上一篇中的站点访问问题,如果我们需要并发访问10个不同的网站,我们该如何处理?
在上一篇中,我们使用了java.net.socket
类来实现了这样的需求,以一线程处理一连接的方式,并配以线程池的控制,貌似得到了当前的最优解。可是这里也存在一个问题,连接处理是同步的,也就是并发数量增大后,大量请求会在队列中等待,或直接异常抛出。
为解决这问题,我们发现元凶处在“一线程一请求”上,如果一个线程能同时处理多个请求,那么在高并发下性能上会大大改善。这里就借住 JAVA 中的 nio 技术来实现这一模型。
nio 的阻塞实现
关于什么是 nio,从字面上理解为 New IO,就是为了弥补原本 I/O 上的不足,而在 JDK 1.4 中引入的一种新的 I/O 实现方式。简单理解,就是它提供了 I/O 的阻塞与非阻塞的两种实现方式(当然,默认实现方式是阻塞的。)。
下面,我们先来看下 nio 以阻塞方式是如何处理的。
建立连接
有了上一篇 socket 的经验,我们的第一步一定也是建立 socket 连接。只不过,这里不是采用 new socket()
的方式,而是引入了一个新的概念 SocketChannel
。它可以看作是 socket 的一个完善类,除了提供 Socket 的相关功能外,还提供了许多其他特性,如后面要讲到的向选择器注册的功能。
建立连接代码实现:
// 初始化 socket,建立 socket 与 channel 的绑定关系
SocketChannel socketChannel = SocketChannel.open();
// 初始化远程连接地址
SocketAddress remote = new InetSocketAddress(this.host, port);
// I/O 处理设置阻塞,这也是默认的方式,可不设置
socketChannel.configureBlocking(true);
// 建立连接
socketChannel.connect(remote);
获取 socket 连接
因为是同样是 I/O 阻塞的实现,所以后面的关于 socket 输入输出流的处理,和上一篇的基本相同。唯一差别是,这里需要通过 channel 来获取 socket 连接。
-
获取 socket 连接
Socket socket = socketChannel.socket();
-
处理输入输出流
PrintWriter pw = getWriter(socketChannel.socket());
BufferedReader br = getReader(socketChannel.socket());
完整示例
package com.jason.network.mode.nio;
import com.jason.network.constant.HttpConstant;
import com.jason.network.util.HttpUtil;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
public class NioBlockingHttpClient {
private SocketChannel socketChannel;
private String host;
public static void main(String[] args) throws IOException {
for (String host: HttpConstant.HOSTS) {
NioBlockingHttpClient client = new NioBlockingHttpClient(host, HttpConstant.PORT);
client.request();
}
}
public NioBlockingHttpClient(String host, int port) throws IOException {
this.host = host;
socketChannel = SocketChannel.open();
socketChannel.socket().setSoTimeout(5000);//设置最大超时时间
SocketAddress remote = new InetSocketAddress(this.host, port);
this.socketChannel.connect(remote);
}
public void request() throws IOException {
PrintWriter pw = getWriter(socketChannel.socket());
BufferedReader br = getReader(socketChannel.socket());
pw.write(HttpUtil.compositeRequest(host));
pw.flush();
String msg;
while ((msg = br.readLine()) != null){
System.out.println(msg);
}
}
private PrintWriter getWriter(Socket socket) throws IOException {
OutputStream out = socket.getOutputStream();
return new PrintWriter(out);
}
private BufferedReader getReader(Socket socket) throws IOException {
InputStream in = socket.getInputStream();
return new BufferedReader(new InputStreamReader(in));
}