网络编程基础 -- Socket:流,TCP连接,TCP可靠性概述

JerryXia 发表于 , 阅读 (981)

如前文所说,不同类型的Socket与不同类型的底层协议族以及同一协议族中的不同协议相关联。而我想说的主要就是TCP/IP协议族中的内容。现在TCP /IP协议族中的主要socket类型为"流套接字(stream socket)"和"数据报套接字(datagram socket)"。如果类比到现实中 stream socket类似于打电话沟通,datagram socket类似于写信沟通。当然,其他协议族当然也有相应的stream socket和datagram socket。

我学习的时候喜欢把类似的事物的异同点相比较,因此我就先从相同点开始说起。

一.Client和Server

无论是打电话还是写信,总会有打电话的一方和写信的一方,相对应的就会有接电话的一方和收信的一方。后者通过回信或者接听电话对请求发出者作出相应。互联网通信也与这个过程类似。客户端 (client)和服务器(server)这两个术语代表了这两种不同的角色:client是通信的发起者,而server则会被动的等待客户端发起信,并对其做出响应。

这里要提出的一点是,区分client和server的关键点是谁是请求的发出者,谁是请求的响应者。因此,client和 server之间的角色是可以互换的。一旦原先的client变成了请求的响应者,那么client就变成了server,反之亦然。server并不是 高配置的装了oracle等数据的高级服务器,client也并不是就是指个人电脑,也不是浏览器。记住,client和server只是对请求的发出者 和请求的响应者的一种抽象概念。

为什么要区分这两者呢?因为一个程序是作为client和server决定了它在于其对等端(peer)建立通信时所使用的Socket API的形式。什么叫对等端?客户端的对等端就是服务器,反之亦然。更进一步来说,是因为client首先需要知道server的所在地,也就是要知道 server的IP和port,但是反之则不需要。因为如果有必要,server可以通过相应的API来获取其client的地址。这与打电话非常类似,被呼叫者不需要知道拨电话者的号码就可以接听电话,如果有需要,办个来电显示就可以知道打电话方的电话号码。只要通信建立成功,server和 client就没有什么区别了。(什么,接听免费?打电话收费?好吧,我承认这个是区别之一)

二.TCP Socket(stream socket)

在TCP/IP协议族中,Stream socket对应是以TCP作为其端对端协议,提供了一个基于连接的可信赖的“字节流”服务。

以下就是我第一次读到类似的话时产生的疑问,一直记录在我的书上:

  1. 什么是流?
  2. 什么是基于连接?连接又是什么?
  3. 什么是可信赖?到底可信赖到什么程度?难道发送端这边断电了接收端仍然还能接到数据?

我相信也会有人产生和我类似的疑问

(1)流简单来说就是一个简单有序的字节序列。注意它是有序的。很容易理解输入流就是以有序的方式写入新的字节,而输出流则是读取字节。因为TCP Socke是基于流的,所以我们可以猜到Socket实例中都会维护着相应的InputStream对象和OutputStream对象。当我们通过 Socket的OutputStream输出数据后,这些字节最终能在连接的另一端通过InputStream读取出来。

明白TCP传送的是一个没有记录边界概念的字节流。这一点很重要,可以总结为TCP中没有用户可见的"分组"概念,它只是传送了一个字节流,我们无法准确地预测在一个特定的读操作中会返回多少字节。最最简单的例子来说,你通过OutputStream send 3次的数据可能OutputStream read 2次就读取完了,而这中间又可能有多种情况存在。这里涉及到TCP的可靠性这一性质,这一点将在接下来的文章里细说。

(2)和字节序列在网络的环境中称作报文一样,这里的“连接”也是一个基于特定上下文所使用的词。要说连接先从"无连接”来说更容易明白。很显然,如果不同的主机间需要通信,那么肯定需要以某种方式连接起来,无论是有线的还是无线的方式。那么无连接通信指的到底是什么?

回答就是,基于连接和无连接指的是一种协议。也就是说,在这里的连接并不指的是物理介质,而是一种传输的方式,说明了如何在物理介质上来传输数据。无论是基于连接还是无连接,都有可能在同一条网线上传送着数据。

对TCP来说,连接完全是"想象"的。它是由端点(client和server)所记忆的状态组成的,并不存在"物理"连接。虽然我们前面用打电话来类比TCP,但打电话的时候是有物理连接的。这 里的连接是指,只要连接的双方认为 TCP 连接存在,并且可以互相发送 IP packet,那么 TCP 连接就一直存在。简单来说,就是一端认为另一端能够接受数据,就记录数据的发送状态并把数据发送出去,除非知道另一端不再能接收到数据了。也就是说,所谓 的连接,就是client认为我能把数据传送到server,server是存在的。而server认为我应该等待client把数据传送过来。而 我们认为连接存在是通过三次握手来实现的。这其中又会产生问题,问题在于连接是想象的,因此并不是实时的。也就是说并不是像你拔掉网线后在右下角的连接提 示就会有个红叉叉出现。当一端出问题的时候,另一端可能仍然会认为连接是存在的。

