互联网的世界很复杂,信息从一端传向另一端过程也相当复杂,中间可能通过若干个硬件,为了提高发送和接收效率,在发送端及接收端都将引入缓冲区,所以两端的套接字都拥有各自的缓冲区,当然这种缓冲区的引入也带来了不确定的延时,在发送端一般先将消息写入缓冲区,直到缓冲区填满才发送,而接收端则一次只读取最多不超过缓冲区大小的消息。

Tomcat在处理客户端的请求时需要读取客户端的请求数据,它同样需要一个缓冲区用于接收字节流,即套接字输入缓冲装置,它主要的责任是提供一种缓冲模式从socket中读取字节流,提供填充缓冲区的方法,即将字节读到缓冲区buf,提供解析http协议请求行的方法,提供解析http协议请求头的方法,按照解析的结果组装请求对象Request。

套接字输入缓冲装置的工作原理并不会复杂,如下图所示,InternalInputBuffer包含以下几个变量:字节数组buf、整型pos、整型lastValid、整型end。其中buf是用于存放缓冲的字节流,它的大小由程序设定,tomcat中默认是设置为8 * 1024,即8k字节;pos表示读取指针,读到哪个位置值即为多少;lastValid表示从操作系统底层读取数据填充到buf中最后的位置;end表示缓冲区buf中http协议请求报文头部结束的位置,同时也表示报文体的开始位置。

图中从上往下看,最开始缓冲区buf是空的,将socket操作系统底层的若干字节流读取到buf中,于是状态如②所示,读取到的字节流将buf从头往后进行填充,同时pos为0,lastValid为此次读取后最后的位置值,接着第二次读取操作系统底层若干字节流,每次读取多少是不确定,字节流应该接在②中lastValid指定的位置后面而非从头开始,此时pos及lastValid根据实际情况被赋予新值,假如再读取一次则最终状态为⑤,多出了一个end变量,它的含义是http请求报文的请求行及请求头结束的位置。

为了更好理解如何从底层读取字节流并进行解析,下面将给出简化的处理过程,首先需要一个方法提供读取字节流,如下,其中inputStream代表套接字的输入流,通过socket.getInputStream()获取,其中read方法用于读取字节流,它表示从底层读取最多(buf.length-lastValid)长度的字节流,且把这些字节流填入buf数组中,填充的起始位置为buf[pos]开始,nRead表示实际读取到的字节数。通过对上面这些变量的操作则可以准确操作缓冲装置,成功填充返回true。

publicclass InternalInputBuffer{

byte[] buf=newbyte[8*1024];

int pos=0;

int lastValid=0;

public booleanfill(){

int nRead = inputStream.read(buf,pos, buf.length - lastValid);            

if (nRead >0) {

lastValid = pos + nRead;

}

return (nRead> 0);

}

}

有了填充的方法往下需要一个解析报文的操作过程,受篇幅影响此处只提供对请求行的方法及路径的解析为例子说明,其他的解析按照类似操作即可。http协议请求报文的格式如图,请求行一共有三个值需要解析出来:请求方法、请求url及协议版本,以空格间隔并以回车符换行符结尾。解析方法如下:

publicboolean parseRequestLine(){

int start = 0;

byte chr = 0;

boolean space = false;

while (!space) {

if (pos >= lastValid)

fill();

if (buf[pos] == (byte) ' ') {

space = true;

byte[] methodB = new byte[pos -start];

System.arraycopy(buf, start,methodB,0, pos - start);

String method = newString(methodB);

request.setMethod(method);

}

pos++;

}

while (space) {

if (pos >= lastValid)

fill();

if (buf[pos] == (byte) ' ') {

pos++;

} else {

space = false;

}

}

start = pos;

while (!space) {

if (pos >= lastValid)

fill();

if (buf[pos] == (byte) ' ') {

space = true;

byte[] uriB = newbyte[pos-start];

System.arraycopy(buf, start,uriB ,0, pos - start);

String uri = new String(uriB);

request.setUri(uri);

}

pos++;

}

return true;

}

第一个while循环用于解析方法名,每次操作前必须判断是否需要从底层读取字节流,当pos大于等于lastValid时即需要调用fill方法读取,当字节等于ASCII编码的空格时就截取start到pos之间的字节数组,它们便是方法名的字节组成,转成String对象后设置到request对象中;第二个while循环用于跳过方法名与uri之间所有的空格;第三个while循环用于解析uri,它的逻辑与前面方法名解析的逻辑差不多,解析到的uri最终也设置到request对象里中。

至此,整个缓冲装置的工作原理基本搞清楚了,一个完整的过程是从底层字节流的读取到对这些字节流的解析并组装成一个请求对象request方便程序后面使用,由于每次不能确切保证从底层读取到的字节流,于是通过对pos、lastValid变量进行控制以至于完成对字节流的准确读取接收。除此之外,输入缓冲装置还提供了解析请求头部的方法,处理逻辑是按照http协议的规定对头部解析,然后依次放入request对象中。需要额外说明的是,tomcat实际运行中并不会将请求行、请求头等参数解析后就转化为String类型设置到request,而是继续使用ASCII码存放这些值,因为对这些ASCII码转码会导致性能问题,它的思想是只有到需要使用的时候再进行转码,很多参数没使用到就不进行转码,以此提高处理性能。这方面详细内容在Request章节有涉及。

