java NIO多路复用 原理

服务器程序(如tomcat)启动时,调用操作系统函数epoll_creat创建epoll的文件描述符集合,用来存放每个客户端的请求和请求类型。
一个请求过来时,服务器程序(如tomcat)创建一个与这个请求连接对用的通道,并将此通道对应的文件描述符注册到上述创建的文件描述符集合中。当与此文件描述符对应的事件发生时,服务器程序(如tomcat)调用操作系统函数epoll_ctl将此事件放入就绪事件列表中。操作系统函数epoll_wait会不断轮询就绪事件列表。并为就绪事件创建应用程序线程去处理请求。

名词解释:
文件描述符:文件描述符是计算机科学中的一个术语,是一个指向文件的引用的抽象化概念。它往往只适用于Unix、Linux这种系统。直白地说,在Linux系统下,一切皆文件,我们不管有什么操作,都离不开对文件的读写,而文件描述符实际上就是一个索引值,它会指向一个文件,这个文件维护了进程对文件操作的记录,说白了,看着这个描述符指向的文件,我就知道我下一步要读写哪个文件了。
epoll_creat:该函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socketChannel fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socketChannel fd数。随你定好了。只要你有空间。
epoll_ctl:该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。
epoll_wait:该函数用于轮询I/O事件的发生;
事件 包括读事件和写事件
读事件:从操作系统内核缓存复制数据到应用程序缓存。
写事件:从应用程序缓存复制数据到操作系统内核缓存。
实际应用程序读写数据都是交给操作系统,并不跟硬盘直接打交道。
读事件就绪:操作系统内核缓存中已经有数据,可以读取了。
写事件就绪:操作系统内核缓存已经有空闲空间,可以写了。
参考文章:https://blog.csdn.net/qq_41563912/article/details/120139213?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-120139213-blog-118443270.pc_relevant_3mothn_strategy_and_data_recovery&spm=1001.2101.3001.4242.1&utm_relevant_index=3

https://blog.csdn.net/mikewuhao/article/details/106915576
在这里插入图片描述

NIO模式的webServer示例代码
SimpleHttpServer.java

package main.java;

