6.1 TCP 协议是流式协议
很多读者从接触网络知识以来,应该听说过这句话:TCP 协议是流式协议。那么这句话到底是什么意思呢?所谓流式协议,即协议的内容是像流水一样的字节流,内容与内容之间没有明确的分界标志,需要我们人为地去给这些协议划分边界。
举个例子,A 与 B 进行 TCP 通信,A 先后给 B 发送了一个 100 字节和 200 字节的数据包,那么 B 是如何收到呢?B 可能先收到 100 字节,再收到 200 字节;也可能先收到 50 字节,再收到 250 字节;或者先收到 100 字节,再收到 100 字节,再收到 100 字节;或者先收到 20 字节,再收到 20 字节,再收到 60 字节,再收到 100 字节,再收到 50 字节,再收到 50 字节......
不知道读者看出规律没有?规律就是 A 一共给 B 发送了 300 字节,B 可能以一次或者多次任意形式的总数为 300 字节收到。假设 A 给 B 发送两个大小分别是 100 字节和 200 字节的数据包,作为发送方的 A 来说,A 是知道如何划分这两个数据包的界限的,但是对于 B 来说,如果不人为规定多少字节作为一个数据包,B 每次是不知道应该把收到的数据中多少字节作为一个有效的数据包的,而规定每次把多少数据当成一个包就是协议格式定义的内容之一。
经常会有新手写出类似下面这样的代码:
发送端:
//...省略创建socket,建立连接等部分不相关的逻辑...
char buf[] = "the quick brown fox jumps over a lazy dog.";
int n = send(socket, buf, strlen(buf), 0);
//...省略出错处理逻辑...
2
3
4
接收端:
//省略创建socket,建立连接等部分不相关的逻辑...
char recvBuf[50] = { 0 };
int n = recv(socket, recvBuf, 50, 0);
//省略出错处理逻辑...
printf("recvBuf: %s", recvBuf);
2
3
4
5
为了专注问题本身的讨论,我这里省略掉了建立连接和错误处理的逻辑。上述代码中发送端给接收端发送了一串字符”the quick brown fox jumps over a lazy dog.“,接收端收到后将其打印出来。
类似这样的代码在本机一般会工作的很好,接收端也如期打印出来预料的字符串,但是一放到局域网或者公网环境就出问题了,即接收端可能打印出来字符串并不完整;如果发送端连续多次发送字符串,接收端会打印出来的字符串不完整或出现乱码。不完整的原因很好理解,即对端某次收到的数据小于完整字符串的长度,recvBuf 数组开始被清空成 \0,收到部分字符串后,该字符串的末尾仍然是 \0,printf 函数寻找以 \0 为结束标志的字符结束输出;乱码的原因是如果某次收入的数据不仅包含一个完整的字符串,还包含下一个字符串部分内容,那么 recvBuf 数组将会被填满,printf 函数输出时仍然会寻找以 \0 为结束标志的字符结束输出,这样读取的内存就越界了,一直找到 \0 为止,而越界后的内存可能是一些不可读字符,显示出来后就乱码了。
我举这个例子的目的是希望你能对“ TCP 协议是流式协议”有一个直观的认识。正因为如此,我们需要人为地在发送端和接收端规定每一次的字节流边界,以便接收端知道从什么位置取出多少字节来当成一个数据包去解析,这是我们设计网络通信协议格式要做的工作之一。