说明
从 JDK1.4 开始, Java增加了新的 io 模式,nio(new IO), nio在底层采用了新的处理方式,极大地提高了IO的效率。
nio与原来的io对比:
io | nio |
---|---|
ServerSocket | ServerSocketChannel |
Socket | SocketChannel |
要理解 NioSocket 的使用必须要理解三个概念:
- Buffer
- Channel
- Selector
类比快递:
- Buffer(要送的货物)
- Channel(送货员)
- Selector(中转站的分拣员)
NioSocket 中服务端的处理过程分5步:
- 创建 ServerSocketChannel 并设置相应参数
- 创建 Selector 并注册到 ServerSocketChannel 上。
- 调用 Selector 的 select 方法等待请求。
- Selector 接收请求后使用 selectedKeys 返回 SelectionKey 集合。
- 使用 SelectionKey 获取到 Channel、Selector 和操作类型并进行具体操作。
实例
NIOServer.java
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.nio.charset.Charset;
import java.util.Iterator;
/**
* @author : xiao
* @date : 17/10/23 下午3:14
* @description :
*/
public class NIOServer {
public static void main(String[] args) throws Exception {
// 创建ServerSocketChannel,监听8080端口
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(8080));
// 设置为非阻塞模式
ssc.configureBlocking(false);
// 为ssc注册选择器
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
// 创建处理器
Handler handler = new Handler(1024);
while (true) {
// 等待请求,每次等待阻塞3s,超过3s后线程继续向下运行,如果传入0或者不传参数将一直阻塞
if (selector.select(3000) == 0) {
System.out.println("等待请求超时。。。");
continue;
}
System.out.println("处理请求。。。");
// 获取待处理的SelectionKey
Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
while (keyIter.hasNext()) {
SelectionKey key = keyIter.next();
try {
// 接收到连接请求时
if (key.isAcceptable()) {
handler.handleAccept(key);
}
// 读数据
if (key.isReadable()) {
handler.handleRead(key);
}
} catch (IOException ex) {
keyIter.remove();
continue;
}
// 处理完后,从待处理的SelectionKey迭代器中移除当前所使用的key
keyIter.remove();
}
}
}
private static class Handler {
private int bufferSize = 1024;
private String localCharset = "UTF-8";
public Handler() {
}
public Handler(int bufferSize) {
this(bufferSize, null);
}
public Handler(String LocalCharset) {
this(-1, LocalCharset);
}
public Handler(int bufferSize, String localCharset) {
if (bufferSize > 0) {
this.bufferSize = bufferSize;
}
if (localCharset != null) {
this.localCharset = localCharset;
}
}
public void handleAccept(SelectionKey key) throws IOException {
SocketChannel sc = ((ServerSocketChannel) key.channel()).accept();
sc.configureBlocking(false);
sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
}
public void handleRead(SelectionKey key) throws IOException {
// 获取channel
SocketChannel socketChannel = (SocketChannel) key.channel();
// 获取buffer并重置
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.clear();
// 没有读到内容则关闭
if (socketChannel.read(buffer) == -1) {
socketChannel.close();
} else {
// 将buffer转换为读状态
buffer.flip();
// 将buffer中接收到的值按localCharset格式编码后保存到receivedString
String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
System.out.println("received from client: " + receivedString);
// 返回数据给客户端
StringBuilder sendString = new StringBuilder();
// 响应报文首行,200表示处理成功
sendString.append("HTTP/1.1 200 OK\r\n");
sendString.append("Content-Type:text/html;charset=" + localCharset + "\r\n");
// 报文头结束后加一个空行
sendString.append("\r\n");
sendString.append("<!DOCTYPE html>");
sendString.append("<html lang=\"en\">");
sendString.append("<head>");
sendString.append(" <meta charset=\"UTF-8\">");
sendString.append(" <title>Title</title>");
sendString.append("</head>");
sendString.append("<body>");
sendString.append(" <h4>hello world! </h4>");
sendString.append("</body>");
sendString.append("</html>");
buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset));
socketChannel.write(buffer);
// 关闭Socket
socketChannel.close();
}
}
}
}