TCP是一种流协议(stream protocol)。这就意味着数据是以字节流的形式传递给接收者的,没有固有的"报文"或"报文边界"的概念。从这方面来说,读取TCP数据就像从串行端口读取数据一样--无法预先得知在一次指定的读调用中会返回多少字节(也就是说能知道总共要读多少,但是不知道具体某一次读多少)。

为了说明这一点,我们假设在主机A和主机B的应用程序之间有一条TCP连接,主机A上的应用程序向主机B发送一条报文。进一步假设主机A有两条报文要发送,并两次调用send来发送,每条报文调用一次。很自然就会想到从主机A向主机B发送的两条报文是作为两个独立实体,在各自的分组中发送的,如图 2-25所示。

图 2-25

但不幸的是,实际的数据传输过程很可能不会遵循这个模型。主机A上的应用程序会调用send,我们假设这条写操作的数据被封装在一个分组中传送给B。实际上,send通常只是将数据复制到主机A的TCP/IP栈中,就返回了。由TCP来决定(如果有的话)需要立即发送多少数据。做这种决定的过程很复杂,取决于很多因素,比如发送窗口(当时主机B能够接收的数据量),拥塞窗口(对网络拥塞的估计),路径上的最大传输单元(沿着主机A和B之间的网络路径一次可 以传输的最大数据量),以及连接的输出队列中有多少数据。下图只显示了主机A的TCP封装数据时可能使用的诸多方法中的4种。在图2-26中,M11和M12表示M1的第一和第二部分,M21和M22与之类似。如图2-26所示,TCP不一定会将一条报文的全部内 容都放在一个分组(一个包)中传送出去。

图2-26

现在,我们从主机B应用程序的角度来看这种情形。总的来说,主机B应用程序任意一次调用recv时,都不会对TCP发送给它的数据量做任何假设。比如,当主机B应用程序读取第一条报文时,可能会出现下列4种结果。

实际上,可能的结果不止4种,但我们忽略了出错和EOF之类的结果。我们还假设应用程序读取了所有可读的数据。

  1. 没有数据可读,应用程序阻塞,或者recv返回一条指示说明没有数据可读。到底会发生什么情况取决于套接字是否标识为阻塞,以及主机B的操作系统为系统调用recv指定了什么样的语义。
  2. 应用程序获取了报文M1中的部分而不是全部数据。比如,发送端TCP像图2-26D那样对数据进行分组就会发生这种情况。
  3. 应用程序获取了报文M1中所有的数据,除此之外没有任何其他内容。如果像图2-26A那样对数据分组就会发生这种情况。
  4. 应用程序获取了报文M1的所有数据,以及报文M2的部分或全部数据。如果像图2-26B或图2-26C那样对数据进行分组就会发生这种情况。

注意,这里还有一个定时问题。如果主机B的应用程序在主机A发送了第二条报文之后一段时间内都没有读取第一条报文,那么这两条报文都会成为可读的。这就和图2-26B所示情况相同了。这些描述说明,通常,在任意指定时刻,可读的数据量都是不确定的。

需要再次说明的是,TCP是一个流协议(stream protocol),尽管数据是以IP分组的形式传输的,但分组中的数据量与send调用中传送给TCP多少数据并没有直接关系。而且,接收程序也没有什么可靠的方法可以判断数据是如何分组的,因为在两次recv调用之间可能会有多个分组到来。即使接收端应用程序的响应非常及时,也可能会发生这种情况。例如,一个分组丢失了,而且后继分组都安全到达,TCP会将后继分组中的数据保存起来,直到重传第一个分组并正确收到为止。此时,所有数据对应用程序都是可用的。

TCP会记录它发送了多少字节,以及确认的字节,但它不会记录这些字节是如何分组的。实际上,有些实现在重传丢失分组的时候传送的数据可能比原来的多一些或少一些。

对TCP应用程序来说,就没有"分组(包)"这种概念。如果应用程序的设计与TCP对数据的分组方式有所关联,就应该考虑重新设计这个应用程序了。

既然任意一次指定的读操作中返回的数据量都是不可预测的,就必须在应用程序中做好应对这种情况的准备,这些情况下边界都是由应用程序级维护的。

最简单的情况就是定长报文。在这种情况下,只需要读取报文中固定数量的字节就可以了。

TCP是一个可靠的协议。有时人们会说:"TCP能够保证它所发送数据的可靠传输。"这种说法尽管很常见,但却非常不恰当。

首先,只要稍微想一下就会知道这不可能是对的。比如,假设在数据传输的过程中将一台主机从网络上断开,TCP这侧不管做出何种努力,都无法获取其余 的数据。网络确实会中断,主机确实会崩溃,用户确实会在TCP连接仍然活跃的时候关机。这些事件或其他类似的事件都使得TCP无法将它从应用程序收到的数 据传送出去。

