您的位置:首页 > 其它

NIO-结合Selector设计一个简易WebServer

2017-11-04 17:17 585 查看
参考资料

建议大家找找这种资料来了解下什么是IO,什么是NIO,NIO的理念是什么。

一点小结论

Channel、Buffer、Selector是NIO的核心API。

Channel类似旧IO的流,可读可写,读写都是面向Buffer。



Channel通常来自文件或网络:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
……
RandomAccessFile randomAccessFile = new RandomAccessFile("/Users/zhengwei/lanqiao/ConditionOperator.java", "rw");
FileChannel channel = randomAccessFile.getChannel();


Selector是非阻塞的核心,不像以前所有的读写都要阻塞,如果要并发就必须开多线程来监听每一路的IO。Selector有一种注册机制,首先读写事件不阻塞,只需向Selector注册感兴趣的事件,这样CPU可以去干点别的事,只需定期轮询Selector即可。



简易Server的实现思路

打开Server通道,并做初始化
initServerChannel


实例化一个Selector,对Server通道的连接就绪事件感兴趣:

//打开一个选择器
Selector selector = Selector.open();// Selector的创建
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// 对channel的连接就绪感兴趣


轮询Selector状态,如果出现连接事件,实例化一个客户端套接字连接通道,将这个通道上的可读事件注册给Selector;

if (key.isAcceptable()) {
dealAcceptable(serverSocketChannel, selector);
}


如果出现可读事件,读取客户端的HTTP连接报文,写回一段HTTP响应报文;

else if (key.isReadable()) {// 有读的事儿没?
dealReadable(selector, key);
}


如果出现可写事件,完成内容写,并关闭连接。

else if (key.isWritable()) {
dealWritable(key);
}


完整代码

package org.lanqiao.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Server implements Runnable {
//Server启停标志
private boolean interrupted = false;

public void run() {
try {
ServerSocketChannel serverSocketChannel = initServerChannel();

//打开一个选择器 Selector selector = Selector.open();// Selector的创建 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// 对channel的连接就绪感兴趣
while (!interrupted) {
//查询就绪的通道数量,这是一个阻塞方法
int readyChannels = selector.select(500);
//没有就绪的则继续进行循环
if (readyChannels == 0)
continue;
final Set<SelectionKey> selectionKeys = selector.selectedKeys();// 啥事,阻塞
//迭代当前已发生的事件
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
dealAcceptable(serverSocketChannel, selector);

} else if (key.isConnectable()) {
// a connection was established with a remote server.
}else if (key.isReadable()) {// 有读的事儿没? dealReadable(selector, key); } else if (key.isWritable()) { dealWritable(key); }
//从key集合中删除key,这一步很重要
keyIterator.remove();
}
}

} catch (IOException e) {
e.printStackTrace();
}
}

private void dealWritable(SelectionKey key) throws IOException {
// a channel is ready for writing
SocketChannel clientChannel = (SocketChannel) key.channel();
clientChannel.shutdownInput();
clientChannel.close();
}

private void dealReadable(Selector selector, SelectionKey key) {
// a channel is ready for reading
//该key有Read事件
SocketChannel clientChannel = (SocketChannel) key.channel();
String lines = NIOx.readAllLine(clientChannel);
System.out.println("lines===\n" + lines);
try {
clientChannel.register(selector, SelectionKey.OP_WRITE);
//书写报文
clientChannel.write(ByteBuffer.wrap((
"HTTP/1.1 200 OK\n" +
"Date: Sat, 4 Nov 2017 23:59:59 GMT\n" +
"Content-Type: text/html;charset=utf-8\n" +
"Content-Length: 5\n" +
"\n" +
"hello").getBytes()));
} catch (IOException e) {
e.printStackTrace();
}
}

private void dealAcceptable(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
//该key有ACCEPT事件
//将监听得到的channel强转为ServerSocketChannel
// a connection was accepted by a ServerSocketChannel.
//得到接收到的SocketChannel
SocketChannel clientChannel = serverSocketChannel.accept();
clientChannel.configureBlocking(false);// 打死不阻塞
clientChannel.register(selector, SelectionKey.OP_READ);
}

private ServerSocketChannel initServerChannel() throws IOException {
//打开ServerSocketChannel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//得到ServerSocket对象
final ServerSocket serverSocket = serverSocketChannel.socket();
//监听端口
serverSocket.bind(new InetSocketAddress(8082));
//配置为非阻塞
serverSocketChannel.configureBlocking(false);
return serverSocketChannel;
}

public static void main(String[] args) {
new Thread(new Server()).start();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  NIO web server Selector