核心机制:延时应答,捎带应答,面向字节流
延时应答
为了提高传输效率,承接滑动窗口,让传输效率提高一点,让窗口尽可能的大一些(在可靠性的前提下)
不立即返回 ack,而是稍微再等一等
为了给接收方留出一点时间,好能够多消费一些,接受缓冲区的剩余空间更大一点(接受方的应用程序就是消费者)
捎带应答
网络通信中,经常是"一问一答"的模型, 客户端发起 request,服务器返回 response
返回 ack 的时机,是在收到请求之后立即返回的
ack(光TCP报头)只有报头没有载荷报头中比较关键的,ack, 确认序号,窗口大小
发挥响应的时机,需要服务器进行一定的计算可能比较复杂,可能花费比较多的时间
普通的响应报文,ack 这一位是 0 确认序号是无效的,确认序号都是无效的,窗口大小这一位 无效,基于延时应答的基础上,提高传输效率的方案
但是 TCP 会延时应答,一旦推迟,就正好赶上,服务器要返回响应了,就可以直接把 ack 报文和响应数据,做成一个 tcp 数据报返回给客户端就可以了
面向字节流
读取/写入的时候,读写操作有很多方式,非常灵活
读 100 字节
1.一次读 10个字节,10次完成
2.一次读 20 个字节,5次完成
3.一次读 50 个字节,2次完成
粘包问题
接收方分用的时候,去掉报头,把载荷内容放到一个,接受缓冲区中
接受方的应用程序,read 的时候,就有很多种 read 的可能性
read 的可能性:
1)a a a b b b c c c
2)aa ab bb cc c
3)aaa bbb ccc
4)aaab bbbc c
5) aa abb bcc c
怎么 read 是取决于应用程序的代码是咋写的
具体怎么来读,才能确保读到的是一个"完整的应用层数据包"
粘包,粘的是应用层的数据包
TCP 字节的特效,收到多个 TCP 数据报的时候把所有的载荷都给混到一起,放到接受缓冲区里
包的边界比较模糊,就好像是"粘上了一样"
粘包问题就是咱们非常需要关心的问题
1.通过特殊的分隔符,来作为包边界的区分
比如, 约定每个应用层数据包,都已 ; 结尾
包的数据里面,不能包含分隔符,需要找到合适的符号,确保这个符号在正文中不会重复出现,即使 ascil 码表中,也有不少的字符,可以用来作为分隔符的,有一下特殊的"不可见字符"历史遗留问题
在比如,之前写的回显服务器,当时是使用 \n 作为分隔符的
String request = sacnner.next(); => 读到空白符就结束了,空白符是统称,包括但是不限于:空格,换行,回车,制表符,分页符,垂直制表符
writer.println(request) => 发送请求的时候,使用 \n 作为结束标记的,由于是通过控制台来进行控制输入的请求内容的,控制台输入的内容本来就不会包含 \n