原文:https://github.com/robbiehanson/CocoaAsyncSocket/wiki/CommonPitfalls
Common Pitfalls - Don't Be A Victim (常见陷阱 - 不要成为受害者)
多年来,我们注意到,许多问题都是由于对 TCP 协议的普遍混淆而产生的。用知识来武装自己,这样你就不会在未来失去时间。
TCP is a stream
TCP 协议的模型是基于一个无限长的单一连续流的概念。这是一个你需要理解的非常重要的概念,也是我们看到的第一大困惑的原因。
这到底是什么意思,它对开发者有什么影响?
想象一下,你正试图通过套接字发送一些消息。所以你做了这样的事情(在伪代码中)。
socket.write("Hi Sandy.");
socket.write("Are you busy tonight?");
数据如何在另一端显示出来?如果你认为另一端会在两个独立的阅读中收到两个独立的句子,那么你刚刚中了一个常见的陷阱! 喘息吧! 请继续阅读。
TCP 不会把写(write)作为单独的数据。TCP 认为所有的写入都是一个单一的连续流的一部分。因此,当你发出上述写入时,TCP 会简单地将数据复制到它的缓冲区。
TCP_Buffer = "Hi Sandy.Are you busy tonight?"
然后继续以最快的速度发送数据。而为了在网络上发送数据,就需要 TCP 和其他网络协议将这些数据分解成可以在介质(以太网、WiFi 等)上传输的小块。在这样做的过程中,TCP 可能会以任何它认为合适的方式将数据分解。下面是一些例子,说明如何将这些数据拆分并发送。
- "Hi San" , "dy.Ar" , "e you " , "busy to" , "night?"
- "Hi Sandy.Are you busy" , " tonight?"
- "Hi Sandy.Are you busy tonight?"
上面的例子也展示了数据将如何到达另一端。我们先来考虑一下例子1。
Sandy 已经发出了一个 socket.read()
命令,正在等待数据的到来。所以她第一次读取的结果可能是 "Hi San"。Sandy 可能会开始处理这些数据。而在应用程序处理数据的同时,TCP 流继续接收第 2 个和第 3 个数据包。然后 Sandy 又发出一个 socket.read()
命令,这次她得到的是 "dy.Are you"。
这显示了 TCP 连续流的本质。TCP 协议在开发者 API 层面,完全没有数据包的概念,也没有数据的分离。
但这难道不是一个重大的缺陷吗?其他那些使用 TCP 的协议是如何工作的呢?
HTTP 是一个很好的例子,因为它非常简单,而且大多数人都见过它。当一个客户端连接到服务器并发送请求时,它以一种非常特殊的方式进行。它发送一个 HTTP 头,头的每一行都以 CRLF(回车,换行)结束。所以类似这样:
GET /page.html HTTP/1.1
Host: google.com
此外,HTTP 头的结束是由连续两个 CRLF 发出信号的。由于协议指定了终止符,所以很容易从 TCP 套接字中读取数据,直到到达终止符。
然后服务器发送响应:
HTTP/1.1 200 OK
Content-Length: 216{ Exactly 216 bytes of data go here }
同样,HTTP 协议使 TCP 的使用变得简单。读取数据,直到你得到连续的 CRLF。这就是你的头。然后从头中解析出内容-长度,现在你可以简单地读取一定数量的字节。
回到我们最初的例子,我们可以简单的为我们的消息使用一个指定的终结器。
socket.write("Hi Sandy.\n");
socket.write("Are you busy tonight?\n");
如果 Sandy 使用的是 AsyncSocket,那她就太幸运了!因为 AsyncSocket 提供了非常容易使用的读取方法,允许你指定终结器。AsyncSocket 为你做剩下的事情,并会在两个独立的读中传递两个独立的句子!这就是 AsyncSocket 的好处。
Writes(写入数据)
向 TCP 套接字写入数据会发生什么?写入完成后,是否意味着对方收到了该数据?我们至少可以认为计算机已经发送了数据吗?答案是 NO、NO。
回忆一下两件事:
- 所有发送和接收的数据必须被分解成小块才能在网络上发送。
- TCP 处理了很多复杂的问题,比如重新发送丢失的数据包,并提供按顺序传送,以便信息以正确的顺序到达。
因此,当你发出写的时候,数据只是简单地复制到操作系统网络栈中的一个底层缓冲区。这时 TCP 软件就会开始它的魔法,它包括前面提到的所有酷炫的东西,比如。
- 把数据分解成小块,以便在网络上发送;
- 确保丢失的数据包适时地重新发送;
- 确保您的数据以正确的顺序到达远程目的地;
- 监听网络拥塞状况;
- 使用花哨的算法来尽可能快地完成所有这些
所以当你发出 "写入数据" 的命令时,操作系统的反应是 "我有你的数据,我将尽我所能把数据传送到远程目的地。"
但是... 我怎么知道远程目标何时收到了我的数据?
这正是大多数人遇到问题的地方。一个很好的思考方式是这样的:
想象一下,你想给一个朋友发一封信。不是电子邮件,而是传统的蜗牛信。你知道,可以通过邮局实现你的目标。所以,你写了信并把它放在你的邮箱中。后来邮递员过来取信。这时你可以放心,邮局会尽一切努力把信送到你朋友手中。但你怎么知道你的朋友是否收到了信呢?我想,如果信寄回来的时候,上面盖着 "退回发件人 "的印章,你就可以确定你的朋友没有收到信。但如果它没有回来呢?只要知道它进入了你朋友的邮箱就够了吗?(假设这是一封非常非常重要的信。)答案是否定的。也许它从来没有离开过邮箱。也许他的室友捡到信后不小心把它扔掉了。如果室友是负责任的,把信放在你朋友的桌子上呢?这就够了吗?如果你的朋友去度假了,你的信被丢在一堆垃圾信里怎么办?所以,真正知道你的朋友是否收到信的唯一方法就是当你收到他们的回复。
这是对套接字的一个很好的比喻。当你把数据写入套接字时,就像把信放进了邮箱。操作系统就像当地的邮递员,过来取信。一个巨大的邮局系统,将信件路由到目的地,就像网络。而把你的信投递到朋友信箱的邮递员就像你朋友电脑上的操作系统。然后由你朋友电脑上的应用程序从操作系统中读取数据,并对其进行处理(从邮箱中取信,并实际读取)。
那么我怎么知道远程目的地什么时候收到了我的数据呢?这不是 TCP 能告诉你的。充其量,它只能告诉你,信已经送进了他们的邮箱。它不能告诉你应用程序是否已经读取了这些数据并进行了处理。也许远程端的应用程序崩溃了。或者远程用户在读取数据之前退出了应用程序。或者远程用户经历了断电。长话短说,如果需要的话,由应用层来回答这个问题。
网友评论