服务器程序(如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 略有改动