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

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. SpringBoot学习之集成mybatis

    一.spring boot集成Mybatis gradle配置: //gradle配置: compile("org.springframework.boot:spring-boot-star ...

  2. 笔记3 装配Bean总结

    一.自动化装配bean 1.组件扫描 2.自动装配 CompactDisc.java package Autowiring; public interface CompactDisc { void p ...

  3. 你知道src、url、href的全称吗?

    url:Uniform Resource Locator统一资源定位符 src:Source资源 href:Hypertext Reference超文本引用

  4. pupeteer初体验

    官方文档: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagescreenshotoptions puppet ...

  5. Python小代码_2_格式化输出

    Python小代码_2_格式化输出 name = input("name:") age = input("age:") job = input("jo ...

  6. javascript 中如何判断是否是JSON格式的字符串

    var stringToJson = function(value){ try{ eval('('+value+')'); return angular.fromJson(value); } catc ...

  7. ctf writeup之程序员密码

    起因 在v2ex上看到有人发了一篇帖子,说做了一个程序员小游戏,遂试玩了一下. 游戏的地址在这里: http://www.bettertomissthantomeet.com/pages/level. ...

  8. 牛客网编程练习之PAT乙级(Basic Level):1034 写出这个数

    AC代码: import java.util.*; /** * @author CC11001100 */ public class Main { public static void main(St ...

  9. Java中的内存分配

    Java程序在运行时,需要在内存中分配空间,为了提高效率,就对空间进行了不同区域的划分,因为每一片区域否有特定的处理数据方式和内存管理方式. 1.栈存储局部变量 2.堆存储new出来的东西 3.方法区 ...

  10. Docker使用 Supervisor 来管理进程

    Docker 容器在启动的时候开启单个进程,比如,一个 ssh 或者 apache 的 daemon 服务.但我们经常需要在一个机器上开启多个服务,这可以有很多方法,最简单的就是把多个启动命令放到一个 ...