九、传输层协议
传输层协议有UDP协议、TCP协议等;
两个远端机器通过使用"源IP",“源端口号”,“目的IP”,“目的端口号”,"协议号"来标识一次通信;
9.1端口号的划分
0-1023:知名端口号,HTTP,HTTPS,FTP,SSH等应用层协议,他们的端口号都是固定的;如:ssh使用的是22号端口,ftp(rzsz使用的就是ftp协议)使用的是21号端口,telnet使用的是23号端口,http使用的是80号端口,https使用的是443号端口,smtp(邮件协议)使用的是25号端口;
1024-65535:操作系统动态分配的端口号,比如客户端使用的由操作系统提供的端口号;其实在这些动态分配的端口号中,也是有一些约定好的应用层端口号,如:MySQL使用3306号端口;
在操作系统中有配置文件记录了知名端口号;
vim /etc/services
#可以查看知名端口号;
所以在绑定端口号的时候要注意,避开这些知名端口号;
一个进程可以绑定多个端口号,但是一个端口号只能绑定一个进程;
9.2stat相关指令
netstat
#可以查看本地和网络通信;
-l,列出监听状态的服务;
-t,查看tcp的服务;
-u查看udp的服务;
-a,查看所有服务;
-n,拒绝显示别名;
-p,显示相关进程;iostat
-x,查看设备读写速度awk '{print $2}'
#将第二列过滤;xargs
#将输出的多行信息,以行为单位转换成命令行参数;
#可以搭配起来实现杀死一批进程;pidof name
#直接得到含有name关键字的一批进程的pid;
十、UDP协议
传输层、网络层、数据链路层,都要考虑,1.将报头和有效载荷分离;2.有效载荷应该交付给哪一个上层协议;
10.1UDP报头
1.整个报文的长度是8个字节;使用定长报头的方式实现报头和有效载荷分离;
2.使用16位的目的端口号,将有效载荷上交给对应服务进程;
源端口号是操作系统自动生成的,目的端口号是需要用户传递的,可以定位目的服务,便于服务响应数据个客户端;
16位UDP长度指的是整个报文的长度;通过UDP长度-8就可以得到正文的长度;
16位UDP检验和,当数据出现偏差时,可以检测出来然后丢弃,如果成功就将数据传输给上层协议;尽管是进行了数据检验,但是还是无法保证数据的可靠性;服务端收到的只可能一个完整报文和一个不完整报文;
10.2UDP协议特点
UDP套接字是全双工的;
1.无连接,不需要建立连接就可以进行通信;
2.不可靠传输,a.传输过程当中出现了数据乱序;b.接收缓冲区写满了,之后到达的数据会直接被丢弃;
3.面向数据报传递;
面向数据报:发送几次报文,接收方就需要接收几次报文;不能对报文进行拆分和合并;即每个报文一定是完整的且每次收到的一定是一个报文;不需要对数据进行报头和有效载荷的分离,直接进行反序列化;
10.3UDP的缓冲区
UDP没有真正意义上的发送缓冲区,只有接收缓冲区用来保存来不及处理的报文;因为服务端收到的只可能一个完整报文和一个不完整报文,有对报文的检验,所以不需要发送缓冲区的存在;
10.4UDP注意事项
如果发送的数据大于16位的长度即64kb,这是就需要将数据分成多个64kb的数据块,多次发送报文;
10.5站在操作系统角度理解UDP
在操作系统中位UDP的报头设计了专门的结构化数据类型;类似如下位段结构
struct udp_header
{uint32_t src_port:16; uint32_t dst_port:16; uint32_t length:16; uint32_t check_code:16;
};
操作系统维护了UDP的报文结构体类型,指向了报文封装的一段空间;操作系统用双链表的形式将报文维护了起来;
struct sk_buff{char* start;char* end;char* pos;int type;struct sk_buff* next;
};
当操作系统进行封装报文的时候,会先创建报头对象并且添加报头字段,然后将报头对象放到空间的最开始位置,用户层缓冲区的数据放到报头对象的后面,报文的start指向空间的起始地址,end指向空间的最后位置,pos指向报文的有效部分结尾;
10.6基于UDP的应用层协议
NFS: 网络文件系统 ;
TFTP: 简单文件传输协议 ;
DHCP: 动态主机配置协议 ;
BOOTP: 启动协议(用于无盘设备启动) ;
DNS: 域名解析协议;
十一、TCP协议
TCP叫做传输控制协议,很多应用层协议都会使用TCP协议,因为它的传输保证可靠性还有对高效传输有一定的策略;TCP也是全双工的;
11.1TCP缓冲区的理解
用户在应用层使用应用层协议进行序列化和反序列化,使用系统调用接口对用户层缓冲区和内核缓冲区的内容进行拷贝;而在传输层,只是考虑内核发送缓冲区中有什么并将其可靠高效地发送到远端的接收缓冲区当中,其实也是一种拷贝;
read/recvfrom等接口阻塞是因为,接收缓冲区中没有数据,资源不就绪,操作系统会将服务进程阻塞,等待着有数据了资源就绪,才会进行读取;类似于键盘等硬件资源不就绪,进程阻塞;
对数据的发送是由TCP协议决定的,这体现出了TCP协议的控制特点;
11.2TCP协议的段格式
4位首部长度:包含了选项的长度,即大小是从0-15;4位首部大小的基本单位是4字节,所以大小范围是0-60字节,标准报头20个字节,选项最多是40个字节;当不考虑选项时,一般4位首部长度是5;
使用端口号将有效载荷交给上层;
使用固定长度+自描述字段(4位首部长度)进行有效载荷和报头的分离;
16位窗口大小:返回确认应答时,自己接收缓冲区的剩余空间大小,用于辅助流量控制的;
32位序号:保证数据传输按序到达;TCP是面向字节流的,会将报文从左向右依次拷贝到发送缓冲区中,每一个字节天然就有了编号,即数组下标;其中报文最后一个字节的数组下标将作为32位序号的填充字段;
32位确认序号:确认应答是哪一个报文的应答;32位确认序号填充的是收到报文的序号加1;
6个标记位:区分不同的报文类型,进而使得服务器执行不同的行为;
16位紧急指针:当URG为一时,16位紧急指针才有意义,否则就是无效;表示本次报文紧急数据距报文开始位置的偏移量;因为TCP紧急数据一般只允许一个字节,所以只是使用了一个紧急指针没有相关偏移量;
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//第四个参数可以使用MSG_OOB选项发送带外数据(紧急数据);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//第四个参数可以使用MSG_OOB选项,从正常数据流当中读取带外数据;
紧急数据使用场景,服务端一般对于客户是无法直接使用的,即不可知并且数据使用序号进行排队;当服务端出现了异常,客户端需要知道发生了什么事情,就使用紧急数据让服务端优先处理询问,获取表示服务端当前状态的编号,这样就得知了服务端当前的工作状态;
11.3TCP协议保证可靠性
1.双方通信的时候一定要保证报头信息的完整;
2.可靠性要保证数据不能丢包,当服务端读到速度过慢或者其他原因,使得缓冲区很快写满,导致数据大面积丢包;这时候就需要客户端和服务器协同,控制双方的读写速度,来减少数据丢包;通过控制发送数据的速度,使得读端来得及读取数据,规避大面积丢包问题,这种方式叫做流量控制;
可靠性要保证丢包重传和超时重传,所以理论上是不用进行流量控制的,但是对于一个完整的报文已经到达了目标主机,直接丢弃是不合理的,会使得曾经发送消耗的成本变得没有意义,浪费掉了,控制流量明显比丢包重传更有意义更合理;
3.TCP保证可靠性的最基本的一个特点就是:确认应答(一般不带数据,只有报头),通信双发都要进行确认应答;服务端对于客户端发送过来的所有请求都要进行响应;
当进行TCP网络通信的时候,客户端发送消息必须带一个完整的报头,服务器收到后会向客户端发送一个确认应答,也是要带一个完整的报头;可是双方并不能很好的保证发送和读取的速度均衡,此时客户端会根据服务端的接收缓冲区剩余空间的大小调整发送的速度;客户端根据确认应答返回的响应报头属性中的16位窗口大小(填充着服务端接收缓冲区剩余空间的大小)就可以调整发送的速度;
只要收到了应答就可以保证刚刚我发送出去的消息成功递达了,所以对于没有应答的消息是无法保证可靠性的;双方在发送一条消息和接收到一个应答这种模式下,总会有一个消息或者是应答是新发出去的,不能保证可靠性;
11.3.1TCP最简单最原始的通信方式
由于双方通信要保证的是发送方数据的可靠性,所以不需要考虑接收方发送给发送方的应答是否可靠,只需要知道发送方接收到了应答就已经充分说明了发送方的数据是可靠了;所以 在每一次通信过程中要保证数据可靠是单个方向的,是主动方需要保证可靠;
一段时间内主动方没有收到应答,则会认为数据丢失,对历史数据进行超时重传;为了能够支持重传要在接收到应答之前将数据维持一段时间;
现实生活中常见的不是发送消息收到应答保证发送的消息是可靠的,常见的是主动方发送消息,被动方收到了消息并会发送消息,而此消息间接地表明了主动方消息的可靠性;在TCP通信中不是发送确认应答和主动消息两个报文,而是将两报文合并成了一个报文,这种方式叫做捎带应答;
11.3.2TCP常规的通信方式
TCP原始的通信方式对于主动方如果要发送多个消息,就必须接收到了应答才能发送下一个消息,这样串行的通信效率无疑是非常低下的;原则上发送多个消息,只要保证每一个消息都有应答就行,并不需要串行方式进行;
这种并行的方式存在丢包问题,需要提出策略解决;
对于并行方式的发送消息会存在乱序问题,需要进行解决保证数据的可靠性;使用32位序号就可以保证数据顺序的可靠性;
TCP发送是不可能按照字节去发送的,而是按照数据块发送的;为了保证不乱序需要对每一个数据块设置序号,而这个序号就是数据块最后一个字节的下标;对于一次性发送的多个消息,需要同样多个应答,但是为了区分是哪一个消息的应答,就使用了32位确认序号;
确认序号表示1.主动方发送的序号消息已经被收到了;2.确认序号之前的数据已经全部收到了,如:1001表示包括0-1000的数据已经收到;3.下次发送要从确认序号指定的数字开始发送;
确认序号之前的数据已经全部收到了,这样就允许有少量的应答丢包,只要最后一个应答存在就可以保证多个数据传输是可靠的;
因为双方通信是地位对等的,都需要发送数据然后得到应答,所以要将两个序号区分开;而且对于被动方捎带应答的这种情况可能既要发消息(需要主动方发送确认应答)又要提交应答,即一个报文是具有两种属性的,需要不同的序号进行区分;
11.3.3TCP不同类型的报文
TCP通信是会先建立连接,然后数据通信,最后断开连接,这些过程都会发送TCP报文,这也就意味着报文是有着不同的类型的,使得服务器执行不同的动作;通过使用6个标记位来区分不同的报文类型,进而使得服务器执行不同的行为;
6个标记位:
1.ACK(Acknowledgement),确认序号是否有效,表示是应答报文;大部分的报文默认是被置为一的,因为大部分报文是捎带应答报文;
2.SYN(synchronize),请求连接,表示此报文为同步报文段,建立连接报文;(控制)
3.FIN(finish),表示断开连接报文;(控制)
4.PSH(push),发送方在定期询问的时候会将,此标志位置为一,使得接收方立即将接收缓冲区的数据交付到上层,这种方式有利于提高传输的效率;(控制)
流量控制本质上也是一种同步;如果通信双方的接收缓冲区都满了。就会出现僵持的情况,这时候就需要1.发送方通过定期询问的方式知道对方缓冲区的大小,如果对方发送了应答就说明接收缓冲区有空间了;2.缓冲区被读取的同时向发送方发送应答,使得发送方可以继续发送,相当于唤醒发送方;
5.RST(reset),发送方可能建立好了连接,但是接收方并没有建立好,所以就需要接收方向发送方发送一个报文,RST标志位置为一,使得发送方重新建立连接;
TCP保证可靠性,但并不能保证一定可靠,允许连接失败的;三次握手成功就要建立一个连接,所以会存在大量的连接,操作系统,传输层内部会管理大量的连接(建立和维护连接是有成本的),使用连接来管理信道;
TCP建立连接时要经过三次握手的,第一次握手,客户端要向服务端发送SYN报文,请求建立连接;第二次握手,服务端要向客户端发送SYN+ACK报文,既是对客户端请求的应答,又是向客户端发送建立连接请求;第三次握手,客户端向服务端发送ACK报文,表示对服务端请求的应答;
通信双方发送和接收是有时间差的,对于客户端来说,只要将第三次握手,即对服务端的应答发出去,就说明建立连接成功,因为第三次握手对于客户端是没有应答的;而对于服务端来讲,收到了应答就说明连接建立好了;
因为时间差的存在注定了,通信双发对于建立连接的认知是不一致的;在第二次握手完成时,客户端就会建立连接,创建好结构体对象,继续就向后走,开始发送数据;这样就会导致因为第三次握手的应答报文丢失或者正正常通信中服务器异常关闭,实际上是没有建立好连接的,但是客户端却认为是建立好了连接并发送了数据,所以这时候就需要服务端发送RST报文,让客户端重新建立连接,即释放结构体对象并重新发送应答报文,使得真正地建立好了连接;
6.URG(urgent),紧急标志位,表示16位紧急指针是否有效;
TCP通信一般要求报文按序到达,但是有时候是有插队的需求存在,即优先处理该报文;