UDP的报文结构及注意事项

Posted 哔卟哔卟_: )

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UDP的报文结构及注意事项相关的知识,希望对你有一定的参考价值。

UDP的报文结构及注意事项

🔎UDP的报文结构


图片来自网络


源端口和目的端口

如果将 源IP 和 目的IP 看作是两台计算机在网络中的地址
那么 源端口 和 目的端口 就看作是两台计算机中的程序(比如 QQ)

举个栗子🥝

唐僧的自我介绍
贫僧自东土大唐而来, 欲往西天拜佛求经

这里的东土大唐就是源IP, 西天就是目的IP
这里的贫僧就是源端口, 拜佛就是目的端口

报文长度

一个 UDP 报文长度的最大值是 64KB
(2字节 --> 0 ~ 65535 --> 65535 / 1024 ≈ 64)

校验和

校验和是为了判断当前传输的数据是否出错

网络传输是有一定几率出现故障的(外部环境的干扰,强磁场的影响等)

举个栗子🥝

女神让滑稽老哥去买菜
分别是(1)芹菜 (2)黄瓜 (3)豆角 (4)白菜, 一共4样

滑稽老哥由于出门太急,只听清了要买4样菜
这时候滑稽老哥买的菜的种类不等于4样, 那么他一定是买错了
但是如果他买了4样菜,也不一定就买对了, 有可能滑稽老哥将黄瓜买成了土豆

校验和就是为了判定买的菜是不是4样
如果不是4样菜, 就一定是出错了
如果是, 也不一定就对


为了让校验和能够识别率更高一些(更为可靠), 计算的时候通常会用数据内容作为参数进行计算
数据内容发生变化, 校验和也会发生变化
(选取内容的一部分, 通过一些算术运算, 数学公式的变换,得到一个数值. 例如奇偶校验等)

发送方, 把载荷数据, 带入到校验和算法中, 计算生成的校验和结果(sum1)


接收方, 收到的数据, 既有载荷, 也有校验和 sum1
接收方通过同样的算法计算载荷得到校验和 sum2
对比 sum1 与 sum2 是否相同
如果不同, 则证明传输有误

🔎UDP的注意事项

端口号

端口号的取值范围是 0 ~ 65535 (2字节)

其中 < 1024 的端口号, 称为 “知名端口号”, 这部分端口是给一些服务器预留的, 编码的时候通常不使用这部分端口号

报文长度

使用 UDP 编程时, 需要注意 UDP 的报文长度不能过长

校验和

校验和的结果相同, 不一定传输无误
校验和的结果不同, 一定传输有误

🔎结尾

创作不易,如果对您有帮助,希望您能点个免费的赞👍
大家有什么不太理解的,可以私信或者评论区留言,一起加油

Linux UDP

UDP的特点

无连接 直接发发发

基于消息的数据传输服务 , 因此不存在TCP的粘包问题,但是存在丢包问题

不可靠。

一般情况下UDP更加高效

技术分享图片

 

UDP注意点

UDP报文可能会丢失、重复

UDP报文可能会乱序

UDP缺乏流量控制

udp缓冲区写满以后,没有流量控制机制,会覆盖缓冲区。

UDP协议数据报文截断

如果接收到的数据报,大于缓冲区;报文可以被截断;后面的部分会丢失。

recvfrom返回0,不代表连接关闭,因为udp是无连接的。

sendto可以发送数据0包。。。只含有udp头部。

一个小的DEMO

技术分享图片
// server.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m)         do         {                 perror(m);                 exit(EXIT_FAILURE);         } while(0)

void echo_srv(int sock)
{
    char         recvbuf[1024] = {0};
    struct         sockaddr_in peeraddr;
    socklen_t    peerlen;
    int         n;
    
    while (1)
    {
        peerlen = sizeof(peeraddr);
        bzero(recvbuf, sizeof(recvbuf));
        n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (struct sockaddr*)&peeraddr, &peerlen);
        if (n == -1)
        {
            if (errno == EINTR)
                continue;
            
            ERR_EXIT("recvfrom");
        }
        else if (n > 0)
        {
            int ret = 0;
            fputs(recvbuf, stdout);
            //注意sendto需要指定对方的地址
            ret = sendto(sock, recvbuf, n, 0, (struct sockaddr*)&peeraddr, peerlen);
            printf("ret :%d
", ret);

        }
    }

    close(sock);
}

int main(void)
{
    int sock;
    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        ERR_EXIT("socket");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8001);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
        ERR_EXIT("bind");

    echo_srv(sock);
    return 0;
}
View Code
技术分享图片
// client.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m)     do     {             perror(m);             exit(EXIT_FAILURE);     } while(0)

void echo_cli(int sock)
{
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8001);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int ret;
    char sendbuf[1024] = {0};
    char recvbuf[1024] = {0};
    while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
    {        
        //sendto第一次发送的时候,会绑定地址
        sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
        /*sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);*/
        
        //send(sock, sendbuf, strlen(sendbuf), 0);
        ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
        if (ret == -1)    
        {
            if (errno == EINTR) 
                continue;
            ERR_EXIT("recvfrom");
        }

        fputs(recvbuf, stdout);
        memset(sendbuf, 0, sizeof(sendbuf));
        memset(recvbuf, 0, sizeof(recvbuf));
    }

    close(sock);


}

int main(void)
{
    int sock;
    if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
        ERR_EXIT("socket");

    echo_cli(sock);

    return 0;
}
View Code

UDP的发送端是发送到UDP的缓冲里面了

UDP中的ICMP异步错误

关闭udp服务端,若启动udp客户端,从键盘接受数据后,再发送数据。udp客户端阻塞在sendto位置; udp发送报文的时,只把数据copy到发送缓冲区。在服务器没有起来的情况下,可以发送成功。 所谓ICMP异步错误是指:发送的报文的时候,没有错误,接受报文recvfrom的时候,回收到ICMP应答。 异步的错误,是无法返回未连接的套接字。udp也可以调用connect。udp调用connet,并没有三次握手,只是维护了一个状态信息(和对等方的),包括对方的IP和端口等信息。一但调用connect,就可以使用send函数 就不会阻塞在recvfrom了,会收到一个ICMP的错误报文

客户端调用connet和不调connet的区别。

udp也可以调用connet

udp客户端调用了connect以后,不会阻塞在recvfrom函数这里。

一但调用connect,就可以使用send函数

UDP协议数据报文截断

如果接收到的数据报,大于缓冲区;报文可以被截断;后面的部分会丢失。

SOCKET性能问题分析

客户端连接服务器,打开的最大文件句柄是1024

如何突破1024?

FD_SETSIZE 是 内核中定义的一个宏,是1024, 如果使用select管理,最多也只能管理1024个

因此,仅仅修改ulimits这个也是不可以的。如果想要修改,可以修改内核,在重新进行编译。

使用pool或者是epool可以突破这个机制。最大的性能提升还是在客户端,客户端如何快速的建立连接。

以上是关于UDP的报文结构及注意事项的主要内容,如果未能解决你的问题,请参考以下文章

运输层协议--TCP及UDP协议

网络基础TCPUDP协议

一篇讲透前端开发所需网络知识

Linux网络编程必学!——Linux_网络编程_UDP

Linux网络编程必学!——Linux_网络编程_UDP

网络 -- UDP知识