文章目录
- 1、端口号
- 2、UDP协议
信息加上应用层报头后,下一步发送到传输层
1、端口号
端口号标识了一个主机上进行通信的唯一一个应用程序。 在TCP/IP协议中,通过源IP,源端口号,目的IP,目的端口号,协议号来标识唯二的两个主机之间的通信。
0 - 1023:知名端口号,HTTP、FTP、SSH等这些广为使用的应用层协议,他们的端口号都是固定的;1024 - 65535:操作系统动态分配的端口号,客户端程序的端口号就是由操作系统从这个范围分配的。有些服务器的端口号是固定的
Linux可通过命令: vim /etc/services来打开一个文件,里面有各个固定的端口号。自己写的程序在使用端口号时要避开知名端口号。
一个进程可以绑定多个端口号,一个端口号不能被多个进程绑定。
netstat命令。命令选项-nltp显示正在运行的网络连接,会显示端口号,去掉n就是显示对应的服务器,-ntp只显示tcp套接字的,有p就显示PID,-np就显示所有协议,-nup显示udp服务,-naup显示所有的udp服务。l仅列出有在监听(Listen)的服务状态。
pidof命令
在登陆云服务器,开始使用bash时,整个服务是由第一行/usr/sbin/sshd来做的,它就是守护进程,守护进程的名字都是d结尾。登陆后,会运行起一个子进程,看第一行的PID就能找到这个子进程,这个子进程通过程序替换把bash加载给用户去使用,加载的过程是将标准输入输出错误重定向到套接字中,然后用户输入的就是在网络中发送,服务端再给出响应。pidof可以获得sshd以及它启动的服务的PID,直接pidof sshd。pidof可以查看进程id,pidof后跟进程名。
2、UDP协议
上图是UDP报头 + 数据,数据就是有效载荷,有效载荷是上层通过send或者write这样的系统调用把数据发送给了传输层。UDP报头总共4字节,前16个比特位是源端口号,后16位是目的端口号。还有4字节,一半表示16位UDP长度,一半表示UDP检验和。
那么UDP的报头和有效载荷如何分离?通过上段得知,UDP报头是固定长度的,而且还知道UDP长度,这个长度减去8字节就是有效载荷的长度。报头中有目的端口号,那么就知道要交付给谁,有效载荷就能上交给应用层了。
了解上面的结构后,接下来再看这个结构对应到实际应当是什么样的报文,以及这些数据信息是怎么封装并发送,解包的。
tcp/ip是属于操作系统的,现在用的系统是Linux,Linux是用C写的,udp也就是C写的,任何报头都是遵守协议的,协议本质就是struct,struct有两个形式,结构体和位段,报头也是struct类型的。
struct udp_header
{
uint16_t src_port;
uint16_t dst_port;
uint16_t udp_len;
uint16_t check;
}
//上结构体,下位段
struct udp_header
{
uint32_t src_port: 16;
uint32_t dst_port: 16;
uint32_t udp_len: 16;
uint32_t check: 16;
}
客户端和服务端都认识udp协议,客户端应用层有一串数据,要拷贝给系统内部,做法就是定义一个缓冲区,用char指针指向它,让指针往后走8个字节,拷贝进有效载荷,也就是应用层的数据;指针回到一开始,给它强转成struct udp_header类型,填写各个成员,比如指针p,((struct udp_header)p)->src_port = …,这时候就完成了封装;服务端收到后,开始提取,也是定义一个char*指针s,s + sizeof(struct udp_header)就指向了有效载荷起始处,拿到了有效载荷,回到起始位置,强转成struct udp_header类型,获取4个成员们,就成功分离,并拿到所有信息了,这也就是解包。
应用层因为协议变化多,所以需要协议配合序列化反序列化;而传输层的TCP和网络层的IP协议定下后就不会怎么变了,所以用结构体就可以,也节省空间。
如果检验和出错,就直接丢弃报文。16位UDP长度表示整个数据报(UDP报头 + UDP数据)的最大长度。
UDP的特点
无连接:知道对端的IP和端口号就可以进行传输
不可靠:没有确认机制,没有重传机制,如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息,直接丢弃报文,也就是丢包了。客户端创建套接字,服务器创建套接字绑定就可以传。
面向数据报:不能够灵活地控制读写的次数和数量。
如果报文不对,UDP的检验和就会出错,UDP层就丢弃这个报文,所以UDP一定会保证报文的正确,然后再发送出去。每一层都是如此,别人发出来什么报文,我这层就一定能得到什么报文,这就是面向数据报。发送端调用一次sendto,发送100个字节,接收端也必须调用对应的一次recvfrom,接收100个字节,而不能循环接收,每一次都接收10个字节。
UDP没有真正意义上的发送缓冲区。调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作;UDP具有接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了,再到达的UDP数据就会被丢弃。不保证顺序就表明了UDP的不可靠性。UDP的socket既能读也能写,这就是全双工,半双工则意味着一次只能读或者写。
UDP协议首部中有一个16位的最大长度,2的16次方个比特位,也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部),然而64K在当今的互联网环境下,是一个非常小的数字,如果我们需要传输的数据超过64K,就需要在应用层手动的分包,多次发送,并在接收端手动拼装。
基于UDP的应用层协议
结束。