我:都一个多月没来这里了呢,这次去拜访一下谁呢……
TCP&UDP:啊,幽灵来了啊!你都很久没来了啊,最近很忙是吗?
我:最近事情是比较多……你们两位是?
TCP:我是传输层的TCP(Transmission Control Protocol,传输控制协议)!旁观这位是同样大名鼎鼎的UDP(User Datagram Protocol,用户数据报协议),你应该都听说过吧?
我:那是自然。你是面向连接的协议,而UDP是无连接的协议;你是可靠的,而UDP是不可靠的;你们的上层协议就是那些应用层协议,你们是应用程序进程和操作系统之间的桥梁……
TCP:不错啊,幽灵,我记得一开始你来这里的时候还什么都不知道呢,结果闹了不少笑话。看来真是“士别三日,应当刮目相看”呢(笑)。不过……你知道为什么吗?
我:什么为什么?
TCP:我是说,你知道为什么我要通过三路握手建立连接呢?为什么我和UDP是应用程序进程和操作系统之间的桥梁呢?为什么我释放连接的时候也要进行三路握手呢(不过也可以通过RST直接单方面释放连接)?
我:……这还真不知道。
TCP:那我还是从头开始说吧。传输层的定位在网络层和应用层之间。网络层及以下层都是对用户隐藏的(用户无法直接与之交互),而且独立于应用程序进程工作;而应用层则是直接面对用户的,应用层协议都是配合对应的具体应用程序进程进行工作的,例如HTTP协议配合浏览器进程,XMPP协议配合IRC(Internet Relay Chat)进程,SMTP配合邮件客户端进程。工作在传输层的软件或硬件被称作transport entity(传输实体),负责实现进程间通信。
而我所在的传输层,一方面不受具体应用程序的制约,另一方面又能和用户直接交互外加对用户屏蔽下层工作细节,从而实现远程进程之间的端到端通信。要做到这一切,就必须处在应用程序进程和操作系统之间。
对了,说到进程之间的端到端通信,这里要引入一个重要概念:SAP(Service Access Point,服务访问点)。要在两个远程进程间建立连接,就必须要设置SAP。传输层的SAP是端口,被称作TSAP(Transport Service Access Point,传输服务访问点);而网络层的SAP是IP地址,被称作NSAP(Network Service Access Point,网络服务访问点)。IP地址只能定位到主机,加上端口之后才能定位到进程,进而实现端到端通信。
然后呢,网络编程中的一个重要人物就要出场了!
Socket:现在是我的专场!我叫socket(套接字),专门负责进程之间的远程通信,程序员们最熟悉我了(笑)。不管是建立连接还是维持连接还是释放连接,都需要我出马才行啊:服务器通过LISTEN开始被动监听,准备建立连接;客户端发出CONNECT命令,主动要求建立连接;服务器接收到命令之后就ACCEPT,连接建立了!然后呢,客户端发送RECEIVE命令,要求收到指定的数据;服务器收到之后就SENT,把指定数据发送出去;最后,服务器和客户端都发送CLOSE命令,释放连接。
这些命令都是通过socket实现的,而且所有的socket都引用了前面提到的SAP以访问对应的进程。
我:快头晕了……
TCP:别急,下面就开始说有意思的事了。你有没有想过,三路握手其实很麻烦啊?为什么不能直接就建立连接呢?比如说直接发送连接建立请求和数据过去,然后马上就处理好了,干嘛还要对方确认再正式建立连接呢?
我:是为了保证可靠吧……具体我也不清楚
TCP:并不是为了保证可靠。可靠的含义是:1,先发送的先到达;2,出现丢包和损坏等数据没被正常送到的情况时,保证重传。
第一点是通过建立虚电路实现的,一个连接只有一条路由路径;而要实现第二点也很简单,一方传输数据和连接请求过去之后要求对方发送确认信息即可。
可是,还有一个很严重的问题没有解决:假设一个客户端给服务器发送了带着数据的连接请求,但是却因为网络拥塞而超时了;那么此时客户端会重新发送之前的请求,这次没有超时,成功建立了连接。
问题在于,一段时间之后最开始的那个请求也到达了,结果就是服务器没法识别出这是雷同请求,然后又建立连接了!
我:这会造成什么严重后果吗?
TCP:会造成极为严重的后果!想象一下这样一个场景:客户端向银行服务器发送请求,要求向一个账户转入一万美元;结果发生了前面所说的状况,那么最终结果就是转入了两万美元,但客户端却一无所知!
我:这听起来真够糟糕的。不过,可以给每个连接都加上独一无二的序列号啊?
TCP:听起来不错,问题在于……服务器出于性能上的考虑,并不会专门建立一个序列号数据库来记住那些连接的不同序列号的。
我:(汗)那就只能先不发送数据,只发送连接建立请求,然后服务器发送确认信息,客户端再发送数据;这样一来,后来雷同请求来了之后,服务器端就会先问客户端是不是真的想要建立连接,然后客户端就会发现异常从而拒绝了。
TCP:没错,就是这样!这一过程就是我的三路握手(three-way handshake)!当然实现的时候就没这么简单了,但基本思路就是这样的!
我:哈哈,被我猜对了啊!
TCP:接下来该说说怎样释放连接了。如果对方主机长时间无响应或者断网了抑或是出现了其他异常状况,另一方就会直接发送RST包单方面释放连接。
但是在正常情况下,当通信完成时,我并不会单方面释放连接的。
我:为什么呢?
TCP:想象一下,一个客户端发送了最后一个请求,然后服务器响应之后就单方面释放连接了,可是响应数据包丢失了。那么,客户端就会请求重传,但此时服务器已经释放了连接,不会再理会客户端了。
我:那么这数据就永远丢失了!
TCP:是啊。不过呢,释放连接比建立连接要难弄的多了。我先说一个故事吧:有一个山谷,山谷里驻扎着一只三人军队,叫A队;两边的山顶上分别驻扎着B队的两个分队,每个分队各两人。B队想要消灭A队,但是如果分队单独行动,那么一定会输的。
我:那就想办法同时行动啊。
TCP:这是自然。问题在于,B队的两个分队要想取得联系,只能派斥候(斥候没有战斗力,不算分队成员)走过山谷去通知对方。而斥候是有可能被抓的,也就是说不保证消息能送到。在无法确定对方是否收到消息的情况下,不管哪个分队都不会行动的。
我:那么,在分队1的斥候到达分队2把消息带来之后,分队2再派斥候把确认消息带过去就行了啊?
TCP:真的吗?带确认消息的斥候也是有可能被抓的!如果说斥候被抓了,那还不是没法确定对方是否收到消息?
我:那就再派斥候把确认消息收到的确认消息传回去(这话说起来怎么这么绕口)……
TCP:还是不行啊!确认消息的确认消息还是有可能送不到啊。最关键的一点在于,最后一个斥候是否到达是无法被确认的;那么送出最后一个斥候的分队就会犹豫对方是不是收到了确认信息,会不会同时攻击A队。一旦这么犹豫了,分队就不会参加攻击行动,那么也就无法战胜A队了。
现在,试着把斥候换成计算机网络中的不可靠通信信道,把攻击命令换成连接释放命令吧。
我:这么说实际上根本就做不到一起释放连接了?
TCP:不,实际情况反倒好办很多:还是拿前面的例子,在完成最后一次数据传递后,客户端发送FIN包,服务器端接收到之后发送FIN+ACK包,同时释放连接;客户端接收到ACK包或者长时间接收不到响应,都会释放连接。
我:相当于不管不顾了。不过既然本来就是个无解的问题,那也只能这么办了。
TCP:啊,这次扯了太多了,对不起,UDP你只能下次再出场了。
UDP:好吧,T!C!P!
科普文链接集合:
http://ift.tt/1G8ShFs
via 细节的力量 http://ift.tt/1bEppto
No comments:
Post a Comment