问题:
如下代码底层是如何把响应写到socket的???
一般常用写法为:
// 清空所有的头信息、状态码、数据缓存buffer
response.reset();
response.setContentType("charset=utf-8");
response.setStatus(200);
// 写到缓存区域,如果写的内容大于缓冲区域buffer,会自动flush到网络上,翻过来说,如果内容不多,则先存在缓存中,等内容多了一并写到网络上,这样提高效率
response.getWriter().write(responseString);
// 如果缓存中还剩下没写到网络的内容,则写到网络上
response.getWriter().flush();
response.getWriter().close();
write的流程
org.apache.catalina.connector.CoyoteWriter 类
(1)CoyoteWriter#write() [1]
(2)使用CoyoteWriter持有的OutputBuffer#write()执行
org.apache.catalina.connector.OutputBuffer 类
[1] public void write(String s, int off, int len) throws IOException {
。。。
while(sOff < sEnd) {
// translate的作用是把String类型的s写到缓存CharBuffer cb 中(里面有个char[]数组)
int n = this.transfer(s, sOff, sEnd - sOff, this.cb);
sOff += n;
// 如果数据缓存长度已满,则直接flush,下面详细说明flush
if (this.isFull(this.cb)) {
this.flushCharBuffer();[2]
}
}
。。。
}
[2] private void flushCharBuffer() throws IOException {
// 正真写数据,从char[]中读出
this.realWriteChars(this.cb.slice());[3]
。。。。。。
}
[3] public void realWriteChars(CharBuffer from) throws IOException {
while(true) {
if (from.remaining() > 0) {
// 将char[]缓存成byte[]缓存,实际写到网络上时,都是从byte[]缓存中读取出来的
this.conv.convert(from, this.bb);
if (this.bb.remaining() != 0) {
if (from.remaining() > 0) {
// 写到网络上
this.flushByteBuffer();[4]
continue;
}
if (this.conv.isUndeflow() && this.bb.limit() > this.bb.capacity() - 4) {
// byte[]的缓存写到网络上
this.flushByteBuffer();
}
continue;
}
}
return;
}
}
通过名字就看的出来,是从byte[]中读出再写到网络上
[4] private void flushByteBuffer() throws IOException {
this.realWriteBytes(this.bb.slice());
。。。。。。
}
public void realWriteBytes(ByteBuffer buf) throws IOException {
。。。。。。
// 使用response写buffer
this.coyoteResponse.doWrite(buf); [5]
}
org.apache.coyote.Response 类
[5] public void doWrite(ByteBuffer chunk) throws IOException {
。。。
this.outputBuffer.doWrite(chunk);[6]
。。。
}
outputBuffer的运行类型是一个org.apache.coyote.http11.Http11OutputBuffer类
org.apache.coyote.http11.Http11OutputBuffer 类
[6] public int doWrite(ByteBuffer chunk) throws IOException {
if (!this.response.isCommitted()) {
// ActionCode.COMMIT就是表示数据要提交,发送到网络中
this.response.action(ActionCode.COMMIT, (Object)null); [7]
}
。。。。。。
}
org.apache.coyote.AbstractProcessor 类
[7] public final void action(ActionCode actionCode, Object param) {
AtomicBoolean result;
switch(actionCode) {
case COMMIT:
if (!this.response.isCommitted()) {
this.prepareResponse(); [8]
}
case CLOSE:
this.action(ActionCode.COMMIT, (Object)null);
this.finishResponse();
break;
case ACK:
this.ack();
break;
根据Writer的操作,底层会映射不同的写Http网络的操作,使用这些case来匹配实现
prepareResponse、finishResponse都是使用实现类 org.apache.coyote.http11.Http11Processor 类
[8] protected final void prepareResponse() throws IOException {
。。。
// 实际的发送到网络端
this.outputBuffer.commit(); [9]
}
org.apache.coyote.http11.Http11OutputBuffer 类
[9] protected void commit() throws IOException {
// 使用 SocketWrapperBase 和 ByteBuffer 把数据写到网络中
socketWrapper.write(this.isBlocking(), this.headerBuffer);
}
}
org.apache.tomcat.util.net.NioEndpoint(内部类:NioSocketWrapper)类
最终使用内部类NioSocketWrapper实现写到网络中
protected void doWrite(boolean block, ByteBuffer from) {
this.pool.write(from, socket, selector, writeTimeout);
if (block) {
while(!socket.flush(true, selector, writeTimeout)) {
}
}
再探究下Writer的三个方法:
response.getWriter().write(responseString);
response.getWriter().flush();
response.getWriter().close();
我们的以下三个操作,都会调用到flushCharBuffer;而close方法,还在调用了flushCharBuffer后,调用了coyoteResponse.action(ActionCode.CLOSE, (Object)null);来结束网络

本文深入解析了HTTP响应如何从应用层写入到底层Socket的过程。从CoyoteWriter到OutputBuffer,再到Http11OutputBuffer,直至最终的数据在网络层面的传输,详细介绍了每个环节的实现机制和关键步骤。
5828

被折叠的 条评论
为什么被折叠?



