目录
再谈端口号
端口号范围划分
认识知名端口号
俩个问题
netstat
pidof
UDP协议
UDP的特点
面向数据报
UDP的缓冲区
TCP协议
封装和解包
理解TCP报头和位段
理解可靠性
序号和确认序号
TCP如何做到全双工
端口号(Port)标识了一个主机上进行通信的不同的应用程序;
在TCP/IP协议中, 用 "源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信(可以通过netstat -n查看)
输入netstat -nltp,0.0.0.0代表任意IP,Proto:使用的传输协议
0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.
1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.
我们可以看到SID和PID相等,这就是守护进程。
有些服务器是非常常用的, 为了使用方便, 人们约定一些常用的服务器, 都是用以下这些固定的端口号:
ssh服务器, 使用22端口
ftp服务器, 使用21端口
telnet服务器, 使用23端口
http服务器, 使用80端口
https服务器, 使用443
执行下面的命令, 可以看到知名端口号,cat /etc/services
我们自己写一个程序使用端口号时, 要避开这些知名端口号
一个进程是否可以bind多个端口号?可以
一个端口号是否可以被多个进程绑定?不能,通过端口号映射到进程,当一个端口号绑定多个进程时,数据不知道要被交付给哪个进程。
netstat是一个用来查看网络状态的重要工具.
语法: netstat [选项]
功能:查看网络状态
常用选项:
n 拒绝显示别名,能显示数字的全部转化成数字
l 仅列出有在 Listen (监听) 的服務状态
p 显示建立相关链接的程序名
t (tcp)仅显示tcp相关选项
u (udp)仅显示udp相关选项
a (all)显示所有选项,默认不显示LISTEN相关
在查看服务器的进程id时非常方便.
语法: pidof [进程名]
功能:通过进程名, 查看进程id
进程的标准输入和命令行参数
杀掉某个进程,注意我们平时杀掉某个进程时是kill -9 pid(这种方式叫做命令行参数)
用pidof查出进程pid,经过管道把pidof的输出,做为指令当作kill -9的输入,即以命令行参数的方式把pid交给kill,不能以标准输入的方式交给kill,xargs是将标准输入转化为命令行参数
应用层把数据向下输送,传输层也会给应用层向上交付数据。
几乎任何协议都要解决俩个问题:a.如何分离(封装)b.如何交付
如何分离(封装):采用固定长度的报头(前8个字节)
如何交付:由于报头长度固定,所以可以将报头(下图中8字节长度)和有效载荷分离(下图中的数据),分离完之后可以从报头中提取有效的属性,如报文的16位目的端口号,再根据报头中的16位端口号,进行向上交付,因为(应用层)进程bind了端口号。
为什么我们再应用层编写代码的时候,每一次写端口号的时候,都喜欢uint16_t?因为协议用的端口号就是16位的。
udp是如何正确的提取整个完整报文的?根据固定长度的报头提取到16位udp长度,16位udp长度-8的结果就是有效载荷的长度。
UDP是具有将报文一个一个正确接受的能力的。UDP是面向数据报的。
16位UDP长度, 表示整个数据报(UDP首部+UDP数据)的最大长度;
如果校验和出错, 就会直接丢弃
UDP报头其实就是一个结构体类型(位段)
封装报头的过程
内核层中有报头对象,之后把应用层数据拷贝到内核层,再给加一层报头即可。
当提取这些内容时,可以用一个void*指针
提取报文的时候给指针+报头长度,让指针走到报文这里,再用strcpy提取有效载荷,基本原理是这样,但实际OS要复杂的多,这样写只是为了便于理解。
UDP传输的过程类似于寄信.
无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接;
不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息;
面向数据报: 不能够灵活的控制读写数据的次数和数量;
应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并;
用UDP传输100个字节的数据:
如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个字节; 而不能循环调用10次recvfrom, 每次接收10个字节;
UDP没有真正意义上的 发送缓冲区. 调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作;
UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;
UDP的socket既能读, 也能写, 这个概念叫做 全双工
如何理解send/write/recvfrom/write/read/recv/send...这些IO类接口?
这些函数本质都是拷贝函数。其实是把数据由用户层拷贝到了内核层缓冲区当中,拷贝完成之后,函数才会返回,同理当用户层收数据时,是把数据从内核层拷贝到了用户层。
内核层缓冲区都是传输层协议提供的。内核层什么时候发数据,发多少?由OS来关心,传输层来确定发多少,什么时候发,发错了怎么办。
UDP全双工(读写可同时进行),半双工(读写不能同时进行)。
UDP使用注意事项
我们注意到, UDP协议首部中有一个16位的最大长度. 也就是说一个UDP能传输的数据最大长度是64K(包含UDP首
部).
然而64K在当今的互联网环境下, 是一个非常小的数字.
如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装;
基于UDP的应用层协议
NFS: 网络文件系统
TFTP: 简单文件传输协议
DHCP: 动态主机配置协议
BOOTP: 启动协议(用于无盘设备启动)
DNS: 域名解析协议
当然, 也包括你自己写UDP程序时自定义的应用层协议;
TCP全称为 "传输控制协议(Transmission Control Protocol"). 人如其名, 要对数据的传输进行一个详细的控制;TCP报头的标准长度是20字节。
源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去;
32位序号/32位确认号: 后面详细讲;
4位TCP报头长度: 表示该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部最大长度是15 * 4 = 60
6位标志位:
URG: 紧急指针是否有效
ACK: 确认号是否有效
PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段
SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
16位窗口大小: 后面再说
16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也
包含TCP数据部分.
16位紧急指针: 标识哪部分数据是紧急数据;
40字节头部选项: 暂时忽略
先拿出TCP报文的前20个字节即报头,我们看到其中还有一个字段叫选项,TCP报头是边长的,报头可以携带通信时的一些选项,选项也属于报头。
TCP报头里有一个4位首部长度(包括20字节和选项长度), 范围:0000~1111即0~15,单位是4字节,即共60字节,整个TCP报头最大是60字节,具体是多少要看选项长度,4位首部长度范围(在标准20字节的基础上)即【5,15】,即【0101,1111】,如果报头没有带选项,4位首部长度就是0101。
解包过程:1.提取20字节 2.根据标准报头,提取4位首部长度*4,若结果为20则代表报头读完了。否则就代表有选项,即提取4位首部长度*4-20结果就是选项长度。至此读完了报头。剩下的都是有效载荷。
遗留问题:TCP是没有整个报文的大小,或者有效载荷。因为是面向字节流的。无法判定报文和报文的边界,也不需要判定,TCP只需要把所收到的所有数据拿走,剩下的交给上层,至于数据在2进制字节流当中,该如何被解释这是由应用层关心的。
根UDP理解方式一样,都是由位段构成。
不可靠的原因:距离变长了。
OS内部不谈协议,TCP/IP,而网络要谈,因为网络距离变长了。
可靠性:如A发消息B能收到。
网络中不存在100%可靠的协议。但是在局部上能做到100%可靠,如发送某条消息之后,对方正确回应,这就保证了刚才发送消息的可靠性。
TCP协议的确认应答机制:只要一个报文收到了对应的应答,就能保证我发出的数据对方收到了。
客户端一次可能向服务端发送多个报文,发送的顺序,不一定是接收的顺序。客户端如何确认,哪一个应答对应哪一个请求?
每一个报文,一定是携带了完整报头的TCP报文。引入了序号之后,每个报文里都携带了序号,当服务器收到报文之后,应答得时候按照确认序号和序号应答。确认序号和序号对应,确认序号一般都是序号+1.
序号和确认序号作用:将请求和应答一一对应。
确认序号的含义:表示确认序号对应的数字,之前的所有的报文已经收到了。告诉对方下次发送,从确认序号指明的序号发送
如确认序号是1001,就代表1001之前的确认序号对方全收到了,告诉对方下次从1001开始发。
确认序号,表示的含义:确认序号之前的数据已经全部收到。
允许部分确认丢失,或者不给应答,如我们发送1000,2000,3000,其中2000丢失了没收到,对方只能回应1001,不能回应3001,如果回应3001会和基本概念冲突。
为什么要有俩个字段数字(序号和确认序号)?
因为TCP是全双工的,任何一方都能边收边发。如果server既想给对方确认又想同时给对方发送它的消息,往往给对方发送消息,本身就是应答。因此就要俩个字段的数字。
有了序号和确认序号不用担心乱序的问题,因为任何一方都会收到报文,报文会携带序号排序,
TCP是有接收缓冲区和发送缓冲区的,TCP解决的是如何发送的问题,所以被称作传输控制协议。因为TCP有接收和发送缓冲区,如果我们有client和server,我们就有了俩对接收和发送缓冲区。所以TCP支持全双工通信。