喜欢研究java的同学可以交个朋友,下面是本人的微信号:

套接字输入缓冲装置——InternalInputBuffer的更多相关文章

  1. 套接字输入流——InputStream

    输入缓冲装置里面必须要包含读取字符的通道,否则就谈不上缓冲了,这个通道就是InputStream,它属于jdk中java.io包的类,有了它我们就可以从源头读取字符,它的来源可以有多种多样,这里主要探 ...

  2. Linux Socket 原始套接字编程

    对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发 ...

  3. c 网络与套接字socket

    我们已经知道如何使用I/O与文件通信,还知道了如何让同一计算机上的两个进程进行通信,这篇文章将创建具有服务器和客户端功能的程序 互联网中大部分的底层网络代码都是用C语言写的. 网络程序通常有两部分组成 ...

  4. Python黑帽编程2.8 套接字编程

    Python黑帽编程2.8 套接字编程 套接字编程在本系列教程中地位并不是很突出,但是我们观察网络应用,绝大多数都是基于Socket来做的,哪怕是绝大多数的木马程序也是如此.官方关于socket编程的 ...

  5. 【Python网络编程】利用Python进行TCP、UDP套接字编程

    之前实现了Java版本的TCP和UDP套接字编程的例子,于是决定结合Python的学习做一个Python版本的套接字编程实验. 流程如下: 1.一台客户机从其标准输入(键盘)读入一行字符,并通过其套接 ...

  6. Linux进程间通信(八):流套接字 socket()、bind()、listen()、accept()、connect()、read()、write()、close()

    前面说到的进程间的通信,所通信的进程都是在同一台计算机上的,而使用socket进行通信的进程可以是同一台计算机的进程,也是可以是通过网络连接起来的不同计算机上的进程.通常我们使用socket进行网络编 ...

  7. 002.ICMP--拼接ICMP包,实现简单Ping程序(原始套接字)

    一.大致流程: 将ICMP头和时间数据设置好后,通过创建好的原始套接字socket发出去.目的主机计算效验和后会将数据原样返回,用当前时间和返回的数据结算时间差,计算出rtt. 二.数据结构: ICM ...

  8. 初探网络编程--TCP套接字编程演示

    今天看了一下<计算机网络:自顶向下方法>,也就是计算机网络的教材的应用层一章,决定实现以下后面的Java C/S应用程序的例子,用来演示TCP和UDP套接字编程. 程序流程如下: 1.一台 ...

  9. C语言与套接字

    我们已经知道如何使用I/O与文件通信,还知道了如何让同一计算机上的两个进程进行通信,这篇文章将创建具有服务器和客户端功能的程序 互联网中大部分的底层网络代码都是用C语言写的. 网络程序通常有两部分组成 ...

随机推荐

  1. C++内存机制中内存溢出、内存泄露、内存越界和栈溢出的区别和联系

    当我们在用C++做底层驱动的时候,经常会遇到内存不足的警告,究其原因,往往是因为内存出现溢出,泄露或者越界等原因.那么他们之间有什么联系吗? 内存溢出(out of memory) 是指程序在申请内存 ...

  2. SQL 收缩数据库日志的几种办法 (2005与2008 略有区别)

    在SQL Server 2000/2005中可以快速压缩日志log文件,通过SQL, 方法一: ---DBTEST 为数据库名,顺序最好别乱.注意:要先截断再清空,最后收缩! backup log D ...

  3. Xcode8 添加PCH文件

    转自默默desire 1.)  打开你的Xcode工程. 在Supporting Files目录下,选择 File > New > File > iOS > Other > ...

  4. c# 复制dataset中table结构跟数据

    DataTable dt = new DataTable(); dt = ds.Tables[0].Copy();

  5. drool-6.5的自学demo

    先丢代码地址 https://gitee.com/a247292980/drools 再丢pom.xml <project xmlns="http://maven.apache.org ...

  6. C语言 字符串 字符串处理操作 字符串与函数

    字符数组的定义和初始化 宏常量+1  强调了字符串的最大强度 推荐忽略长度的定义 不能对所指向的存储单元内容修改,除非是字符串数组的定义 因为指针变量指向的是字符串数组的值,可以被修改. 未初始化 字 ...

  7. 蚂蚁代理免费代理ip爬取(端口图片显示+token检查)

    分析 蚂蚁代理的列表页大致是这样的: 端口字段使用了图片显示,并且在图片上还有各种干扰线,保存一个图片到本地用画图打开观察一下: 仔细观察蓝色的线其实是在黑色的数字下面的,其它的干扰线也是,所以这幅图 ...

  8. k8s Kubernetes v1.10 最简易安装 shell

    k8s Kubernetes v1.10 最简易安装 shell # Master 单节点快速安装 # 最简单的安装shell,只为快速部署k8s测试环境 #环境centos 7.4 #1 初始化环境 ...

  9. 【完整的App项目】颖火虫笔记v2

    好久没写博客了,一方面是因为最近确实很忙,另一方面自己在改进颖火虫笔记这款App,在前面说过该App主要是模仿的印象笔记,(还不知道的,请看:http://blog.csdn.net/htq__/ar ...

  10. Switch控件详解

    Switch控件详解 原生效果 5.x 4.x 布局 <Switch android:id="@+id/setting_switch" android:layout_widt ...