jdk下httpserver源码解析
在写这篇博客之前我查了很久发现全网都没有一篇写httpserver源码解析的
所以今天就由我来为大家解析一下httpserver的源码。(这里我会去掉其中的https部分的源码,只讲http部分,对httpserver中https的实现感兴趣的读者可以尝试自己去阅读,这部分并不复杂)
第一次在没有参考资料的情况下写这么长一篇源码解析,可能会有很多错误和讲不清楚的地方,希望大家尽量指出来。
本文链接
httpserver的简单使用例子
大家最好先跟着我构建这样一个小demo,跑起来之后再一步一步去看源码
/** * @author 肥宅快乐码 */ public class httpserversample { private static void serverstart() throws ioexception { httpserverprovider provider = httpserverprovider.provider(); // 监听端口8080,连接排队队列,如果队列中的连接超过这个数的话就会拒绝连接 httpserver httpserver =provider.createhttpserver(new inetsocketaddress(8080), 100); // 监听路径为restsample,请求处理后回调restgethandler里的handle方法 httpserver.createcontext("/restsample", new restgethandler()); // 管理工作线程池 executorservice executor = new threadpoolexecutor(10,200,60, timeunit.seconds, new linkedblockingqueue<>(), new threadpoolexecutor.abortpolicy()); httpserver.setexecutor(executor); httpserver.start(); system.out.println("server started"); } public static void main(string[] args) throws ioexception { serverstart(); } } /** * 回调类,里面的handle方法主要完成将包装好的请求头返回给客户端的功能 */ class restgethandler implements httphandler { @override public void handle(httpexchange he) throws ioexception { string requestmethod = he.getrequestmethod(); // 如果是get方法 if ("get".equalsignorecase(requestmethod)) { // 获取响应头,接下来我们来设置响应头信息 headers responseheaders = he.getresponseheaders(); // 以json形式返回,其他还有text/html等等 responseheaders.set("content-type", "application/json"); // 设置响应码200和响应body长度,这里我们设0,没有响应体 he.sendresponseheaders(200, 0); // 获取响应体 outputstream responsebody = he.getresponsebody(); // 获取请求头并打印 headers requestheaders = he.getrequestheaders(); set<string> keyset = requestheaders.keyset(); iterator<string> iter = keyset.iterator(); while (iter.hasnext()) { string key = iter.next(); list values = requestheaders.get(key); string s = key + " = " + values.tostring() + "\r\n"; responsebody.write(s.getbytes()); } // 关闭输出流 responsebody.close(); } } }
httpserver初始化及启动源码
初始化
① 最开始我们通过 httpserverprovider provider = httpserverprovider.provider(); 创建了一个httpserverprovider,也就是这里的defaulthttpserverprovider
// httpserverprovider.java public static httpserverprovider provider () { // 这里我们删掉了其他部分,只留下172、173两行 // 这里创建了一个defaulthttpserverprovider provider = new sun.net.httpserver.defaulthttpserverprovider(); return provider; }
② 之后我们调用 httpserver httpserver =provider.createhttpserver(new inetsocketaddress(8080), 100); ,
也就是调用了defaulthttpserverprovider的createhttpserver创建一个httpserverimpl,当然这里也可以用createhttpsserver创建一个httpsserverimpl,但是前面说了我们这篇不分析https,所以这里忽略了createhttpsserver方法
还有这里创建serverimpl的构造方法我们暂时不讲,留到后面再讲
// defaulthttpserverprovider.java public httpserver createhttpserver (inetsocketaddress addr, int backlog) throws ioexception { return new httpserverimpl (addr, backlog); } // httpserverimpl.java httpserverimpl ( inetsocketaddress addr, int backlog ) throws ioexception { server = new serverimpl (this, "http", addr, backlog); }
③ 接下来我们创建了一个监听路径 httpserver.createcontext("/restsample", new restgethandler());
// httpserver.java public abstract httpcontext createcontext (string path, httphandler handler) ; // httpcontextimpl.java public httpcontextimpl createcontext (string path, httphandler handler) { // 这里调用的server是serverimpl类的对象 return server.createcontext (path, handler); }
这里成功返回了一个httpcontextimpl对象,这个我们后面会说,这里我们要知道的是,httpserverimpl调用的是serverimpl的实现
到这里我们差不多可以聊一下httpserver的主要结构了:
主要结构
httpserver是这里的祖先类,它是一个抽象类,抽象了一个httpserver应该有的方法
而httpsserver和我们想象的不一样,它和httpserver不是平行关系,而是httpserver的子类,它在httpserver的基础上加了sethttpsconfigurator和gethttpsconfigurator这两个方法而已
httpserverimpl和httpsserverimpl虽然都是实现类,但是它们的方法都是调用serverimpl的方法,都是围绕serverimpl的
所以我们也可以把serverimpl看做这个项目的核心类
④ 之后设置一下工作线程池,初始化任务就完成了
executorservice executor = new threadpoolexecutor(10,200,60, timeunit.seconds, new linkedblockingqueue<>(), new threadpoolexecutor.abortpolicy()); httpserver.setexecutor(executor);
启动
httpserver.start();
启动自然和我们刚刚聊的结构一样都是从httpserver开始一层调一层调用到serverimpl的方法的:
// httpserver.java public abstract void start () ; // httpserverimpl.java public void start () { server.start(); } // serverimpl.java public void start () { // server未绑定端口或处于已启动或已关闭状态 // 顺便先提一下,这里就可以留意到,serverimpl作为一个核心类,管理了各种各样的状态(state)等 if (!bound || started || finished) { throw new illegalstateexception ("server in wrong state"); } // 如果没有设置线程池,那就用默认的,默认的话等于没有用线程池,是直接execute的,所以尽可能直接创建线程池 if (executor == null) { executor = new defaultexecutor(); } // 创建了一个dispatcher线程,用来分发任务,如accept或者readable thread t = new thread (dispatcher); // 设置一下状态 started = true; // 运行线程 t.start(); }
serverimpl结构图
前面我们说过,serverimpl是这整个项目的核心部分,它管理了httpserver的状态,提供了各种接口以及通用的方法,它也负责了几个内部类线程的启动
所以,接下来我们会分为serverimpl、dispatcher、exchange、servertimertask与servertimertask1四个部分来讲解
serverimpl
主要属性
(https相关的我去掉了)
比较长,大家稍微过一眼有个印象,之后遇到的时候再回来看就行
// http或https private string protocol; private executor executor; // 负责接收连接用的类(这个本来在209行附近,我把它提上来了) private dispatcher dispatcher; // contextlist这个类只是封装了一个list<httpcontextimpl>及一些方法,如限制监听的context(路径)的数目和查找context的方法 private contextlist contexts; private inetsocketaddress address; // nio相关的那些类 private serversocketchannel schan; private selector selector; private selectionkey listenerkey; // 负责管理之前提到的idle连接,也就是长连接的set // 长连接时,连接如果没有任务,就加进去. 如果超过一定时间没有任务,则主动断开长连接 private set<httpconnection> idleconnections; // 管理所有的连接,方便在stop等情况下直接断开所有连接 private set<httpconnection> allconnections; // 管理req连接和rsp连接,防止请求或响应超时,超时时由定时线程断开连接 private set<httpconnection> reqconnections; private set<httpconnection> rspconnections; // 这两个之后6.4的exchange的addevent方法部分我们再说 private list<event> events; private final object lolock = new object(); // 各种状态,相信大家看得懂是什么意思 private volatile boolean finished = false; private volatile boolean terminating = false; private boolean bound = false; private boolean started = false; // 系统时间,会由servertimertask进行更新 private volatile long time; // 这个似乎并没有任何用 private volatile long subticks = 0; // 这个是用来记录一共更新了多少次time的,相当于时间戳一样的东西 private volatile long ticks; // 把httpserver包装进来,方便调用 private httpserver wrapper; // 这个的意思是servertimertask每隔多长时间定期run一下,因为servertimertask是一个定时任务线程 // 默认是10000ms也就是10秒一次 private final static int clock_tick = serverconfig.getclocktick(); // 这个是允许长连接驻留的时间,默认是30秒 private final static long idle_interval = serverconfig.getidleinterval(); // 允许最大长连接数,默认200 private final static int max_idle_connections = serverconfig.getmaxidleconnections(); // servertimertask1的定期时间,默认是1秒 private final static long timer_millis = serverconfig.gettimermillis (); // 最后这两个默认为-1,至于为什么是-1后面servertimertask部分我们会说 private final static long max_req_time = gettimemillis(serverconfig.getmaxreqtime()); private final static long max_rsp_time=gettimemillis(serverconfig.getmaxrsptime()); private final static boolean req_rsp_clean_enabled = max_req_time != -1 || max_rsp_time != -1; // servertimertask和servertimertask1的对象,跑起来就是servertimertask和servertimertask1线程了 private timer timer, timer1; private logger logger;
构造方法
这就是刚刚2.1小节中提到的serverimpl的构造方法,没什么要讲的,无非就是初始化了变量并启动了servertimertask和servertimertask1线程
serverimpl ( httpserver wrapper, string protocol, inetsocketaddress addr, int backlog ) throws ioexception { this.protocol = protocol; this.wrapper = wrapper; this.logger = logger.getlogger ("com.sun.net.httpserver"); serverconfig.checklegacyproperties (logger); this.address = addr; contexts = new contextlist(); schan = serversocketchannel.open(); if (addr != null) { serversocket socket = schan.socket(); socket.bind (addr, backlog); bound = true; } selector = selector.open (); schan.configureblocking (false); listenerkey = schan.register (selector, selectionkey.op_accept); dispatcher = new dispatcher(); idleconnections = collections.synchronizedset (new hashset<httpconnection>()); allconnections = collections.synchronizedset (new hashset<httpconnection>()); reqconnections = collections.synchronizedset (new hashset<httpconnection>()); rspconnections = collections.synchronizedset (new hashset<httpconnection>()); time = system.currenttimemillis(); timer = new timer ("server-timer", true); // 可以看到,在初始化阶段两个定时任务就已经启动了 timer.schedule (new servertimertask(), clock_tick, clock_tick); if (timer1enabled) { timer1 = new timer ("server-timer1", true); timer1.schedule (new servertimertask1(),timer_millis,timer_millis); logger.config ("httpserver timer1 enabled period in ms: "+timer_millis); logger.config ("max_req_time: "+max_req_time); logger.config ("max_rsp_time: "+max_rsp_time); } events = new linkedlist<event>(); logger.config ("httpserver created "+protocol+" "+ addr); }
当然serverimpl有很多通用的方法,但是这里我们不讲,等到用到它们的时候我们再讲,这样比较方便了解这些通用方法的具体用途
dispatcher
先来看它的run方法
run()
public void run() { // 如果已经完全关闭服务器,那就不用任何处理了 while (!finished) { try { // ================这段大概就是把处理完成返回结果完毕的连接注册进idle长连接里面,后面流程经过再细讲===================================== list<event> list = null; synchronized (lolock) { if (events.size() > 0) { list = events; events = new linkedlist<event>(); } } if (list != null) { for (event r: list) { handleevent (r); } } for (httpconnection c : connstoregister) { reregister(c); } connstoregister.clear(); // ======================================================================================================================== // 阻塞,超过1000ms就继续运行 selector.select(1000); /* process the selected list now */ set<selectionkey> selected = selector.selectedkeys(); iterator<selectionkey> iter = selected.iterator(); while (iter.hasnext()) { selectionkey key = iter.next(); iter.remove (); // 这里listenrkey是accept事件,相当于key.isacceptable() if (key.equals (listenerkey)) { // 如果正在关闭服务器,那就不用处理了,直接把新的连接continue然后remove掉就可以了 if (terminating) { continue; } socketchannel chan = schan.accept(); // 根据需要开启tcpnodelay,也就是关闭nagle算法,减小缓存带来的延迟 if (serverconfig.nodelay()) { chan.socket().settcpnodelay(true); } if (chan == null) { continue; /* cancel something ? */ } chan.configureblocking (false); selectionkey newkey = chan.register (selector, selectionkey.op_read); // 创建connection并把channel放进去 httpconnection c = new httpconnection (); c.selectionkey = newkey; c.setchannel (chan); // 把connection缓存到key中 newkey.attach (c); // 请求开始,注册到reqconnections中 requeststarted (c); allconnections.add (c); } else { try { if (key.isreadable()) { boolean closed; socketchannel chan = (socketchannel)key.channel(); // 这里把刚刚attach缓存的connection取出来了 httpconnection conn = (httpconnection)key.attachment(); // 这里的这种先取消注册并设置为阻塞的读取方式与多次读取有关 // 因为后面是先读头部,之后再读取body等其他部分的 key.cancel(); chan.configureblocking (true); // 如果这个connection是之前保存着的空闲长连接,那么直接移出idleconnections中 // 并加入reqconnections开始请求(因为io流都初始化好了,可以直接用) if (idleconnections.remove(conn)) { // 加入reqconnections开始请求 requeststarted (conn); } // 调用handle进行后续处理 handle (chan, conn); } else { assert false; } } catch (cancelledkeyexception e) { handleexception(key, null); } catch (ioexception e) { handleexception(key, e); } } } // 调用select去掉cancel了的key selector.selectnow(); } catch (ioexception e) { logger.log (level.finer, "dispatcher (4)", e); } catch (exception e) { logger.log (level.finer, "dispatcher (7)", e); } } try {selector.close(); } catch (exception e) {} }
这里稍微总结一下,dispatcher的run主要就是完成socket连接的accept和readable事件的分发功能,其中accept分发给它自己,它自己创建channel并注册,自己创建连接并缓存。而readable事件则在经过简单处理后交给handle去调用exchange线程继续进行后续任务
handle(socketchannel, httpconnection)
public void handle (socketchannel chan, httpconnection conn) throws ioexception { try { // 构造一个exchange后让executor线程池去执行,这里相当于一个异步任务 · // 在将任务交给executor后,dispatcher就可以返回了 exchange t = new exchange (chan, protocol, conn); executor.execute (t); } catch (httperror e1) { logger.log (level.finer, "dispatcher (4)", e1); closeconnection(conn); } catch (ioexception e) { logger.log (level.finer, "dispatcher (5)", e); closeconnection(conn); } }
exchange
既然前面把任务丢给了exchange,那么接下来我们就来看exchange的run方法在做什么
run()
public void run () { // context对应着这个http请求访问的路径和处理器, // 而一个未解析http请求自然context为null,也就是不知道这个请求是想请求哪个路径的 context = connection.gethttpcontext(); boolean newconnection; try { // 这里是已经解析过的http请求才会进去,因为它们有context // 为什么会有解析过的http请求呢?想想长连接,前面dispatcher的75、76行我们提到过 // 长连接也就是idleconnection会缓存那些io流在connection里面,当然也包括context //(但是只是存在,并不代表context不需要重新解析,毕竟再次请求时请求的资源链接不一定相同) if (context != null ) { this.rawin = connection.getinputstream(); this.rawout = connection.getrawoutputstream(); newconnection = false; } else { newconnection = true; if (https) { // . . . . . . } else { // 这里request的两种stream都封装了一些读写方法,比较繁琐所以不分析了 rawin = new bufferedinputstream( new request.readstream ( serverimpl.this, chan )); rawout = new request.writestream ( serverimpl.this, chan ); } connection.raw = rawin; connection.rawout = rawout; } request req = new request (rawin, rawout); requestline = req.requestline(); // 读取请求的一行后,如果请求为空就关闭connection // 那么什么情况为空呢?大家都知道,http请求大体分三部分, // 1.三次握手连接,被封装成socket的accept // 2.开始发送内容,被封装成socket的readable事件 // 那么四次挥手呢?其实也是readable,但是其内容为空 // 所以这里其实是挥手关闭连接的意思 if (requestline == null) { closeconnection(connection); return; } // 获取请求类型(get/post...) int space = requestline.indexof (' '); if (space == -1) { reject (code.http_bad_request, requestline, "bad request line"); return; } string method = requestline.substring (0, space); // 获取请求的url int start = space+1; space = requestline.indexof(' ', start); if (space == -1) { reject (code.http_bad_request, requestline, "bad request line"); return; } string uristr = requestline.substring (start, space); uri uri = new uri (uristr); // http请求版本(1.0/1.1...) start = space+1; string version = requestline.substring (start); headers headers = req.headers(); // 如果是采用transfer-encoding,那么解析body的方式不同, // 而且context-length将被忽略,所以标记为长度clen = -1 // 具体可以去了解一下transfer-encoding string s = headers.getfirst ("transfer-encoding"); long clen = 0l; if (s !=null && s.equalsignorecase ("chunked")) { clen = -1l; } else { // 没用transfer-encoding而用了content-length s = headers.getfirst ("content-length"); if (s != null) { clen = long.parselong(s); } if (clen == 0) { // 如果主体长度为0,那么请求已经结束,这里将connection从 // reqconnections中移出,并添加当前时间,加入rspconnections requestcompleted (connection); } } // 这里就是最开始serverimpl属性(可以回去看)里contextlist里封装的方法 // 用来查询是否有匹配的context路径 ctx = contexts.findcontext (protocol, uri.getpath()); if (ctx == null) { reject (code.http_not_found, requestline, "no context found for request"); return; } connection.setcontext (ctx); // 如果没有回调方法,也就是最开始demo里自定义的restgethandler类 if (ctx.gethandler() == null) { reject (code.http_internal_error, requestline, "no handler for context"); return; } // 相当于http请求的完整封装,后面再包上一层httpexchangeimpl就是 // restgethandler类里的回调方法handle的参数了 tx = new exchangeimpl ( method, uri, req, clen, connection ); // 看看有没有connection:close参数,1.0默认close,需要手动开启keep-alive string chdr = headers.getfirst("connection"); headers rheaders = tx.getresponseheaders(); if (chdr != null && chdr.equalsignorecase ("close")) { tx.close = true; } if (version.equalsignorecase ("http/1.0")) { tx.http10 = true; if (chdr == null) { tx.close = true; rheaders.set ("connection", "close"); } else if (chdr.equalsignorecase ("keep-alive")) { rheaders.set ("connection", "keep-alive"); int idle=(int)(serverconfig.getidleinterval()/1000); int max=serverconfig.getmaxidleconnections(); string val = "timeout="+idle+", max="+max; rheaders.set ("keep-alive", val); } } // 是新连接而不是长连接的话,给connection赋值一下 if (newconnection) { connection.setparameters ( rawin, rawout, chan, engine, sslstreams, sslcontext, protocol, ctx, rawin ); } // 如果客户端发出expect:100-continue,意思就是客户端想要post东西(一般是比较大的),询问是否同意 // 返回响应码100后客户端才会继续post数据 string exp = headers.getfirst("expect"); if (exp != null && exp.equalsignorecase ("100-continue")) { logreply (100, requestline, null); sendreply ( code.http_continue, false, null ); } // 获取一下系统自带的过滤器sf或者用户自定义的过滤器uf,这里都默认为无 list<filter> sf = ctx.getsystemfilters(); list<filter> uf = ctx.getfilters(); // 构造成一个链表,以链表的形式一层一层调用过滤器 filter.chain sc = new filter.chain(sf, ctx.gethandler()); filter.chain uc = new filter.chain(uf, new linkhandler (sc)); // 初始化一下包装的io流,这里我把getrequestbody拿过来,两个大同小异 /** *public inputstream getrequestbody () { * if (uis != null) { * return uis; * } * if (reqcontentlen == -1l) { * uis_orig = new chunkedinputstream (this, ris); * uis = uis_orig; * } else { * uis_orig = new fixedlengthinputstream (this, ris, reqcontentlen); * uis = uis_orig; * } * return uis; *} */ tx.getrequestbody(); tx.getresponsebody(); if (https) { uc.dofilter (new httpsexchangeimpl (tx)); } else { // 开始执行过滤方法,参数和我刚刚提到的一样,就是包成httpexchangeimpl的exchangeimpl // 接下来我们就往这里看 uc.dofilter (new httpexchangeimpl (tx)); } } catch (ioexception e1) { logger.log (level.finer, "serverimpl.exchange (1)", e1); closeconnection(connection); } catch (numberformatexception e3) { reject (code.http_bad_request, requestline, "numberformatexception thrown"); } catch (urisyntaxexception e) { reject (code.http_bad_request, requestline, "urisyntaxexception thrown"); } catch (exception e4) { logger.log (level.finer, "serverimpl.exchange (2)", e4); closeconnection(connection); } }
dofilter()
// filter.java的chain内部类 public void dofilter (httpexchange exchange) throws ioexception { // 递归调用直到没有filter时,调用自定义的回调方法,也就是restgethandler的handle方法 if (!iter.hasnext()) { handler.handle (exchange); } else { filter f = iter.next(); f.dofilter (exchange, this); } }
我重新贴一遍demo里的restgethandler给大家看(17和32行的注释有改动,注意看):
/** * 回调类,里面的handle方法主要完成将包装好的请求头返回给客户端的功能 */ class restgethandler implements httphandler { @override public void handle(httpexchange he) throws ioexception { string requestmethod = he.getrequestmethod(); // 如果是get方法 if ("get".equalsignorecase(requestmethod)) { // 获取响应头,接下来我们来设置响应头信息 headers responseheaders = he.getresponseheaders(); // 以json形式返回,其他还有text/html等等 responseheaders.set("content-type", "application/json"); // 设置响应码200和响应body长度,这里我们设0,没有响应体,这里也初始化了io流 // 这里如果为0,则初始化chunkedoutputstream或undeflengthoutputstream // 如果不为0,则初始化fixedlengthoutputstream he.sendresponseheaders(200, 0); // 获取响应体 outputstream responsebody = he.getresponsebody(); // 获取请求头并打印 headers requestheaders = he.getrequestheaders(); set<string> keyset = requestheaders.keyset(); iterator<string> iter = keyset.iterator(); while (iter.hasnext()) { string key = iter.next(); list values = requestheaders.get(key); string s = key + " = " + values.tostring() + "\r\n"; responsebody.write(s.getbytes()); } // 关闭输出流,也就是关闭chunkedoutputstream // 接下来看这里 responsebody.close(); } } }
在回调方法完成返回数据给客户端的任务后,调用了close方法
close()
这里我们重点关注最后一行代码
public void close () throws ioexception { if (closed) { return; } flush(); try { writechunk(); out.flush(); leftoverinputstream is = t.getoriginalinputstream(); if (!is.isclosed()) { is.close(); } } catch (ioexception e) { } finally { closed = true; } writefinishedevent e = new writefinishedevent (t); // 这里我们只关注最后一行,其他的不关注 // 这行调用了addevent方法 t.gethttpcontext().getserverimpl().addevent (e); }
addevent()
// 这里就调用了4.1中serverimpl的属性的第28、29、30行的内容 void addevent (event r) { // 而这里的锁,就是防止dispatcher的run方法最前面那里 // 防止它取出events时与这里的add产生冲突 synchronized (lolock) { events.add (r); // 这里的wakeup就是往管道里输入一个字节唤醒dispatcher里 // 的selector.select(1000),让它不再阻塞,去取出events selector.wakeup(); } }
到这里exchange的工作就完成了,接下来我来稍微总结一下:
- 首先exchange对http请求进行解析和封装,匹配相应的context的handle,初始化一下io流
- 然后exchange调用相应的回调handle方法进行处理
- handle方法一般都是我们自己写的响应方法,我这里自定义的restgethandler的handle方法负责把请求头作为内容响应回去,也就是下图这种效果
- 然后handle方法调用io流的close关闭io流,表示响应结束
- 并调用addevent方法把exchangeimpl封装成event放进list里面,至于为什么要这么做我们接下来继续分析
既然有地方加入list,那自然有地方取出list,回忆一下,我们刚刚见到list<event>的主要有两个地方
一个是serverimpl属性里的28~30行,也就是说它是serverimpl的属性
还有一个地方则是dispatcher类的run方法里,我说了后面再细讲,大家可以回去瞄一眼在什么位置
接下来我们就来讲这个部分:
public void run() { // 如果已经完全关闭服务器,那就不用任何处理了 while (!finished) { try { // 这里就把events取出来放到list里面了,并把events重新赋值空对象 list<event> list = null; // 还记得我们刚刚说过,lolock锁是防止addevent操作和取操作冲突的 synchronized (lolock) { if (events.size() > 0) { list = events; events = new linkedlist<event>(); } } // 之后遍历取出每个event,并调用handleevent方法 if (list != null) { for (event r: list) { // 接下来看这里 handleevent (r); } } for (httpconnection c : connstoregister) { reregister(c); } connstoregister.clear();
handleevent(event)
/** * 处理event,将长连接加入等待重新注册的connectionstoregister列表中 */ private void handleevent (event event) { exchangeimpl t = event.exchange; httpconnection c = t.getconnection(); try { if (event instanceof writefinishedevent) { if (terminating) { finished = true; } // 完成响应,处理一些状态,可以自己去看,没几行 responsecompleted (c); leftoverinputstream is = t.getoriginalinputstream(); if (!is.iseof()) { t.close = true; } // 如果空闲的连接超过max_idle_connections(默认200,可以看之前serverimpl的属性), // 则不能再添加了,并且关闭连接 if (t.close || idleconnections.size() >= max_idle_connections) { c.close(); allconnections.remove (c); } else { if (is.isdatabuffered()) { requeststarted (c); handle (c.getchannel(), c); } else { // 将连接加入connectionstoregister列表中等待重新注册进 connectionstoregister.add (c); } } } } catch (ioexception e) { logger.log ( level.finer, "dispatcher (1)", e ); c.close(); } }
之后就是遍历connectionstoregister列表并将连接注册进idleconnections长连接set中
for (httpconnection c : connstoregister) { // 接下来看这里 reregister(c); } connstoregister.clear();
reregister()
/** * 把之前cancel的key重新用非阻塞的方式监听起来 * 并且把连接加入idleconnections空闲连接中 */ void reregister (httpconnection connection) { try { socketchannel chan = connection.getchannel(); chan.configureblocking (false); selectionkey key = chan.register (selector, selectionkey.op_read); key.attach (connection); connection.time = gettime() + idle_interval; idleconnections.add (connection); } catch (ioexception e) { logger.log(level.finer, "dispatcher(8)", e); connection.close(); } }
就这样,完成响应的请求就在idleconnection中缓存起来
整体流程图
从一个http请求讲起
上面是我抓的包,可以看到一个http请求一共三部分组成,第一部分是tcp三次握手连接服务端,第二部分是传输信息主体,第三部分就是tcp四次挥手断开连接
而这三部分的tcp操作都对应抽象成了socket的操作,所谓socket,其实就是对tcp和udp的一个上层抽象,方便程序员调用的
其中最明显的,就是accept对应三次握手操作了
所以接下来,我们的流程图就会从一次http请求开始,展示这三个部分分别对应项目的哪些部分,让读者有一个更清晰的理解
如果还是不理解的话,建议对着图重新看一遍这篇文章
最后,在这个过程中,有调用到serverimpl的requeststarted()方法,以及我没有标出来的requestcompleted和close时调用的responsecompleted(这两个这篇文章里没有,可以自己追踪去看一下在哪里调用了),这些方法都是对serverimpl的属性:
private set<httpconnection> idleconnections; // 管理所有的连接,方便在stop等情况下直接断开所有连接 private set<httpconnection> allconnections; // 管理req连接和rsp连接,防止请求或响应超时,超时时由定时线程断开连接 private set<httpconnection> reqconnections; private set<httpconnection> rspconnections;
做了一系列添加删除操作代表请求开始,请求结束,响应开始,响应结束和代表长连接被缓存起来等,那么这些到底有什么用呢?缓存connection吗?并不是。connection是缓存在key里面的,通过attachment获得。其实他们的真实作用是方便在超时的时候由定时任务去清理它们。
定时任务servertimertask和servertimertask1
// 前面我们在serverimpl的构造方法说过,这两个定时任务都已经运行了 // 这个负责清理长连接的是10秒(serverimpl里的clock_tick)运行一次 class servertimertask extends timertask { public void run () { linkedlist<httpconnection> toclose = new linkedlist<httpconnection>(); time = system.currenttimemillis(); ticks ++; synchronized (idleconnections) { for (httpconnection c : idleconnections) { if (c.time <= time) { toclose.add (c); } } for (httpconnection c : toclose) { idleconnections.remove (c); allconnections.remove (c); // 这里调用httpconnection的close方法,方法里清理输入输出流和关闭channel等 c.close(); } } } } // 这个是每秒(timer_millis)执行一次 class servertimertask1 extends timertask { // runs every timer_millis public void run () { linkedlist<httpconnection> toclose = new linkedlist<httpconnection>(); time = system.currenttimemillis(); synchronized (reqconnections) { if (max_req_time != -1) { for (httpconnection c : reqconnections) { if (c.creationtime + timer_millis + max_req_time <= time) { toclose.add (c); } } for (httpconnection c : toclose) { logger.log (level.fine, "closing: no request: " + c); reqconnections.remove (c); allconnections.remove (c); c.close(); } } } toclose = new linkedlist<httpconnection>(); synchronized (rspconnections) { if (max_rsp_time != -1) { for (httpconnection c : rspconnections) { if (c.rspstartedtime + timer_millis +max_rsp_time <= time) { toclose.add (c); } } for (httpconnection c : toclose) { logger.log (level.fine, "closing: no response: " + c); rspconnections.remove (c); allconnections.remove (c); c.close(); } } } } }
本来只是想简单写一个httpserver玩玩的,但是查了网上很多资料,发现代码质量有些参差不齐,所以就干脆直接参考了jdk里的httpserver的源码了,总体感觉很简洁。当然如果没有特殊需要的话,还是读集合类juc之类的源码比较有价值一些。
最后*惯再附一图:
熬夜变垃圾!(;´д`)