前言
花了一两个星期的时间去研究了一下Socket,结果发现一个NIO这么牛逼的东西,并且现在的tomcat,Jetty等热门的服务器都通过Nio实现了高效的httpServer。
之所以NIO能如此牛逼,是因为NIO比BIO的数据传输更加细分。BIO是一个请求一个线程处理,这导致了客户端或者服务端的读写操作比较耗时的时候,再处理上万个请求时候,就有对应上万个请求,虽然现在有线程池帮助处理回收线程,但是性能上的开销还是巨大的。而NIO,可以将一次的请求进行连接,读,写等的行为的拆分,这使得我们可以在这些行为进行分开处理,就能工作线程的开销大大减少,从而达到优化的目的。
其实网上也有不少NIO httpSocket的简单实例damo,但是我想要的是httpSocket 加上线程池,这能更加贴近我们平时使用的web容器实现原理。
小坑
在做HttpSocket加上线程池的时候,遇到一个坑,就是在请求页面的时候,在读取数据代码会抛出ClosedChannelException异常,尤其是频繁刷新的时候,我推测主要原因时候在读取数据的时候,通道关闭引起的,但是我只是在请求没有读取到数据的时候,有关闭通道关闭的代码,有数据时并不会关闭。 经过百度和头脑风暴的过程,我将目标锁定在遍历selectionKey的Iteratior的remove方法,因为读写操作有专门的线程池处理,而我们的Iteratior.remove方法是在主线程上执行,而selectionKey和channel是有关联的,如果remove掉,就相当于关闭了channel,由于异步的问题,也就是主线程在调用romve方法的时候,读写线程池还在处理,或者未出,就有可能出现这个异常。于是将remove写进只有在监听到连接的时候才调用,读写的时候不进行调用。
当然,这是我个人理解,如有不同想法,请在下方评论,谢谢。
代码实现
代码简单,不再进行任何的解析
NIO服务器
package bin.study;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class NioServerNw {
private String ip;
private int port;
private Selector selector;
private ReadThreadNw mReadThreadNw;
private WriteThreadNw mWriteThreadNw;
public static void main(String[] args) throws IOException {
NioServerNw nioServerNw=new NioServerNw("127.0.0.1",8081);
nioServerNw.startListen();
}
public NioServerNw(String ip, int port) {
this.ip = ip;
this.port = port;
mReadThreadNw=new ReadThreadNw(this);
mWriteThreadNw=new WriteThreadNw(this);
}
public void startListen() throws IOException{
selector=Selector.open();
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
serverSocketChannel.bind(new InetSocketAddress(ip,port));
while (true){
int result=selector.selectNow();
if(result==0){
continue;
}
System.out.println("result=="+result);
Iterator<SelectionKey> it=selector.selectedKeys().iterator();
while (it.hasNext()){
final SelectionKey key=it.next();
if(key.isValid()&&key.isAcceptable()){
accept(key);
// it.remove();
}else if(key.isReadable()){
mReadThreadNw.addTask(new Runnable() {
@Override
public void run() {
try {
read(key);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
}
}
private void write(SelectionKey key) throws IOException {
// System.out.println("start write");
SocketChannel channel= (SocketChannel) key.channel();
String restText=getResponseText();
ByteBuffer buffer=ByteBuffer.wrap(restText.getBytes());
while (buffer.hasRemaining()){
channel.write(buffer);
}
channel.close();
// System.out.println("End Write");
}
private String getResponseText() {
String str="HTTP/1.1 200 OK\n Content-Type: text/html;charset=UTF-8\n\n <html><head><title>BIN</title></head>"
+"<body><h1>Hello World!!</h1></body>";
return str;
}
private void read(final SelectionKey key) throws IOException {
// System.out.println("Start read");
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
boolean hasContent = false;
if (!key.isValid()) return;
while (channel.read(buffer) > 0) {
buffer.flip();
buffer.clear();
hasContent = true;
}
if (hasContent) {
System.out.println(new String(buffer.array()));
mWriteThreadNw.addTask(new Runnable() {
@Override
public void run() {
try {
write(key);
} catch (IOException e) {
e.printStackTrace();
}
}
});
} else {
channel.close();
}
}
private void accept(SelectionKey key) throws IOException {
// System.out.println("accept Connection");
ServerSocketChannel serverSocketChannel= (ServerSocketChannel) key.channel();
SocketChannel chanel = serverSocketChannel.accept();
if(chanel!=null){
chanel.configureBlocking(false);
chanel.register(selector,SelectionKey.OP_READ);
}
System.out.println("Connection end");
}
}
读数据线程池
package bin.study;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ReadThreadNw {
private ThreadPoolExecutor mThreadPoolExecutor;
private NioServerNw mNioServer;
public ReadThreadNw(NioServerNw nioServer) {
mThreadPoolExecutor = new ThreadPoolExecutor(
3, 5, 20,
TimeUnit.MINUTES,
new ArrayBlockingQueue<Runnable>(10), new ThreadPoolExecutor.DiscardPolicy());
mNioServer=nioServer;
}
public void addTask(Runnable runnable){
mThreadPoolExecutor.execute(runnable);
}
}
写数据线程池
package bin.study;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class WriteThreadNw {
private ThreadPoolExecutor mThreadPoolExecutor;
private NioServerNw mNioServer;
public WriteThreadNw(NioServerNw nioServer) {
mThreadPoolExecutor = new ThreadPoolExecutor(
3, 5, 20,
TimeUnit.MINUTES,
new ArrayBlockingQueue<Runnable>(10), new ThreadPoolExecutor.DiscardPolicy());
mNioServer=nioServer;
}
public void addTask(Runnable runnable){
mThreadPoolExecutor.execute(runnable);
}
private String getResponseText() {
String str="HTTP/1.1 200 OK\n Content-Type: text/html;charset=UTF-8\n\n <html><head><title>Nio Http Server</title></head>"
+"<body><h1>Hello World!!</h1></body>";
return str;
}
}