实现NIO httpSocket+线程池

前言

花了一两个星期的时间去研究了一下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;
    }
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值