套接字输入缓冲装置——InternalInputBuffer
互联网的世界很复杂,信息从一端传向另一端过程也相当复杂,中间可能通过若干个硬件,为了提高发送和接收效率,在发送端及接收端都将引入缓冲区,所以两端的套接字都拥有各自的缓冲区,当然这种缓冲区的引入也带来了不确定的延时,在发送端一般先将消息写入缓冲区,直到缓冲区填满才发送,而接收端则一次只读取最多不超过缓冲区大小的消息。
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的更多相关文章
- 套接字输入流——InputStream
输入缓冲装置里面必须要包含读取字符的通道,否则就谈不上缓冲了,这个通道就是InputStream,它属于jdk中java.io包的类,有了它我们就可以从源头读取字符,它的来源可以有多种多样,这里主要探 ...
- Linux Socket 原始套接字编程
对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发 ...
- c 网络与套接字socket
我们已经知道如何使用I/O与文件通信,还知道了如何让同一计算机上的两个进程进行通信,这篇文章将创建具有服务器和客户端功能的程序 互联网中大部分的底层网络代码都是用C语言写的. 网络程序通常有两部分组成 ...
- Python黑帽编程2.8 套接字编程
Python黑帽编程2.8 套接字编程 套接字编程在本系列教程中地位并不是很突出,但是我们观察网络应用,绝大多数都是基于Socket来做的,哪怕是绝大多数的木马程序也是如此.官方关于socket编程的 ...
- 【Python网络编程】利用Python进行TCP、UDP套接字编程
之前实现了Java版本的TCP和UDP套接字编程的例子,于是决定结合Python的学习做一个Python版本的套接字编程实验. 流程如下: 1.一台客户机从其标准输入(键盘)读入一行字符,并通过其套接 ...
- Linux进程间通信(八):流套接字 socket()、bind()、listen()、accept()、connect()、read()、write()、close()
前面说到的进程间的通信,所通信的进程都是在同一台计算机上的,而使用socket进行通信的进程可以是同一台计算机的进程,也是可以是通过网络连接起来的不同计算机上的进程.通常我们使用socket进行网络编 ...
- 002.ICMP--拼接ICMP包,实现简单Ping程序(原始套接字)
一.大致流程: 将ICMP头和时间数据设置好后,通过创建好的原始套接字socket发出去.目的主机计算效验和后会将数据原样返回,用当前时间和返回的数据结算时间差,计算出rtt. 二.数据结构: ICM ...
- 初探网络编程--TCP套接字编程演示
今天看了一下<计算机网络:自顶向下方法>,也就是计算机网络的教材的应用层一章,决定实现以下后面的Java C/S应用程序的例子,用来演示TCP和UDP套接字编程. 程序流程如下: 1.一台 ...
- C语言与套接字
我们已经知道如何使用I/O与文件通信,还知道了如何让同一计算机上的两个进程进行通信,这篇文章将创建具有服务器和客户端功能的程序 互联网中大部分的底层网络代码都是用C语言写的. 网络程序通常有两部分组成 ...
随机推荐
- 将 Hexo 个人博客同时部署到 GitHub 和 Coding 上
一.将个人博客托管到 GitHub 上 关于如何快速搭建自己的个人博客,如何完善自己的个人博客,什么是 GitHub ,如何将自己的博客代码托管到 GitHub 上面等等问题,我之前写过三篇文章已经做 ...
- angularJS入门笔记
1.debug调试工具:batarang2.ng指令 1.ng-app=" " 定义angularJS的使用范围:----main方法,入口 ng-app="myModu ...
- Vue-起步篇:Vue与React、 Angular的区别
毋庸置疑,Vue.React. Angular这三个是现在比较火的前端框架.这几个框架都各有所长,选择学习哪种就得看个人喜好或者实际项目了.相比之下, Vue 是轻量级且容易学习掌握的. 1.Vue和 ...
- Spring boot集成swagger2
一.Swagger2是什么? Swagger 是一款RESTFUL接口的文档在线自动生成+功能测试功能软件. Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格 ...
- c++ 文件操作详解
C++ 通过以下几个类支持文件的输入输出: ofstream: 写操作(输出)的文件类 (由ostream引申而来) ifstream: 读操作(输入)的文件类(由istream引申而来) fstre ...
- 如何判断页面是qq浏览器还是微信浏览器打开
// 判断是QQ浏览器还是微信浏览器的js代码isWx = function() { var ua = navigator.userAgent.toLowerCase(); return ua.mat ...
- SpringMVC之拦截器实现登录验证
今天回头看之前发的javaweb学习路线图,发现把路线图中的也学的有一半多了,不过还是路漫漫.在前面的博客中有学习过spring的aop,它利用动态代理实现,在springmvc中也是一样,今天使用H ...
- asp.net使用session完成: 从哪个页面进入登录页面,登录成功还回到那个页面
1.在Login.aspx页面Load中加入 if (!IsPostBack && Request.UrlReferrer != null) { Session[ " ...
- 学习在.NET Core中使用RabbitMQ进行消息传递之持久化(二)
前言 上一节我们简单介绍了RabbitMQ和在安装后启动所出现的问题,本节我们开始正式进入RabbitMQ的学习,对于基本概念请从官网或者其他前辈博客上查阅,我这里不介绍基础性东西,只会简单提一下,请 ...
- RDO Stack: Failed connect to server
Issue: When you create an instance, but cannot connect to the VNC Server because of the error messag ...