import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
public class SimpleHttpServer {
    private final Selector selector;
    int port;
    private Set<SocketChannel> allConnections = new HashSet<>();
    volatile boolean run = false;
    HttpServlet servlet;
    ExecutorService executor = Executors.newFixedThreadPool(5);
    public SimpleHttpServer(int port, HttpServlet servlet) throws IOException {
        this.port = port;
        this.servlet = servlet;
        ServerSocketChannel listenerChannel = ServerSocketChannel.open();
        selector = Selector.open();//打开一个选择器供channel注册
        listenerChannel.bind(new InetSocketAddress(port));
        listenerChannel.configureBlocking(false);
        listenerChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    public Thread start() {
        run = true;
        Thread thread = new Thread(() -> {
            try {
                while (run) {
                    dispatch();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }, "selector-io");
        thread.start();
        return thread;
    }

    public void stop(int delay) {
        run = false;
    }

    private void dispatch() throws IOException {
        int select = selector.select(2000);
        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
        while (iterator.hasNext()) {
            SelectionKey  key = iterator.next();
            iterator.remove();
            if (key.isAcceptable()) {//此键的通道是否已准备好接受新的套接字连接
                ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                SocketChannel socketChannel = channel.accept();
                socketChannel.configureBlocking(false);
                socketChannel.register(selector, SelectionKey.OP_READ);
            } else if (key.isReadable()) {//此键的通道是否已准备好进行读取
                final SocketChannel channel = (SocketChannel) key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                final ByteArrayOutputStream out = new ByteArrayOutputStream();
                while (channel.read(buffer) >0) {
                    buffer.flip();
                    out.write(buffer.array(), 0, buffer.limit());
                    buffer.clear();
                }
                if (out.size() <= 0) {
                    channel.close();
                    continue;
                }
                System.out.println("当前通道:"+channel);
                //解码
                executor.submit(() -> {
                    try {
                        Request request = decode(out.toByteArray());
                        Response response = new Response();
                        if (request.method.equalsIgnoreCase("GET")) {
                            servlet.doGet(request, response);
                        } else {
                            servlet.doPost(request, response);
                        }
                        System.out.println("tt:  "+ new String(encode(response)));
                        channel.write(ByteBuffer.wrap(encode(response)));
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                });
            }
        }


    }


    // 解码Http服务
    private Request decode(byte[] bytes) throws IOException {
        Request request = new Request();
        BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
        String firstLine = reader.readLine();
        System.out.println(firstLine);
        String[] split = firstLine.trim().split(" ");
        request.method = split[0];
        request.url = split[1];
        request.version = split[2];

        //读取请求头
        Map<String, String> heads = new HashMap<>();
        while (true) {
            String line = reader.readLine();
            if (line.trim().equals("")) {
                break;
            }
            String[] split1 = line.split(":");
            heads.put(split1[0], split1[1]);
        }
        request.heads = heads;
        request.params = getUrlParams(request.url);
        //读取请求体
        request.body = reader.readLine();
        return request;

    }

    //编码Http 服务
    private byte[] encode(Response response) {
        StringBuilder builder = new StringBuilder(512);
        builder.append("HTTP/1.1 ")
                .append(response.code).append(Code.msg(response.code)).append("\r\n");

        if (response.body != null && response.body.length() != 0) {
            builder.append("Content-Length: ")
                    .append(response.body.length()).append("\r\n")
                    .append("Content-Type: text/html\r\n");
        }
        if (response.headers!=null) {
            String headStr = response.headers.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue())
                        .collect(Collectors.joining("\r\n"));
            builder.append(headStr+"\r\n");
        }


//      builder.append ("Connection: close\r\n");// 执行完后关闭链接
        builder.append(response.body);
        return builder.toString().getBytes();
    }


    public abstract static class HttpServlet {

        abstract void doGet(Request request, Response response);

        abstract void doPost(Request request, Response response);
    }

    public static class Request {
        Map<String, String> heads;
        String url;
        String method;
        String version;
        String body;    //请求内容
        Map<String, String> params;
    }

    public static class Response {
        Map<String, String> headers;
        int code;
        String body; //返回结果
    }


    private static Map getUrlParams(String url) {
        Map<String, String> map = new HashMap<>();
        url = url.replace("?", ";");
        if (!url.contains(";")) {
            return map;
        }
        if (url.split(";").length > 0) {
            String[] arr = url.split(";")[1].split("&");
            for (String s : arr) {
                if (s.contains("=")) {
                    String key = s.split("=")[0];
                    String value = s.split("=")[1];
                    map.put(key, value);
                }else {
                    map.put(s,null);
                }
            }
            return map;

        } else {
            return map;
        }
    }
}

HttpServerTest.java

package main.java;

import org.junit.Test;

import java.io.IOException;
import java.util.HashMap;


public class HttpServerTest {

    @Test
    public void simpleHttpTest() throws IOException, InterruptedException {
        SimpleHttpServer simpleHttpServer = new SimpleHttpServer(8080, new SimpleHttpServer.HttpServlet() {
            @Override
            void doGet(SimpleHttpServer.Request request, SimpleHttpServer.Response response) {
                System.out.println(request.url);
                response.body="hello word";
                response.code=200;
                response.headers=new HashMap<>();
                if (request.params.containsKey("short")) {
                    response.headers.put("Connection", "close");
                }else if(request.params.containsKey("long")){
                    response.headers.put("Connection", "keep-alive");
                    response.headers.put("Keep-Alive", "timeout=30,max=300");
                }
            }

            @Override
            void doPost(SimpleHttpServer.Request request, SimpleHttpServer.Response response) {

            }
        });
        simpleHttpServer.start().join();
    }
}

Code.java

package main.java;

public class Code {

    public static final int HTTP_CONTINUE = 100;
    public static final int HTTP_OK = 200;
    public static final int HTTP_CREATED = 201;
    public static final int HTTP_ACCEPTED = 202;
    public static final int HTTP_NOT_AUTHORITATIVE = 203;
    public static final int HTTP_NO_CONTENT = 204;
    public static final int HTTP_RESET = 205;
    public static final int HTTP_PARTIAL = 206;
    public static final int HTTP_MULT_CHOICE = 300;
    public static final int HTTP_MOVED_PERM = 301;
    public static final int HTTP_MOVED_TEMP = 302;
    public static final int HTTP_SEE_OTHER = 303;
    public static final int HTTP_NOT_MODIFIED = 304;
    public static final int HTTP_USE_PROXY = 305;
    public static final int HTTP_BAD_REQUEST = 400;
    public static final int HTTP_UNAUTHORIZED = 401;
    public static final int HTTP_PAYMENT_REQUIRED = 402;
    public static final int HTTP_FORBIDDEN = 403;
    public static final int HTTP_NOT_FOUND = 404;
    public static final int HTTP_BAD_METHOD = 405;
    public static final int HTTP_NOT_ACCEPTABLE = 406;
    public static final int HTTP_PROXY_AUTH = 407;
    public static final int HTTP_CLIENT_TIMEOUT = 408;
    public static final int HTTP_CONFLICT = 409;
    public static final int HTTP_GONE = 410;
    public static final int HTTP_LENGTH_REQUIRED = 411;
    public static final int HTTP_PRECON_FAILED = 412;
    public static final int HTTP_ENTITY_TOO_LARGE = 413;
    public static final int HTTP_REQ_TOO_LONG = 414;
    public static final int HTTP_UNSUPPORTED_TYPE = 415;
    public static final int HTTP_INTERNAL_ERROR = 500;
    public static final int HTTP_NOT_IMPLEMENTED = 501;
    public static final int HTTP_BAD_GATEWAY = 502;
    public static final int HTTP_UNAVAILABLE = 503;
    public static final int HTTP_GATEWAY_TIMEOUT = 504;
    public static final int HTTP_VERSION = 505;

    static String msg (int code) {

        switch (code) {
            case HTTP_OK: return " OK";
            case HTTP_CONTINUE: return " Continue";
            case HTTP_CREATED: return " Created";
            case HTTP_ACCEPTED: return " Accepted";
            case HTTP_NOT_AUTHORITATIVE: return " Non-Authoritative Information";
            case HTTP_NO_CONTENT: return " No Content";
            case HTTP_RESET: return " Reset Content";
            case HTTP_PARTIAL: return " Partial Content";
            case HTTP_MULT_CHOICE: return " Multiple Choices";
            case HTTP_MOVED_PERM: return " Moved Permanently";
            case HTTP_MOVED_TEMP: return " Temporary Redirect";
            case HTTP_SEE_OTHER: return " See Other";
            case HTTP_NOT_MODIFIED: return " Not Modified";
            case HTTP_USE_PROXY: return " Use Proxy";
            case HTTP_BAD_REQUEST: return " Bad Request";
            case HTTP_UNAUTHORIZED: return " Unauthorized" ;
            case HTTP_PAYMENT_REQUIRED: return " Payment Required";
            case HTTP_FORBIDDEN: return " Forbidden";
            case HTTP_NOT_FOUND: return " Not Found";
            case HTTP_BAD_METHOD: return " Method Not Allowed";
            case HTTP_NOT_ACCEPTABLE: return " Not Acceptable";
            case HTTP_PROXY_AUTH: return " Proxy Authentication Required";
            case HTTP_CLIENT_TIMEOUT: return " Request Time-Out";
            case HTTP_CONFLICT: return " Conflict";
            case HTTP_GONE: return " Gone";
            case HTTP_LENGTH_REQUIRED: return " Length Required";
            case HTTP_PRECON_FAILED: return " Precondition Failed";
            case HTTP_ENTITY_TOO_LARGE: return " Request Entity Too Large";
            case HTTP_REQ_TOO_LONG: return " Request-URI Too Large";
            case HTTP_UNSUPPORTED_TYPE: return " Unsupported Media Type";
            case HTTP_INTERNAL_ERROR: return " Internal Server Error";
            case HTTP_NOT_IMPLEMENTED: return " Not Implemented";
            case HTTP_BAD_GATEWAY: return " Bad Gateway";
            case HTTP_UNAVAILABLE: return " Service Unavailable";
            case HTTP_GATEWAY_TIMEOUT: return " Gateway Timeout";
            case HTTP_VERSION: return " HTTP Version Not Supported";
            default: return " ";
        }
    }
}

代码来自 https://blog.csdn.net/weixin_42107858/article/details/107601512 略有改动

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值