但更重要的是,TCP"确保可靠传输"这种说法会对不够谨慎的网络程序员产生微妙的影响。当然,没人真的会相信TCP有某种魔法总是可以将数据安全 地传送到其目的地。但是,相信TCP能够保证可靠传输会让人觉得没必要进行防御性编程,也没必要考虑对故障模式的处理,还是那句话,毕竟TCP可以确保可靠传输。

可靠性--是什么,不是什么

在考虑TCP中可能出现的各种故障模式之前,要弄清楚TCP的可靠性意味着什么。如果TCP不能保证将提交给它的所有数据都传送出去,它又能保证什 么呢?第一个问题是向谁保证?图2-41显示,数据流从应用程序A通过它所在主机的TCP/IP栈向下传输,经过几台中间路由器,通过应用程序B所在主机 的TCP/IP栈向上传输,最后抵达应用程序B。一个TCP段离开应用程序A所在主机的TCP层时,会被封装到一个IP数据报中,传送给其对等实体主机。 它所走的路由可能要经过很多路由器,但如图2-41所示,这些路由器都没有TCP层,它们只是转发了IP数据报。

图2-41

实际上,有些"路由器"可能是拥有完整TCP/IP协议栈的通用计算机,但在这种情况下,它们的路由功能也不涉及TCP或应用层。

我们知道IP是个不可靠的协议,那就应该很清楚,在数据传输路径上,第一个可以讨论确保可靠传输问题的地方就是应用程序B所在主机的TCP层。当一 个段抵达应用程序B所在主机的TCP层时,唯一可以确定的就是这个段已经到达了,但它可能损坏了,可能是重复的数据,可能是错序的,或者是由于其他一些原 因无法接受的。注意,发送端TCP无法对这些抵达接收端TCP的段做出任何保证。

但接收端TCP要向发送端TCP确认,也就是说它ACK的数据以及在此数据之前到达的所有数据在TCP层都已经正确收到了,发送端TCP可以安全地 删除这些数据的副本了。这并不意味着已经将数据传送,或者总是可以将数据传送给应用程序。比如,接收端主机可能在刚刚对数据进行了ACK,但应用程序还没 有将其读走之前,就崩溃了。这个问题值得进一步讨论:TCP向发送端提供的唯一一个数据接收通知就是这个ACK。发送端应用程序无法从TCP自身判断对等 实体应用程序是否真的收到数据了。稍后我们会说明,这是应用程序编写者要弄清楚的一种TCP故障模式。

另一个可以讨论确保可靠传输问题的地方是应用程序B。我们知道,无法保证应用程序A发送的所有数据都会到达。TCP能够向应用程序B保证的是所有到达的数据都是按序且未受损的。

前面已经提到过一种TCP故障模式了:TCP已经ACK了的数据实际上可能不会抵达目的应用程序。这是相当罕见的事情,即使发生这种事情,影响也不会太严重。重要的是网络程序员要知道这种可能性的存在,要对这种或其他所有故障模式可能带来的不良后果有所预防。要避免这样一种看法:TCP会做好一切准备,我们不需要为应用程序协议的健壮性操心。

前面讨论的故障模式有很明确的解决方案。对应用程序来说,明确地知道对等实体收到了某个特定的报文是非常重要的,其对等实体就必须将收到报文的信息通知发送端。通常,这种确认信息都是隐式的。比如,如果客户端向服务器请求一些数据,服务器进行了响应,那么,这个响应就隐式地确认收到了原始请求。

一个更难解决的问题是如果服务器没有对收到的报文进行确认,客户端会怎么做?当然,这与特定的应用程序有很大的关系,很可能没有一个统一的解决方 案。但我们应该注意到,仅仅重发请求很可能无法解决问题:我们不想请求银行进行两次转账。数据库系统使用三阶段提交协议来处理这种类型的问 题,某些应用程序可能会使用类似的策略来确保操作"最多执行一次"。

TCP是一个端对端协议,也就是说它自己要在对等实体之间提供可靠的传输机制。但是,认识到"端点"位于对等的TCP层,而不是对等的应用程序中是非常重要的。要求进行端到端确认的应用程序必须自身提供此项功能(比如WCF实现的WS-RM)

看看其他一些"常见的"故障模式。只要两个对等实体仍然连着,TCP就能保证将数据按序、无损坏地传送。只有连接中断时才会出现故障。什么类型的事件会造成这种中断呢?有三种情况可能引发这类问题:

  1. 永久或临时的网络中断;
  2. 对等的应用程序崩溃;
  3. 运行对等应用程序的主机崩溃。

我们会看到,在发送端应用程序中,这些事件会以不同的方式显现出来。

引用自:

  1. Socket:流,TCP连接,TCP可靠性概述
  2. TCP和流
  3. 关于TCP的可靠性

添加新评论