端口号
在网络通信过程中,用端口号来标识发送/接受数据的进程。
端口号的分类
-
公认端口(Well Known Ports):范围从0到1023,这些端口紧密绑定于一些服务,如HTTP(80端口)、FTP(21端口)等。这些端口的通讯明确表明了某种服务的协议。
-
注册端口(Registered Ports):范围从1024到49151,使用这类端口号必须在IANA上登记
-
动态和/或私有端口(Dynamic and/or Private Ports):范围从49152到65535,理论上,不应为服务分配这些端口。但实际上,机器通常从1024起分配动态端口。
常用的端口号
FTP | TELNET | SMTP | DNS | TFTP | HTTP |
---|---|---|---|---|---|
21/20(数据) | 23 | 25 | 53 | 69 | 80 |
HTTPS | MySQL |
---|---|
443 | 3306 |
UDP首部格式
用户数据报UDP有2个字段,首部字段和数据字段。UDP的首部开销小,只有8个字节,由4个字段组成,每个字段都是2个字节。
length字段是用户数据报的长度,最小值是8,仅代表首部
如果接受方UDP发现收到的端口号不正确的话,就丢弃这个报文,并由ICMP发送端口不可达的报文给发送方。
UDP的首部校验和,在计算的时候需要在UDP数据报前面加上12个字节的伪首部,伪首部的增加仅仅是为了计算校验和,在计算的时候,把首部和数据部分一起检验,在接收端,当UDP数据包到达时,接收端会按照相同的步骤重新计算校验和,并将结果与数据包中的校验和进行比较。
如果两者相同,则说明数据包在传输过程中没有发生错误;如果不同,则说明数据包在传输过程中可能已损坏,接收端可以选择丢弃该数据包或进行其他处理。
UDP协议首部的16位长度字段确实限制了单个UDP数据报的最大长度为65,535字节(即2^16 - 1,因为0是全零的保留值)。这个长度包括了UDP首部和UDP数据部分,但通常不包括IP首部。因此,实际可以传输的数据(即“有效载荷”)会小于这个值,因为还需要考虑到IP首部和可能的网络层封装开销。
在大多数现代互联网应用中,这个限制确实可能成为一个问题,因为许多应用需要传输远大于64KB的数据。为了解决这个问题,应用层确实需要实现分包(fragmentation)和重组(reassembly)的逻辑。
UDP缓存区
UDP没有像TCP那样的发送缓冲区来维护待发送数据的队列和流量控制。当应用层调用sendto函数发送UDP数据包时,数据会直接交给内核,由内核将数据封装成UDP数据报,并传递给网络层协议(如IP)进行后续的传输。
UDP具有接收缓冲区,但它与TCP的接收缓冲区在功能和行为上有很大不同。UDP的接收缓冲区主要用于临时存储从网络层接收到的UDP数据报,直到应用层调用recvfrom或类似的函数来读取这些数据。
然而,由于UDP是无连接的协议,它不保证数据包的顺序性。也就是说,即使发送端按顺序发送了多个UDP数据包,接收端也可能以不同的顺序接收到这些数据包。此外,UDP也不提供可靠性保证,所以如果数据包在传输过程中丢失或损坏,接收端将不会收到任何通知。
另外,如果UDP接收缓冲区满了,再到达的UDP数据报通常会被丢弃,而不会像TCP那样进行排队等待。这是因为UDP的设计目标就是提供无连接、不可靠的数据传输服务,所以它不会像在TCP中那样进行复杂的流量控制和拥塞控制。
UDP小例子
服务器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024
int main()
{
int server_fd;
char buffer[BUFFER_SIZE] = {0};
if((server_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0)
{
perror("socket faild");exit(-1);
}
struct sockaddr_in server;
int len = sizeof(server);
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(SERVER_PORT);
if(bind(server_fd, (struct sockaddr*)& server, sizeof(server)) < 0)
{
perror("bind faild");exit(-2);
}
//阻塞等待直到指定的长度
if(recvfrom(server_fd, buffer, BUFFER_SIZE, MSG_WAITALL,
(struct sockaddr*)&address,(socklen_t*)& (sizeof(server)) < 0)
{
perror("recvfrom faild");exit(-3);
}
printf("client#%s:%s\n", inet_ntoa(server.sin_addr), buffer);
close(server_id);
}
客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_IP "127.0.0.1"
#define SERBER_PORT 8080
#define BUFFER_SIZE 1024
int main()
{
int sock = 0;
struct sockaddr_in ser;
char buffer[BUFFER_SIZE] = {0};
if(sock = socket(AF_INT, SOCK_DGRAM, 0) < 0)
{
exit(-1);
}
char* msg = "hello";
ser.sin_family = AF_INET;
ser.sin_port = htons(SERBER_PORT);
ser.sin_addr.s_addr = iner_addr(SERVER_IP);
sendto(sock, msg,strlen(msg),MSG_CONFIRM,
(struct sockaddr*)& ser,sizeof(ser));
close(sock);
}
sendto/recvfrom
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
- sockfd:要发送数据的套接字描述符。
- buf:指向要发送数据的缓冲区的指针。
- len:要发送数据的长度(以字节为单位)。
- flags:指定发送操作的可选标志位。通常设置为0,表示不使用任何特殊标志。
- dest_addr:指向目标地址信息的结构体指针,用于指定数据发送的目标地址和端口。
- addrlen:
dest_addr
结构体的长度。
基于UDP的应用层协议
- NFS: 网络文件系统
- TFTP: 简单文件传输协议
- DHCP: 动态主机配置协议
- BOOTP: 启动协议(用于无盘设备启动)
- DNS: 域名解析协议
- RIP:路由信息协议
- 流式多媒体通信
- VoIP是一种通过IP网络进行语音通信的技术。
- RTP是一个用于传输实时数据的协议,如音频、视频或模拟数据。
UDP总结
- 无连接:知道对端的IP和端口号就直接进行传输, 不需要建立连接
- 不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息;
- 面向数据报: 不能够灵活的控制读写数据的次数和数量,应用层交给UDP多长的报文,UDP就照样发送,所以应用程序必选好报文的大小。