利用非阻塞IO實現的單文件HTTP服務器,可以在讀取客戶端通道數據的時候,分析客戶端的請求數據,從而讓服務器做出合理的響應,這部分在實現中省略了,爲了展示出NIO服務器的一些基本實現形式。
package serverForNIO;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.URLConnection;
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;
// 提供單文件的HTTP服務器,非阻塞
public class HYSingleFileHttpServerNIO {
private int port = 1000; // 默認端口
private ByteBuffer contentBuffer; // 存儲響應數據的緩衝區
public HYSingleFileHttpServerNIO(ByteBuffer data,
String encoding, String MIMEType, int port) {
this.port = port;
// 無論是響應行,響應頭,還是響應體,都轉化成字節byte進行傳送
String header = "HTTP/1.0 200 OK\r\n" +
"Server: HYSingleFileHttpServerNIO\r\n" +
"Content-length: " + data.limit() + "\r\n" +
"Content-type: " + MIMEType + "\r\n\r\n";
byte[] headerData = header.getBytes(Charset.forName("UTF-8"));
// 將響應行,響應頭和響應體添加到緩衝區
ByteBuffer buffer = ByteBuffer.allocate(data.limit() + headerData.length);
buffer.put(headerData);
buffer.put(data);
buffer.flip();
this.contentBuffer = buffer;
}
// 執行監聽通道
public void run() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), port));
// 將通道設置爲非阻塞
serverSocketChannel.configureBlocking(false);
// 將通道註冊到選擇器上面
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 監聽器開始監聽
while (true) {
selector.select();
// 獲取選擇器篩選到滿足條件的通道的key的集合
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = (SelectionKey) keys.next();
// 刪除迭代器當前指向的元素
keys.remove();
try {
if (key.isAcceptable()) {
// 服務器端通道時可接受狀態,通過接收key獲取服務器通道
// 通過服務器端通道的accept方法,獲取請求的客戶端通道
ServerSocketChannel server = (ServerSocketChannel)key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if(key.isReadable()) { // 客戶端通道準備好被讀取
SocketChannel client = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(4096);
// 這裏客戶端一定要有數據傳過來,不然服務器會一直阻塞
client.read(buffer);
// 可以在讀取客戶端的請求數據後,HTTP服務器對讀取到數據緩衝區的數據進行解析.....
// 這裏省略解析客戶端數據的過程
// 將通道切換爲只寫模式(選擇器會看key的通道是否滿足寫的條件)
key.interestOps(SelectionKey.OP_WRITE);
key.attach(contentBuffer.duplicate());
} else if(key.isWritable()) {
SocketChannel client = (SocketChannel)key.channel();
ByteBuffer buffer = (ByteBuffer)key.attachment();
if (buffer.hasRemaining()) {
client.write(buffer);
} else {
client.close();
}
}
} catch (Exception e) {
// TODO: handle exception
key.cancel();
try {
key.channel().close();
} catch (Exception e2) {
// TODO: handle exception
}
}
}
}
}
public static void main(String[] args) throws IOException {
String contentType = URLConnection.getFileNameMap().getContentTypeFor("sources/index.html");
File file = new File("sources/index.html");
FileInputStream fileInputStream = new FileInputStream(file);
byte[] data = new byte[4096];
fileInputStream.read(data);
ByteBuffer byteBuffer = ByteBuffer.wrap(data);
HYSingleFileHttpServerNIO singleFileHttpServerNIO = new HYSingleFileHttpServerNIO(byteBuffer,
"UTF-8", contentType, 1000);
singleFileHttpServerNIO.run();
}
}