管道通信,全方位360无死角探索管道通信
先提疑问,当我输入url打开网页快乐的浏览信息时,后台都做了什么?
如果你没学过TCP协议,HTTP协议以及管道通信那么就耐心往下看吧,你是没办法回答完美的。
TCP协议深入浅出(流迭、拥塞处理这里就不说了,有兴趣自己查吧):
作为巨复杂古老协议之TCP协议,它属于我们经常把玩的HTTP协议之父,是它的基础。
首先我们需要知道TCP在网络OSI的七层模型中的第四层--Transport层(传输层),IP在第三层---NetWork层(网络层),ARP在第二层--Data Link层(链路层),在第二层上的数据,我们叫Frame(数据帧),在第三层上的数据叫Packet(数据包),第四层的叫Segment(数据段),再上层的都叫Data(数据)。 ps:Http协议属于第七层应用层(神功已练成)。
First,我们需要知道,我们程序的数据首先会打到TCP的Segment中,然后TCP的Segment会打到IP的Packet中,然后再打到以太网Ethernet的Frame中,传到对端后,各个层解析自己的协议,然后把数据交给更高层的协议处理。
TCP头格式
你需要注意这么几点:
1:TCP包是没有IP地址的,那是IP层上的事儿。但是有源端口和目标端口。
2:一个TCP连接需要四个元组来表示是同一个连接(src_ip,src_port,dst_ip,dst_port)准确的说是五元组,还有一个是协议。但因为这里只是说TCP协议,所以,这里就说四元组。
3:图中的4个非常重要的东西
SequenceNumber 包的序号,用来解决网络包乱序(reordering)的问题。
AcknowledgeNumber就是ACK---用于确认收到,用来解决不丢包的问题。
Window又叫Advertised-Window,也就是滑动窗口(Sliding Window),用于解决流控的。
TCPFlag 就是包的类型,主要用于操控TCP的状态机的。
TCP的状态机
其实,网络上的传输是没有连接的,包括TCP也是一样的。而TCP所谓的“连接”,其实只不过是在通讯的双方维护一个“连接状态”,让它看上去好像有连接一样。所以TCP的状态变换是非常重要的。
下面是:”TCP协议的状态机“和”TCP建链接“、”TCP断链接“、”传数据“的对照图,把两个图并排放一起,这样方便对照着看。另外,下面的这两个图非常的重要,你一定要记住(挺复杂的)。
那么为什么建链接要3次握手,断链接要4次挥手呢?链上不就得了?
对于建链接的3次握手,主要是要初始化SequenceNumber的初始值。通信的双方要互相通知对方自己的初始化SequenceNumber--所以叫SYN,全称Synchronize SequenceNumbers。也就是上图中的x和y。这个号要作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输的问题而乱序(TCP会用这个序号来拼接数据)。
对于4次挥手,其实你仔细看是2次,因为TCP是全双工的,所以,发送方和接收方都需要Fin和Ack。只不过,有一方是被动的,所以看上去就成了所谓的4次挥手。如果两边同时断链接,那就会进入到Closing状态,然后到达Time_Wait状态。下图是双方同时断链接的示意图(你可以对照着TCP状态机看)
其实,Time_Wait表示的是你主动断链接,所以,这就是所谓的”不作死就不会死"。试想,如果让对端断链接,那么这个破问题就是对方的了。另外,如果你的服务器是Http服务器,那么设置一个Http的KeepAlive就显得多么重要(浏览器会重用一个TCP链接来处理多个Http请求),然后让客户端去断链接(你要小心,浏览器可能会非常贪婪,他们不到万不得已不会去主动断链接)。
TCP重传机制
TCP要保证所有的数据包都可以到达,所以,必须要有重传机制。
注意,接收端给发送端的ACK确认只会确认最后一个连续的包,比如,发送端发了1,2,3,4,5一共5份数据,接收端收到了1,2,于是回ack3,然后收到了4(注意此时3没收到),此时TCP会怎么办?我们要知道,因为正如前面所说,seqNum和ACK是以字节数为单位,所以ACK的时候,不能跳着确认,只能确认最大的连续收到的包,不然,发送端就以为之前的都收到了。
超时重传机制
一种是不回ACK,死等3,当发送方发现收不到3的Ack超时后,会重传3。一旦接收方收到3后,会ACK回4---意味着3和4都收到了。
但是,这种方式会有比较严重的问题,那就是因为要死等3,所以i会导致4和5即便已经收到了,而发送方也完全不知道发生了什么事,因为没有收到ACK,所以,发送方可能会悲观的认为也丢了。所以有可能导致4和45的重传
对此有两种选择:
一种是仅重传timeout的包,也就是第3份数据。
另一种是重传timeout后的所有数据,也就是第3,4,5这三份数据。
这两种方式有好也有不好。第一种会省带宽,但是慢,第二种会快一些,但是会浪费带宽,也可能会有无用功。但总体来说都不好。因为都在等timeout,timeout可能会很长。
快速重传机制
于是,TCP引入了一种叫Fast Retransmit的算法,不以时间驱动,而以数据驱动重传。也就是说,如果,包没有连续到达,就ack最后那个可能被丢了的包,如果发送方连续收到3次相同的ack,就重传。Fast Retransmit的好处是不用等timeout了再重传。
比如:如果发送方发出了1,2,3,4,5份数据,第一份先到送了,于是就ack回2,结果2因为某些原因没收到,3到达了。于是还是ack回2,后面的4和5都到了,但是还是ack回2,因为2还是没有收到,于是发送端收到了3个ack=2的确认,知道了2还没有到,于是就马上重传2,然后接收端收到了2,此时因为3,4,5,都收到了于是ack回6。
Fast Retransmit只解决了一个问题,就是timeout的问题,它依然面临一个艰难的选择,就是重转之前的一个还是重装所有的问题。对于上面的示例来说,是重传#2呢还是重传#2,#3,#4,#5呢?因为发送端并不清楚这连续的3个ack(2)是谁传回来的?也许发送端发了20份数据,是#6,#10,#20传来的呢。这样,发送端很有可能要重传从2到20的这堆数据(这就是某些TCP的实际的实现)。可见,这是一把双刃剑。
SACK 方法
另外一种更好的方式叫:Selective Acknowledgment (SACK),这种方式需要在TCP头里加一个SACK的东西,ACK还是Fast Retransmit的ACK,SACK则是汇报收到的数据碎版。参看下图:
这样,在发送端就可以根据回传的SACK来知道哪些数据到了,哪些没有到。于是就优化了FastRetansmit的算法。当然,这个协议需要两边都支持。在linux下,可以通过tcp_sack参数打开这个功能。
这里还需要注意一个问题--接收方Reneging,所谓Reneging的意思就是接收方有权把已经报给发送端SACK里的数据给丢了。这样干是不被鼓励的,因为这个事儿会把问题复杂化了,但是接收方这么做可能会有极端的情况,比如要把内存给别的更重要的东西。所以发送方也不能完全依赖SACK,还是要依赖ACK,并维护Time-Out,如果后续的ACK没有增长,那么还是要把SACK的东西重传,另外,接收端这边永远也不能把SACK的包标记为ACK。
---------------------------------------------------------以上,就是你需要了解一些TCP基础知识--------------------------------------------------
HTTP协议
HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。
这个协议应该是我们每天都要用到看到听到的了,非常常用。
Http协议的主要特点可概括如下:
1:支持客户/服务器模式。
2:简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有Get,Head,Post。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得服务器的程序规模小,因而通信速度很快。
3:灵活:http允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
4:无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。(TCP里是不是说了?)
5:无状态:http协议是无状态协议,无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。(TCP里是不是也说了?)
一、HTTP协议详解之URL篇
http(超文本传输协议)是一个机遇请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式,HTTP1.1版本中给出一种持续连接的机制,绝大多数的Web开发,都是构建在HTTP协议之上的Web应用。
HTTP URL(URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息)格式如下:
http://host[":"port][abs_path]
http表示要通过http协议来定位网络资源,host表示合法的internet主机域名或者ip地址,port指定一个端口号,为空则使用缺省端口80,abs_path指定请求资源的URI,如果URL中没有给出abs_path,那么当它作为请求URI时,必须以"/"的形式给出,通常这个工作浏览器自动帮我们完成。
eg:
1:输入:www.baidu.com
浏览器自动转换成:http://www.baidu.com
二、HTTP协议详解之请求篇
http请求由三部分组成,分别是:请求行,消息报头,请求正文。
1:请求行:以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本,格式如下:Method Request-URI Http-Version CRLF
其中Method表示请求方法,Request-URI是一个统一资源标识符,Http-Version表示请求的Http协议版本,CRLF表示回车和换行。
请求方法有很多种,各种方法解释如下:
GET 请求获取Request-URI所标识的资源。
POST 在Request-URI所标识的资源后附加新的数据。
HEAD 请求获取由Request-URI所标识的资源的响应消息报头。
PUT 请求服务器存储一个资源,并用Request-URI作为标识
DELETE 请求服务器删除Request-URI所标识的资源
TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断
CONNECT 保留将来使用
OPTION 请求查询服务器的性能,或者查询与资源相关的选项和需求。
例子:
Get方法:在浏览器的地址栏中输入网址的方式访问网页时,浏览器采用Get方法向服务器获取资源,eg:Get/form.html HTTP/1.1(CRLF)
Post方法要求被请求服务器接受附在请求后面的数据,常用于提交表单。
user=jeffrey&pwd=1234//此行以下为提交数据
HEAD方法和GET方法几乎是一样的,对于HEAD请求的回应部分来说,它的HTTP头部中包含的信息与通过GET请求所得到的信息是相同的。利用这个方法,不必传输整个资源内容,就可以得到Request-URI所标识的资源的信息。该方法常用语测试超链接的有效性,是否可以访问,以及最近是否更新。
2请求报头:请求报头允许客户端向服务器端传递请求的附加信息以及客户端自身的信息。
请求头:(拉勾网的某URLPost)
请求正文:
响应头
响应正文
下面我们来逐个解析报头各参数的含义(可以参照上面一点点看,一会儿你就懂了)
1:请求报头
Accept请求报头域用于指定客户端接收哪些类型的信息。eg:上面那个接收json,javascript等;再比如Accept:image/gif,表明客户端希望接受GIF图像格式的资源;
Accept-Charset请求报头域用于指定客户端接收的字符集。eg:Accpet-Charset:iso-8895-1,gb2312.如果在请求消息中没有设置这个域,缺省是任何字符集都可以接受。
Accept-Encoding请求报头域类似于Accept,但是它是用于指定可接受的内容编码。eg:Accept-Encoding:gzip.deflate.如果请求消息中没有设置这个域服务器假定客户端对各种内容编码都可以接受。
Accept-Language请求报头类似于Accept,但是它是用于指定一种自然语言。eg:Accept-Language:zh-cn.如果请求消息中没有设置这个报头域,服务器假定客户端对各种语言都可以接受。
Authorization请求报头域主要用于证明客户端有权查看某个资源。当浏览器访问一个页面时,如果收到服务器的响应代码为401(未授权),可以发送一个包含Authorization请求报头域的请求,要求服务器对其进行验证。
Host请求报头域主要用于指定被请求资源的Internet主机和端口号,它通常从Http URL中提取出来的,eg:我们在浏览器中输入http://www.songtaste.com/index.html,浏览器在发送的请求消息中,就会包含Host请求报头域,如Host:www.songtaste.com,此处缺省端口号80,若指定了端口号则变成Host:www.songtaste.com:[端口号]
User-Agent 我们上网登录论坛的时候,往往会看到一些欢迎信息,其中列出了你的操作系统的名称和版本号,你所使用的浏览器的名称和版本,实际上,服务器应用程序就是从User-Agent这个请求报头域中获取的这些信息。User-Agent请求报头域允许客户端将它的操作系统,浏览器和其他属性告诉服务器。不过这个报头域不是必须的,如果我们自己编写一个浏览器,不使用User-Agent请求报头域,那么服务器就无法获得我们的信息了。
2响应报头
响应报头允许服务器传递不能放在状态行中的附加响应信息,以及关于服务器的信息和对Request-URI所标识的资源进行下一步访问的信息。
常用的响应报头
Location 用于重定向接收者到一个新的位置。Location响应报头域常用在更换域名的时候。
Server 响应报头域包含了服务器用来处理请求的软件信息。与User-Agent请求报头域是相对应的。
Connection:TCP已经说过,2种状态KeepAlive,Close,其中Close是传完即关闭,这样实现短连接,提高后续响应速度。
请求和响应消息都可以传送一个实体。一个实体由实体报头域和实体正文组成,但并不是说实体报头域和实体正文要在一起发送,可以只发送实体报头域。实体报头定义了关于实体正文(eg:有无实体正文)和请求所标识的资源的元信息。
3实体报头
常用的实体报头
Content-Encoding
Content-Encoding实体报头域被用作媒体类型的修饰符,它的值指示了已经被应用到实体正文的附加内容的编码,因而要获得Content-Type报头域中所引用的媒体类型,必须采用相应的解码机制。Content-Encoding这样用于记录文档的压缩方法,eg:Content-Encoding:gzip
Content-Language
Content-Language实体报头域描述了资源所用的自然语言。没有设置该域则认为实体内容将提供给所有的语言阅读
者。eg:Content-Language:da
Content-Length
Content-Length实体报头域用于指明实体正文的长度,以字节方式存储的十进制数字来表示。
Content-Type
Content-Type实体报头域用语指明发送给接收者的实体正文的媒体类型。eg:
Content-Type:text/html;charset=ISO-8859-1
Content-Type:text/html;charset=GB2312
Last-Modified
Last-Modified实体报头域用于指示资源的最后修改日期和时间。
Expires
Expires实体报头域给出响应过期的日期和时间。为了让代理服务器或浏览器在一段时间以后更新缓存中(再次访问曾访问过的页面时,直接从缓存中加载,缩短响应时间和降低服务器负载)的页面,我们可以使用Expires实体报头域指定页面过期的时间。eg:Expires:Thu,15 Sep 2015 16:23:12 GMT
HTTP1.1的客户端和缓存必须将其他非法的日期格式(包括0)看作已经过期。eg:为了让浏览器不要缓存页面,我们也可以利用Expires实体报头域,设置为0,jsp中程序如下:response.setDateHeader("Expires","0");
--------------------------------------------------这就是HTTP协议的内容-------------------------------------------------------
当你看到这的时候,其实心里就应该有了最上面那个问题的一个大体思路了,这里我们公布一下答案
输入url后台都发生了什么呢?
1:客户端会先查找本地的浏览器缓存,系统缓存,路由器缓存,dns缓存等,如果缓存中有数据,则会先从缓存中找,然后直接显示页面。
2:如果缓存过期了,或者说压根就没找到,那么会向服务器发送一个Get请求(其实也就是SYN包),然后客户端处于等待状态(想想三次握手,其实就是SYN_SENT状态(Synchronize SequenceNumber,还想的起来吗?)这是第一次握手)。
3:这时候服务端接收到请求(syn包),必须确认客户的syn(ack=j+1还想的起来不?是不是串上了?),同时自己也发送了一个状态给客户端(SYN包),然后服务器进入等待状态(SYN_RECV),这是第二次握手。
4:客户端收到服务器的状态,向服务器发送确认包,此包发送完毕,客户端和服务器进入连接状态,就开始传输数据了。
5:服务器接受完数据之后再处理数据,最后返回一个HTML给客户端,这样一个最终我们看到的网页就产生了。
那么我们的管道是干嘛的呢?试想一下,如果我们之前的HTTPRequest经过三次握手传入服务器之后,服务器是怎么处理URI的呢,怎么找到具体的处理静态类或者处理对象的具体重载方法的?要是静态页面那好办,比如在windows server中,我们直接发给IIS,IIS直接就可以处理了。可是如果不是静态HTML呢,要是ASPX呢?
这里我们就说到了管道通信的问题,涉及到了.NET的两大核心类HttPModule和HttPHandler。下面我们来重点介绍。
既然我们是在踏踏实实学.Net,那么就不得不对windows系统仔细研究---引子
当请求一个*.aspx文件的时候,这个请求会被inetinfo.exe进程截获,如果你踏踏实实学习了第一篇,你应该知道,进程里面有哪些东西,对,就是dll程序集,因为进程本身是没有业务逻辑判断的,都需要dll来处理,那么它会交给一个叫ASPNET_ISAPI.dll(非常有名的dll)来处理,这时候ASPNET_ISAPI会通过管道(HTTP PipeLine)将请求发送给ASPNET_WP.exe经常,在ASPNET_WP.exe进程中通过HTTPRuntime来处理这个请求,处理完毕后将结果返回给客户端。
IIS5.X
IIS5核心特征是:IIS允许在一个叫InetInfo.exe的进程上的,所以无论是aspx页面还是html页面都是通过这个进程处理的,其中,由于aspx页面扩展名映射到了ASPNET_ISAPI.dll上,所以如果页面是aspx的话,aspnet_isapi.dll则创建一个aspnet_wp.exe这个work Process ,并在初始化的时候加载CLR,所以这是一个托管环境。
需要注意的几点是:
1:同一台服务器只能运行一个aspnet_wp进程,也就是说所有的asp.net web application都在同一个worker process中,但是不意味着他们共享同一个Context和数据库,asp.net里有个appdomain的概念,每个站点或虚拟目录都对应一个单独的appdomain,每个appdomain是隔离的,有自己独立的context和上下文,所以安全性没问题,所以我们的结论是:asp.net程序是基于appdomain的 而不是基于process的。
2:asp.net isapi不但负责创建aspnet_wp worker process,而且负责监控该进程,如果检测到aspnet_wp的性能降低到某个设定的下限,asp.net isapi将结束掉该进程,在Request来了以后,aspnet_isapi将重新创建新的aspnet_wp.exe
3:由于iis和application运行在他们各自的进程中,他们之间的通信必须采用特定的通信机制。由于之间的通信是同机器不同进程的通信(local interprocess communications),处于性能的考虑,他们之间采用基于Named pipe的通信机制,asp.net_iaspi和进程之间的通信通过他们之间的一组pipe实现,同样处于性能的原因。aspnet_isapi通过一部的方式将Request传到进程中并获得Reponse,但进程则是通过同步的方式向aspnet_isapi获得一些基于server的变量。
--------------------------------------------------------------------------------------------------------------------------------
IIS6.X
IIS6和IIS5有3个非常不同的地方:接受请求(Http.sys),应用程序池和w3wp.exe进程。
在IIS5里,InetInfo.exe负责监听并处理请求,在IIS6里,服务器通过一个新组件(http.sys)来接受请求。
Http.sys在接收到http请求的时候,它会根据IIS中的Metabase查看基于该Request的Application属于哪个Application Pool,如果该Application Pool不存在,则创建。否则直接将Request发送到对应的Application Pool Queue中。
每个Application Pool对应着一个进程:w3wp.exe。在IIS Metabase中维护者Application Pool 和进程的Mapping。WAS(Web Administrative Service)根据这样一个mapping,将存在于某个Application Pool Queue的request传递到对应的进程(如果没有,就创建这样一个进程)。在进程初始化的时候,加载AspNet_ISAPI,ASPNet_ISAPI进而加载CLR。
值得注意的是,这样一看IIS5和IIS6还有一个区别就是,在IIS5里ASP.Net ISAPI创建aspnet_wp 进程,进而加载CLR,但是在IIS6里是w3wp通过应用程序池的map关系运行以后,才加载AspNet ISAPI,然后加载CLR。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
这时候你可能会有2个疑问,我们学习一样新知识,容易不断遇到新挑战,更复杂的知识,我给的建议是,咱们不妨暂且放下要学习的知识,把这些新知识也弄懂,就是先把书读厚一些,然后你会有豁然开朗的感觉,不然总有如鲠在喉的滋味不是吗?
这两个疑问是,ASPNET_ISAPI如何处理请求的呢?CLR又是如何处理的呢?
我们一个一个解决
ASPNET_ISAPI如何处理请求的!
首先得要知道,ISAPI是IIS里面的东西,我们把ISAPI当做一个过滤器来使用,因为IIS本身是不支持动态页面的,也就是说当HTML发送过来的时候,IIS自己就可以处理了,ISAPI就没事儿做了。但是对于比如.asp,.aspx,.php等IIS并不知道要如何处理这些后缀标记,它就会把它当成文本,一丝丝都不处理发送到客户端。为了解决这个问题,IIS出现了一种机制,叫做ISAPI过滤器。它是一个COM组件,我们举个例子
ASP.NET服务在注册到IIS的时候,会把每个扩展可以处理的文件扩展名注册到IIS里面(*.ascx,*.aspx....)。扩展启动后,就根据定义好的方式来处理IIS所不能处理的代码,生成标准的HTML代码,生成后把这些代码加入原有的HTML中,最后把完整的HTML返回给IIS,IIS再把内容发送给客户端。