目录
应用层
HTTP协议
- 负责应用程序之间的数据沟通
- 反序列化:将内存中的二进制数据串转换为结构化的数据或对象
- 序列化:将结构化数据或对象转换为内存中的二进制数据串
应用层协议:保证一端发送时构造的数据,在另一端能够正确的进行解析,这种约定就是应用层协议
有很多现成的,非常好用的协议,比如HTTP(超文本传输协议)
- 网络通信协议: 网络数据传输中数据格式的约定。
- 自定制协议: 我们程序员自己设计的数据传输格式
- 知名协议: http协议
URL
平时俗称的网址就是说的URL
urlencode和urldecode
像/?: 等字符,以及被当做特殊意义理解了,因此不能随意出现,因此参数中的特殊字符必须要转译
转译规则:
将需要的字符转换为16进制,从右到左,取四位,每两位做一位,前面加上%,编码成为:%XY的格式
eg:+ > %2B
HTTP协议格式
http是一个明文字符串传输,分了四个部分:
首行\r\n协议头1\r\n协议头2\r\n\r\n正文
1:首行:请求方法/协议版本 + url/状态码 + 协议版本/状态码描述\r\n
2:协议头:请求的属性,都是一个一个键值对,(冒号空格)间隔的键值对,这些键值对
之间以\r\n来进行间隔
3:空行:\r\n\r\n
4:正文
请求方法 URL(请求资源调用符) 协议版本
请求方法:GET (获取资源为主,也能提交数据)
能够提交的url长度是有限的
POST(提交表单数据为主,也可以获取资源)
提交的数据是在正文中的,正文的长度是没有限制的
区别:有无正文
协议头的格式:
协议头中都是一个个以: (冒号空格)间隔的键值对,这些键值对
之间以\r\n来进行间隔
HOST: ......\r\n
Accept: ......\r\n
\r\n(空行)
正文
eg:HTTP/1.1 302 Moved Temporarily
协议版本 状态码 状态码描述
传输层
端口号
通过五元组(源ip,源端口号,目的ip,目的端口号,协议号)可以用来标识一个通信
(IP协议的协议号为4,TCP的协议号为6)
端口号划分
(1)0~1023:知名端口号,是留着备用的,一把都是用于协议,例如HTTP、FTP、SSH
(2)1024~65535:是操作系统动态分配的端口号,客户端程序的端口号,就是由操作糸统从这个范围来分配的,在TCP与UDP的套接字通信中,客户端的端口号就是在此范围中
认识知名端口号
- ssh服务器,22端口号
- ftp服务器,21端口号
- Telnet服务器,23端口号
- http服务器,使用80端口号
- https服务器,使用443端口号
自己写程序时要避开这些端口号
两个问题
1:一个进程能否bind多个端口号
2:一个端口号能否被多个进程bind
如果进程先绑定一个端口号,然后在fork一个子进程,这样的话就可以是实现多个进程绑定一个端口号,但是两个不同的进程绑定同一个端口号是不可以的
两个工具
netstat
//一个用来查看网络状态的工具
t:仅显示tcp相关项
u:仅显示udp相关项
l:仅列出在监听状态的服务状态
pidof
//查看服务器的进程id(通过进程名)
UDP协议
- 16位UDP长度,表示整个数据报(UDP首部+UDP数据)的最大长度
- 如果校验出错,就会直接丢弃
UDP特点
UDP对数据进行原样发送,不会拆分也不会合并,因此如果数据过长,需要我们用户在应用层进行分包
UDP无法保证包序,需要我们用户在应用层进程排序(编号)
udp传输速度快,应用场景就是实时性要求高但是安全性要求不是很高(比如传输视频)
且不会出现沾包的问题
(udp数据报最大长度是64K)
- 无连接:知道对端的ip和端口号直接进行传输
- 不可靠:没有确认应答和重传机制,网络故障无法发到对方也不会返回任何错误信息
udp不会再传输层进行分段数据太长则被丢弃 - 面向数据报:数据不能够灵活的控制发送的次数和数量,因为数据是一整条一整条发送或接受的,这个因为在
UDP头部中定义了udp数据包的长度,所以每一跳数据都是完整发送或接受的,因为这个长度,因此upd的数据
长度固定,不会出现沾包问题
基与udp的应用层协议:DHCP DNS NFS
UDP缓冲区
- UDP没有真正意义上的发送缓冲区,调用sendto会直接交给内核,有内核将数据交给网络层协议
进行后续的传输动作 - UDP具有接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报的顺序和发送的UDP报顺序一致
如果缓冲区满了,在到达UDP的数据会被丢弃
TCP协议
全称为“传输控制协议”,对数据的传输进行一个详细的控制
- TCP源端口(Source Port):16位的源端口其中包含发送方应用程序对应的端口。源端口和源IP地址标示报文发送端的地址。
- TCP目的端口(Destination port):16位的目的端口域定义传输的目的。这个端口指明报文接收计算机上的应用程序地址接口。
- 头长(Header Length):4位包括TCP头大小,指示TCP头的长度,即数据从何处开始。最大为15,单位是32比特(32-bit word)。
- 保留(Reserved):4位值域,这些位必须是0。为了将来定义新的用途所保留,其中RFC3540将Reserved字段中的最后一位定义为Nonce标志。后续拥塞控制部分的讲解我们会简单介绍Nonce标志位。
- 标志(Code Bits):8位标志位
CWR(Congestion Window Reduce):拥塞窗口减少标志被发送主机设置,用来表明它接收到了设置ECE标志的TCP包,发送端通过降低发送窗口的大小来降低发送速率
ECE(ECN Echo):ECN响应标志被用来在TCP3次握手时表明一个TCP端是具备ECN功能的,并且表明接收到的TCP包的IP头部的ECN被设置为11。更多信息请参考RFC793。
URG(Urgent):该标志位置位表示紧急(The urgent pointer) 标志有效。该标志位目前已经很少使用参考后面流量控制和窗口管理部分的介绍。
ACK(Acknowledgment):取值1代表Acknowledgment Number字段有效,这是一个确认的TCP包,取值0则不是确认包。后续文章介绍中当ACK标志位有效的时候我们称呼这个包为ACK包,使用大写的ACK称呼。
PSH(Push):该标志置位时,一般是表示发送端缓存中已经没有待发送的数据,接收端不将该数据进行队列处理,而是尽可能快将数据转由应用处理。在处理 telnet 或 rlogin 等交互模式的连接时,该标志总是置位的。
RST(Reset):用于复位相应的TCP连接。通常在发生异常或者错误的时候会触发复位TCP连接。
SYN(Synchronize):同步序列编号(Synchronize Sequence Numbers)有效。该标志仅在三次握手建立TCP连接时有效。它提示TCP连接的服务端检查序列编号,该序列编号为TCP连接初始端(一般是客户端)的初始序列编号。在这里,可以把TCP序列编号看作是一个范围从0到4,294,967,295的32位计数器。通过TCP连接交换的数据中每一个字节都经过序列编号。在TCP报头中的序列编号栏包括了TCP分段中第一个字节的序列编号。类似的后续文章介绍中当这个SYN标志位有效的时候我们称呼这个包为SYN包。
FIN(Finish):带有该标志置位的数据包用来结束一个TCP会话,但对应端口仍处于开放状态,准备接收后续数据。当FIN标志有效的时候我们称呼这个包为FIN包。
- 窗口大小(Window Size):16位,该值指示了从Ack Number开始还愿意接收多少byte的数据量,也即用来表示当前接收端的接收窗还有多少剩余空间。用于TCP的流量控制。
- 校验位(Checksum):16位TCP头。发送端基于数据内容计算一个数值,接收端要与发送端数值结果完全一样,才能证明数据的有效性。接收端checksum校验失败的时候会直接丢掉这个数据包。CheckSum是根据伪头+TCP头+TCP数据三部分进行计算的。另外对于大的数据包,checksum并不能可靠的反应比特错误,应用层应该再添加自己的校验方式。
- 优先指针(紧急,Urgent Pointer):16位,指向后面是优先数据的字节,在URG标志设置了时才有效。如果URG标志没有被设置,紧急域作为填充。加快处理标示为紧急的数据段。
- 选项(Option):长度不定,但长度必须以是32bits的整数倍。常见的选项包括MSS、SACK、Timestamp等等,后续的内容会分别介绍相关选项。
另外我们一般称数据链路层的发出去的数据包为帧(frame),
称呼网络层发给链路层的数据包为包(packet),
称呼传输层发给网络层的数据包为段(segment)。
但是正如我们描述所用,段、包、帧也经常统称为数据包或者数据报文。
对应用层来说TCP是一个双向对称的全双工(full-duplex)协议,也就是说应用层可以同时发送数据和接收数据。这就意味着数据流在一个方向上的传输是独立于另一个方向的传输的
理解TIME_WAIT状态
1:TIME_WAIT状态,等待的时间是为了保护主动关闭方,一定要等待,因为如果ack没有到达,他要对人家重传的fin进行再次回复,如果没有等待,那么这时候客户端的响应就不是 ack而是rst,对端因为要等的是ack,所以会认为这是要个错误的报文,而继续等待,造成资源浪费
2:等待需要两个MSL时间是为了让网络上迷途的报文彻底消失,防止对后续造成影响
TIME_WAIT状态时主动关闭方出现的,TIME_WAIT说主动关闭方需要等待两个MSL时间(报文最大生存周期)
主动关闭方在最后一次发送ack之后,有可能这个ack会丢掉,被动方等待一段时间之后,没有收到ack会认为fin请求没有发送过去,然后重新发送一个fin到主动方,如果主动方发送ack之后直接关闭,然后又重新建立socket,后续重发的这个fin有可能就会对这个新的socket造成影响,这不是我们希望看到的,因此我们让主动方多等一会,等待多长时间合适,这里用连个MSL时间,确保ack已经发送到对方,或者就算有fin请求,在2MSL时间后也已经被丢弃,不会再传递到主动发(一来一回所以是两个MSL时间)
解决TIME_WAIT状态引起的bind失败方法
在socket() 和 bind()调用之间插入如下代码:
int opt = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
//选项SO_REUSEADDR为1,表示允许创建端口号相同但是Ip地址不同的多个socket描述符
确认应答ACK机制
每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到那些数据,下一次你应该从哪里发
确认应答机制-----发送的每条数据都需要确认回复一下
超时重传机制
主机1给主机2发送数据后,可能因为网络拥堵等原因,数据无法到达主机2
主机1在等待一个特定时间后会进行重发(如果主机1未收到2的确认答复,也会重发)
因此主机会收到很多重复数据,但是利用序列号很容易去重。
超时重查收-------发送方等待一段时间后还没有回复,就认为传输失败了,将数据进行重传
(超时时间是递增的,次数也有限制,超过了重传次数就认为网络断开了)
为了高性能通信,因此会动态计算这个最大超时时间
linux中以500ms为一个单位进行控制,每次判定都是500ms的整数倍
滑动窗口机制
窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,窗口越大,网络吞吐率越高
滑动窗口机制:(一次发送多少取决于tcp协议中窗口大小字段,窗口大小在进行数据传输前会进行双方协商)
- 以四个字段为例,发送前四个字段时,不需要等待任何ACK,直接发送
- 收到第一个ACK之后,滑动窗口向后移动,继续发送第五个,以此类推
- 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区,来记录还有哪些数据没有应答
只有确认应答过后的数据才能从缓冲区删掉
如果丢包,任何进行重传?
情况1:数据到达,ACK丢了
部分ACK丢了不要紧,可以通过后续的ACK进行确认
情况2:数据包丢了
如果之前某一数据包丢了,之后的应答会一直发送请求丢失的数据包
如果发送端,连续三次收到同样一个应答,就会将数据重发
也叫高速重发控制或快重传
流量控制
接收端处理数据的速度是有限的,如果发送太快就会导致接收端的缓冲区被打满
这时候继续发送就会发生丢包,继而引起一系列的连锁反应
因此TCP支持根据接收能力,来决定发送端的速度,这个机制就是流量控制
- 接收端发现自己缓冲区快满了,就会将窗口设置从一个更小的值通知给发送端
- 接收到之后会减慢自己的发送速度
- 如果缓冲区满了,就会将窗口设置为0,
- 这时发送方不在发送数据,但是接收方要定期发送一个窗口探测数据段,把窗口大小告诉发送端
通过不断地重新设定窗口大小,来告诉对方我能一次接收到多少数据,最终达到一个流量控制的效果
避免因为发送太快,而处理太慢,导致接收方缓冲区塞满造成大量的报丢失重传
拥塞窗口(拥塞控制)
虽然TCP有滑动窗口,但是如果一开始就发送大量的数据,任有可能引发问题,
网络上的计算机很可能已经处于拥堵的网络状态,在不清楚当前网络状态的情况下,贸然发送大量数据,可能雪上加爽。
双方进行窗口大小协商之后,对于发送方还有一个叫拥塞窗口,一开始非常小,模清当前网络状态,探探路,但增长非常快,而实际发送大小取小的那一
个,也就意味着拥塞窗口控制了tcp传输的一个慢启动和快速增长。
- 此处就有一个概念为拥塞窗口
- 每次开始时,定义拥塞窗口大小为1
- 每次收到一个ACK应答,拥塞窗口加1
- 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小作比较,取较小的值做为实际发送窗口
- 为了不增长那么快,因此不能使拥塞窗口单纯的加倍
- 引入一个慢启动的阈值
- 当超过这个阈值时,不在按照指数方式增长,而是按照线性方式增长
- 当TCP开始启动时,慢启动的阈值等于窗口的最大值
- 每次超时重发时,慢启动阈值会变成原来一半,同时拥塞窗口置1
少量丢包仅仅触发超时重传,大量的丢包,我们就认为网络拥塞
延时应答
对处理速度比较自信,对接收到的数据可以快速处理,只要稍微延时一下进行ack答应,那么能够说设置的窗口大小就
尽可能的大,一直保证一个tcp的最大吞吐量
假如是立即回答,这个回复的窗口大小就会比较小
窗口越大,网络吞吐量就越大,传输效率就越高,我们要保证网络不拥塞的情况下尽量提高效率
- 那么所有的包都可以延时应答么?
- 肯定也不是
- 数量限制:每隔n个包就应答一次
- 时间限制:超过最大延迟时间就应答一次
- 不同操作系统有差异,一般n取2,超时时间取200ms
捎带应答机制
在延迟应答的基础上,很多情况下,客户端服务器在应用层是一发一收。这是ACK也可以搭顺风车
将确定应答直接标记在即将发送的数据报内,那么这样不仅传输了数据还对上一次接收到的数据处进行应答(少传输一个
应答)
面向字节流
数据的发送和接受灵活,但是会有粘包问题产生
发送端:为了提高性能,将多个小包和到一块进行发送,但是数据没有边界
接收端:接收到的数据都会放在接收的缓冲区,假如处理的比较慢,导致缓冲区会接受的越来越多淤积合并
如何避免粘包:解决数据无边界的问题
数据定长,特殊字符间隔,tlv格式数据
TCP异常情况
- 进程终止:会释放文件描述符,任然可以发送FIN,和正常关闭没什么区别
- 机器重启:和进程终止相同
- 机器掉线网线断开:接收端认为连接还在,一旦有写入操作,会发现连接不在就会进行reset
TCP也会定时检测对方状态,如果不在就释放连接 - 应用层有些协议也有这样的检测机制,如HTTP长连接中,会定期检测对方的状态。如QQ断线之后也会尝试定时重连
TCP小结
为什么TCP这么复杂?
因为要保证可靠性,同时尽可能提高性能
可靠性:
- 校验和
- 序列号
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
提高性能:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
其他
- 定时器:(超时重传定时器,TIME_WAIT定时器)
基于TCP的应用层协议
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
- 自己写TCP时自定义的应用层协议