目录
1.补充知识
1.PORT端口号
1.PORT端口号是为了确认运行的是服务器的哪个具体进程
2.在传输层,UDP和TCP协议的主要工作就是分离报文,将报头的PORT找到,将数据传输给对应服务器的进程中运行
3.在TCP/IP协议中, 用 "源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信(可以通过netstat -n查看);
2.端口号范围划分
1.0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.
2.1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.
3.对应的端口号操作其实不需要端口号也可以执行
3.知名端口号
1.ssh服务器, 使用22端口
2.ftp服务器, 使用21端口
3.telnet服务器, 使用23端口
4.http服务器, 使用80端口
5.https服务器, 使用443
2.UDP协议
1.UDP报头
1.要清楚的是:所谓的协议本质也是一种结构,UDP的协议结构就是如上图。
2.16位UDP长度,表示整个数据报(UDP首部+UDP数据)的最大长度
3.如果校验和出错, 就会直接丢弃
2.UDP****的特点
UDP 传输的过程类似于寄信,发送和接收的永远是一份完整的请求
1.无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接;
2.不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息;
3.面向数据报: 不能够灵活的控制读写数据的次数和数量;也就意味着无法控制传过来的数据的顺序问题。3.UDP****的缓冲区
1.UDP没有真正意义上的发送缓冲区.。调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作;
2.UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;
注意:我们注意到 , UDP 协议首部中有一个 16 位的最大长度 . 也就是说一个 UDP 能传输的数据最大长度是 64K( 包含 UDP 首部). 然而64K 在当今的互联网环境下 , 是一个非常小的数字 . 如果我们需要传输的数据超过64K, 就需要在应用层手动的分包 , 多次发送 , 并在接收端手动拼装 ;4.基于UDP的应用层协议
NFS: 网络文件系统
TFTP: 简单文件传输协议
DHCP: 动态主机配置协议
BOOTP: 启动协议 ( 用于无盘设备启动 )
DNS: 域名解析协议。我们域名访问是先访问指定的域名服务器,找到对应的IP地址随后进行访问。
3.TCP****协议
又叫传输控制协议。
1.TCP报头
报头分离和解包分用
1.由于TCP的标准报头为20字节,那么分离出20字节。此时我们就有标准报文,将他转换为指定的结构,就能提取出信息
2.将4为首部长度提取。所谓的首部长度是表示报头大小的,具体是首部大小乘上4字节。也就是说报头的大小范围在20~60字节。此时具体的报头就确定下来了
3.将端口号读取出,发送给应用层特定的进程完成分用
4.其实报头并没有所谓的有效载荷大小,因为TCP是字节流,它发送数据,另一个主机接收可以知道传过来的数据顺序
bind的作用
bind用于绑定特定端口号的进程和报文。操作系统内部进程不仅仅是通过链表类似形式方便管理的,为了让报文能轻易找到场景bind的进程端口高号,bind的过程就是通过hash算法将端口号和进程映射,那么此时我们如果想要找到曾经的端口号进程,只需要范围哈希表即可
网络协议栈和文件的关系
已知我们能通过bind找到对应的进程,进程由PCB管理,PCB中的文件描述符指向一个文件,该文件就是传输层传来的数据。当我们把传输层的数据拷贝到文件中,也就意味着该数据缓冲到了进程的读写缓冲区中。
2.理解报头
可靠性
1.不可靠的原因:网线太长
2.判断可靠:确认应答,才算可靠。双方通信,对历史信息进行应答保证历史信息是确认接收的,新数据没有应答
工作模式的理解
有两种发送模式,一种是不连续的发送;一种是连续发送
1.该发送模式是连续发送,那么接收到的结果其实很有可能是无序的状态。那么我们需要一个标志来确定先后循环。当然,报头的32位序号就是标识发送的数据顺序。那么应答的数据也需要有确认对应的数据段,也就是报头的32位确认序号。
2.确认序号+1=发送序号,举例假如客户端发送50号序号数据段,那么服务端则是发送51号确认序号确认50号发送序号。这是因为确认序号的定义为:接收方已经接收了确认序号之前的所有数据段,并且之前的数据段是连续的。如果10,11,12号发送过来,接收的确认序号是11,13,那么最终我们只能确认11号之前的全部接收到,并且应答,后面不能确定
3.一个报头同时有序号和确认序号是为了全双工。应答的一端也可以发送其他数据让另一个接收。
发送数据大小限制
作为发送方其实有一点需要考虑,就是接收方是否有足够的大小接收数据。但是这一点还是不够的,因为发送和接收,使得缓冲区的大小是动态变换的。并且不知道对方主机的缓存区大小,所以报头的16位窗口就是用来表示自己主机的传输层接收缓冲区大小的情况。那么一旦发送给另一端主机关于自己主机的缓冲区大小,发送的数据就会根据缓冲区进行调整。
标志位
tcp报文有各种类型,面对不同的tcp,接收方需要有不同的处理方法
不同的标志位表示不同的tcp类型
标志位
ACK : 确认号是否有效,用于应答
SYN : 请求建立连接; 我们把携带SYN标识的称为同步报文段
FIN : 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走ACK用于应答发送方的数据;SYN用于握手;FIN用于挥手,PSH用于将接收端的tcp接收缓冲区尽快处理。
RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段RST的作用是:在三次握手和四次挥手以及通信中时,建立连接失败了。发送发发送请求,但是接收方不认为已经连接成功,那么就要发送RST进行三次握手重新建立连接
URG: 紧急指针是否有效,需要被尽快读取的数据段
接收数据顺序
1.由于从接收缓冲区中读取的数据是先来先处理的,也就是说内部的结构是队列。
2.那么每次传过来的数据是按照传输的先后顺序排列的
3.URG标志位一旦为真,表示需要被尽快读取的数据段,那么该数据段就会插队,优先被上层处理。而这个紧急指针其实是一个有效载荷的偏移量,偏移到指定的位置确认优先被处理。这样的数据为带外数据,但是只有一个字节。用于一些检查,进程是否还在操作某个模块可以用这个带外数据进行检查,而不需要进行漫长的处理字节流等待
4.丢包
丢包:数据发送过程中由于各种原因丢失
1.发送方将发送缓冲区的数据发送给接收方,接收方得到的数据都是由序号的,必须保证数据的完整性
2.对于发送方而言,其实没有所谓的真正丢包一说,因为无法真正的确定。唯一能界定的标准就是时间上的维度,假设超过设定时间就默认判断为丢包。
3.那么丢包了就需要发送方重新传,所以发送方在一段时间内数据是不可以立马删除的。知道数据被应答说明发送成功,此时才不需要之前的数据,由操作系统自动释放。
4.超过的时间也是不固定的,它是根据网络情况浮动的
5.三次握手四次挥手
1.三次握手
1.客户端将报头的SYN置为1请求连接,发送报文
2.服务器接收,将报文的STN置为1请求连接和ACK置为1应答客户端报头SYN发送
3.客户端将报文的ACK置为1,应答服务端报头SYN发送
服务端和客户端建立连接的时间有先后顺序,并且三次握手不一定百分百成功
三次握手的原因:
1.我们要知道链接是要被记录下来的,那么也就意味着需要被管理起来。OP管理链接先描述后组织是需要成本的。
2.如果只一次握手就算成功链接。那么客户端可以不断的向服务端发起SYN请求,服务端就要不断的管理已经建立好的链接。SYN链接直接占着服务器的管理空间,使得可用的链接资源越来越少。这样的问题就出现了SYN洪水
3.如果两次握手。服务端发送ACK如果客户端没收到,那么客户端仍然认为链接未建立,不断发送SYN链接,此时就会出现上面的情况
4.三次握手成功的原因:三次握手是用最小的成本验证全双工的通信链接是已经被建立的,客户端和服务端只要收到ACK应答就说明对方可以发送数据给自己主机;其次,三次握手可以有效防止单机进行对服务器进行攻击。[服务器招到攻击,本身tcp握手就无法解决。即SYN链接这种半链接也会消耗资源]
ddos攻击:使用多台主机(肉鸡)同时访问服务器,使得服务器链接资源完全被消耗而崩溃
5.偶数次一定失败,因为客户端的应答有时候无法被确定,造成和两次握手情况一致;超过三次握手没有必要,因为浪费时间。
2.四次握手
断开连接是双方的事情,所以两边都需要告知对方,自己不想发消息给对方
1.客户端发送FIN给服务端
2.服务端对客户端进行ACK
3.服务端发送FIN给客户端
4.客户端对服务端进行ACK,客户端不发消息指的是不发用户数据,而不是底层的链接消息
此外:tcp不知道数据是否已经发完了,但是用户清楚,因为上层会将套接字文件close。只要close了,那么就说明不在通信。
面向链接之所以保证可靠性是因为:三次握手保证了双方通信链接结构的完整,tcp的各种策略,重发、丢包等决策保证数据的完整性。二者同时实现数据传输的可靠性。
3.各个阶段的状态
建立连接一定是客户端来请求服务端链接
1.最开始服务端为CLOSED状态通过创造套接字,bind套接字;服务端为LISREN状态,将套接字变为监听套接字,再开启accept创造套接字用于接收客户端
2.客户端第一次握手SYN_SENT状态;服务端接收到变为SYN_RCVD,发送SYN+ACK;客户端接受到则链接成功,发送ACK;服务器接收到也变为链接成功
断开连接双方都可以
1.谁先发出FIN,谁先变为FIN_WAIT1状态;另一方收到就变为CLOSE_WAIT等待,并且发送ACK
2.接收方FIN_WAIT1状态变为FIN_WAIT2。CLOSE_WAIT方发送FIN变为LAST_ACK状态等待ACK;FIN_WAIT2状态变为TIME_WAIT
3.最后LAST_ACK成为CLOSED状态,不在发送消息。TIME_WAIT状态一端过了一段时间后被动CLOSED(因为LAST_ACK需要等ACK,重新发)
注意:CLOSE_WAIT状态有风险,因为如果另一方不发送ACK进行应答,那么本地就可能会有大量的CLOSE_WAIT文件。也就说明之前的接收套接字没有被回收,文件描述符泄漏。其他客户端无法链接。所以经过一段时间CLOSE_WAIT状态会自动CLOSED,一般最长时间为2MSL(最大数据段在网络的存活时间)
服务器重启
1.服务器有时候可以立即重启,有时候不能立即重启的原因是:由于此时服务器的bind失败,因为虽然绑定断开,但是服务器链接还没有结束,端口仍然存在所以不能立即重启
2.服务器不能立即重启有缺点,不能立即重启会导致服务器不能让客户端链接,导致企业财产流失
3.设置重启
cppint opt = 1; setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //案例 void initServer() { // 1. 创建socket文件套接字对象 _listensock = socket(AF_INET, SOCK_STREAM, 0); if (_listensock < 0) { exit(SOCKET_ERR); } // 1.1地址复用 int opt = 1; setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 2. bind绑定自己的网络信息 struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = INADDR_ANY; if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0) { exit(BIND_ERR); } // 3. 设置socket 为监听状态 if (listen(_listensock, gbacklog) < 0) // 第二个参数backlog后面在填这个坑 { exit(LISTEN_ERR); } }
6.流量控制
流量控制:接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应.因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制
1.三次握手双方就交换了窗口大小
2.两边在链接后也会不定期检查对方的窗口大小进行动态的网络吞吐
7.滑动窗口
1.应用层缓冲区会将自己的数据发送给tcp层的缓冲区中。
2.tcp层的传输缓冲区中存在滑动窗口。滑动窗口可以视作是一个数组,用于存放不同的数据。滑动窗口大小通过start和end的两个标志位进行调节。
3.start之前的数据表示已经被发送并且已经得到应答的数据;滑动窗口的数据就是已经发送但是未应答的数据;end后有未发生的数据以及空数据的空间
4.start的移动是向右移动,不过前提是当前指向的位置数据是已经被应答的。我们要知道确认序号是指定的数据序号+1,确认序号的定义就是表示当前准确能被接收的数据的序号是确认序号之前的所有数据。那么一旦应答中确认了当前的确认序号的大小,start标记位就会被移动到指定未应答的区域。如果三次发送应答的数据都是一样的,说明此时应答只能确定到当前位置,而滑动窗口中的数据有极高概率有丢失,那么此时就会需要重新传数据。重传的数据一定来自于滑动窗口内部。
5.end标志位,如果当前接受方的应答中表示自己的缓冲区没有剩余空间存储数据,那么end就不会移动,直到有空间。如果有内存,end会调整发送的大小,策略则是要进一步阐述(网络阻塞中解释)
6.那么也就是说滑动窗口的大小是不固定的
7.滑动窗口的结构是一个循环队列。故空间不会因为移动而不断变化。
8.滑动窗口的核心作用是用于提高tcp的发送效率。
8.网络阻塞
1.拥塞控制
1.如果在刚开始阶段就发送大量的数据, 可能引发问题。因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能引起雪上加霜的。而不让主机发送大量数据的决策是拥塞控制
2.TCP引入慢启动机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;
2.拥塞窗口
1.网络中会设置一个拥塞窗口,该结构表明网络中传输的数据上限,一旦超过这个数,很有可能出现网络阻塞
2.每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口。所以滑动窗口需要根据窗口大小和拥塞窗口之间比较进行决策。
3.慢启动:发送开始的时候, 定义拥塞窗口大小为1;每次收到一个ACK应答, 拥塞窗口大小翻倍;
4.慢启动的意义:使得网络快速的恢复,一旦恢复就立即快速处理数据
3.最终决策
指数增张过快,所以需要引入慢启动的阈值
1.当 TCP 开始启动的时候 , 慢启动阈值等于窗口最大值 ;
2.在每次超时重发的时候 , 慢启动阈值会变成原来的一半 , 同时拥塞窗口置回14.总结
1.少量的丢包 , 我们仅仅是触发超时重传 ; 大量的丢包 , 我们就认为网络拥塞 ;
2.当 TCP 通信开始后 , 网络吞吐量会逐渐上升 ; 随着网络发生拥堵 , 吞吐量会立刻下降 ;
3.拥塞控制 , 归根结底是 TCP 协议想尽可能快的把数据传输给对方 , 但是又要避免给网络造成太大压力的折中方案
9.应答的其他策略
延迟应答
1.如果接收数据的主机立刻返回 ACK 应答 , 这时候返回的窗口可能比较小。此时接收方不立即应答,先处理缓冲区的数据,等待一段时间有可能会将窗口大小变大,剩余空间多了,那么ACK返回的窗口大小也更大。这样 有利于提高效率
2.延迟应答条件
数量限制: 每隔N个包就应答一次
时间限制: 超过最大延迟时间就应答一次捎带应答
互相发送消息的同时顺带将对方数据的应答ACK也带上,是比较常见的提高效率策略。
11.延申问题
面向字节流
1.应用层的数据传输到传输无非是将数据拷贝到传输层
2.tcp协议传输到接收方,接收方是无法明确报头的。因为tcp协议一视同仁,面向字节流的意思就是读取的时候几字节几字节的读取。并且tcp报文没有记录有效载荷的大小,这是因为序列的存在直接保证数据的顺序发送,而不需要在乎里面的内容是什么。
3.udp不同,udp面向数据包,只有接受到完整报文的说法,而不存在半个这种情况。
4.读取和发送其实都是交给操作系统,而操作系统管理发多少,不在于人的设置。
粘包问题
1.由于tcp面向字节流,我们无法对数据进行分离。也就出现报文读取的不完整性,因为读无法保证,在tcp看来报文与报文之间无所谓的区别,这就是粘包问题。
2.粘包问题的解决不在tcp,而在应用层,应用层可以读取分离出数据,所以需要发送者先规定好报文和报文之间的界限,已到达应用层读取数据后可以进行区分报文的情况
TCP异常
1.进程结束:进程结束之前会将链接断开
2.关机:其实也是一种进程结束,只是操作系统自行帮忙释放的,与进程结束逻辑一致
3.断电,断网:一方断开,另一方不知道并且仍然保持链接,另一方会发送报文进行确认是否还在运行,一旦超时或者接收不到应答就会自己释放链接
listen的第二个参数第二个参数:表示当前文件描述符被套接字占满,其余的一部分套接字等待连接。参数设置表示发送方先于接收方建立链接,而接收方先不链接发送发的个数。
全链接:双方都链接成功
半链接:一方成功。未链接的一方状态为SYN_RECV
1.如果长期未连接双方也还是会断开。
2.队列长度为设置的flag+1