• 9.1 UDP协议


      TCP 协议是面向连接的基于流的,可靠的传输服务。UDP是无连接的,基于数据报的,不可靠的传输服务,UDP没有粘包,但是会产生丢包。

    UDP模型如下:

    可以看到,服务器端不用listen,也不用accept。而客户端,也不用connect。

    总结UDP的特点如下:

    1、无连接

    2、基于消息的数据传输服务

    3、不可靠

    4、一般情况下UDP更加高效

    注意点:

    1、UDP报文可能会丢失重复

    2、UDP报文可能会乱序

    3、UDP缺乏流量控制

    4、UDP缓冲区写满后,没有流量控制机制,会覆盖缓冲区

    5、UDP协议数据报文截断

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

    6、recvfrom返回0,不代表连接关闭,因为UDP是无连接的

      例如:sendto可以发送数据0包,只包含UDP头部,这时候recvfrem就会返回0

    7、ICMP异步错误

      观察现象:

        关闭UDP服务端,如启动UDP客户端,从键盘接收数据后,再发送数据。UDP客户端会阻塞在recvfrom位置(因为没有对端给本机发),sendto是将数据写到 

        套接字缓冲区,UDP协议栈会选择时机发送。

      说明:

        1、UDP发送报文时,只把数据copy到数据缓冲区,在服务器没有起来的情况下可以发送成功。

        2、所谓ICMP异步错误是指:发送报文的时候,没有错误,recvfrom接收报文的时候,会收到ICMP应答。

        3、异步错误,是无法返回未连接的套接字,UDP也可以调用connect。

    8、UDP connect

      UDP调用connect,并没有三次握手,只是维护了一个状态信息(和对等方的)

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

    简单的UDP回射服务器程序如下:

    服务器:

     1 #include <netinet/in.h>
     2 #include <arpa/inet.h>
     3 #include <stdlib.h>
     4 #include <stdio.h>
     5 #include <errno.h>
     6 #include <string.h>
     7 
     8 
     9 void echo_srv(int sock)
    10 {
    11     char recvbuf[1024] = {0};
    12     struct sockaddr_in peeraddr;
    13     socklen_t peerlen;
    14     int n;
    15     
    16     while(1)
    17     {
    18         peerlen = sizeof(peeraddr);
    19         memset(recvbuf, 0, sizeof(recvbuf));
    20         
    21         n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (struct sockaddr*)&peeraddr,     
    22                     &peerlen);
    23         
    24         if(n == -1)
    25         {
    26             if(errno == EINTR)
    27                 continue;
    28             else
    29             {
    30                 perror("recvfrom error");
    31                 exit(0);
    32             }
    33         }
    34         else if(n > 0)
    35         {
    36             int ret = 0;
    37             fputs(recvbuf, stdout);
    38             ret = sendto(sock, recvbuf, n, 0, (struct sockaddr*)&peeraddr, peerlen);
    39         }
    40     }
    41     
    42     close(sock);
    43 }
    44 
    45 
    46 int main()
    47 {
    48     int sock;
    49     
    50     sock = socket(AF_INET, SOCK_DGRAM, 0);
    51     
    52     if(sock < 0)
    53     {
    54         perror("socket error");
    55         exit(0);
    56     }
    57     
    58     struct sockaddr_in servaddr;
    59     memset(&servaddr, 0, sizeof(servaddr));
    60     
    61     servaddr.sin_family = AF_INET;
    62     servaddr.sin_port = htons(8002);
    63     
    64     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    65     
    66     if(bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
    67     {
    68         perror("bind error");
    69         exit(0);
    70     }
    71     
    72     echo_srv(sock);
    73     return 0;
    74 }

    客户端:

     1 #include <netinet/in.h>
     2 #include <arpa/inet.h>
     3 #include <stdlib.h>
     4 #include <stdio.h>
     5 #include <errno.h>
     6 #include <string.h>
     7 
     8 
     9 
    10 void echo_cli(int sock)
    11 {
    12     struct sockaddr_in servaddr;
    13     memset(&servaddr, 0, sizeof(servaddr));
    14     
    15     servaddr.sin_family = AF_INET;
    16     servaddr.sin_port = htons(8002);
    17     
    18     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    19     
    20     int ret = 0;
    21     char sendbuf[1024] = {0};
    22     char recvbuf[1024] = {0};
    23     
    24     while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
    25     {
    26         sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr*)&servaddr, 
    27                 sizeof(servaddr)
    28                 );
    29         ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
    30         
    31         if(ret == -1)
    32         {
    33             if(errno == EINTR)
    34                 continue;
    35             else
    36             {
    37                 perror("recvfrom error");
    38                 exit(0);
    39             }
    40         }
    41         
    42         fputs(recvbuf, stdout);
    43         memset(sendbuf, 0, sizeof(sendbuf));
    44         memset(recvbuf, 0, sizeof(recvbuf));
    45         
    46     }
    47     
    48     close(sock);
    49 }
    50 
    51 
    52 int main()
    53 {
    54     int sock;
    55     sock = socket(AF_INET, SOCK_DGRAM, 0);
    56     
    57     if(sock < 0)
    58     {
    59         perror("socket error");
    60         exit(0);
    61     }
    62     
    63     echo_cli(sock);
    64 
    65     return 0;
    66 }

    运行结果如下:

    用netstat - na看网络状态如下:

    UDP和TCP不一样,不存在11种状态,因此,我们只能看到一个服务器端的套接字,服务器端执行了bind,所以会显示这个套接字。

    报文截断:

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

    实验程序如下:

     1 #include <unistd.h>
     2 #include <sys/types.h>
     3 #include <sys/socket.h>
     4 #include <netinet/in.h>
     5 #include <stdlib.h>
     6 #include <stdio.h>
     7 #include <errno.h>
     8 #include <string.h>
     9 
    10 #define ERR_EXIT(m) 
    11         do 
    12         { 
    13                 perror(m); 
    14                 exit(EXIT_FAILURE); 
    15         } while(0)
    16 
    17 int main(void)
    18 {
    19     int sock;
    20     if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
    21         ERR_EXIT("socket");
    22 
    23     struct sockaddr_in servaddr;
    24     memset(&servaddr, 0, sizeof(servaddr));
    25     servaddr.sin_family = AF_INET;
    26     servaddr.sin_port = htons(8003);
    27     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    28 
    29     if (bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
    30         ERR_EXIT("bind");
    31 
    32     
    33     sendto(sock, "ABCD", 4, 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
    34     
    35     //数据报方式。。。。不是字节流
    36     //如果接受数据时,指定的缓冲区的大小,较小;
    37     //剩余部分将要截断,扔掉
    38     char recvbuf[1];
    39     int n;
    40     int i;
    41     for (i=0; i<4; i++)
    42     {
    43         n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
    44         if (n == -1)
    45         {
    46             if (errno == EINTR)
    47                 continue;
    48             ERR_EXIT("recvfrom");
    49         }
    50         else if(n > 0)
    51             printf("n=%d %c
    ", n, recvbuf[0]);
    52     }
    53     return 0;
    54 }

    这是一个自己发自己收的UDP程序,第38行我们定义的缓冲区为1字节大小,而33行发送的大小是4字节,recvfrom接收时会一次取出4字节,但是只放一字节到recvbuf中,其他的三字节被丢弃。 我们想看到的现象是recvfrem一个字节一个字节的接收,但是UDP是数据报协议,recvfrom一次接收一个数据报。跟TCP不一样。

    下面做一个只启动客户端,不启动服务器的实验,程序如下:

     1 #include <netinet/in.h>
     2 #include <arpa/inet.h>
     3 #include <stdlib.h>
     4 #include <stdio.h>
     5 #include <errno.h>
     6 #include <string.h>
     7 
     8 
     9 
    10 void echo_cli(int sock)
    11 {
    12     struct sockaddr_in servaddr;
    13     memset(&servaddr, 0, sizeof(servaddr));
    14     
    15     servaddr.sin_family = AF_INET;
    16     servaddr.sin_port = htons(8002);
    17     
    18     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    19     
    20     int ret = 0;
    21     char sendbuf[1024] = {0};
    22     char recvbuf[1024] = {0};
    23     
    24     while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
    25     {
    26         ret = sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr*)&servaddr, 
    27                 sizeof(servaddr)
    28                 );
    29         printf("sendto %d bytes
    ", ret);
    30         printf("send success
    ");
    31         
    32         ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
    33         
    34         if(ret == -1)
    35         {
    36             if(errno == EINTR)
    37                 continue;
    38             else
    39             {
    40                 perror("recvfrom error");
    41                 exit(0);
    42             }
    43         }
    44         
    45         fputs(recvbuf, stdout);
    46         memset(sendbuf, 0, sizeof(sendbuf));
    47         memset(recvbuf, 0, sizeof(recvbuf));
    48         
    49     }
    50     
    51     close(sock);
    52 }
    53 
    54 
    55 int main()
    56 {
    57     int sock;
    58     sock = socket(AF_INET, SOCK_DGRAM, 0);
    59     
    60     if(sock < 0)
    61     {
    62         perror("socket error");
    63         exit(0);
    64     }
    65     
    66     echo_cli(sock);
    67 
    68     return 0;
    69 }

    只启动客户端运行,结果如下:

     UDP也可以调用connect,但是并没有三次握手,只是维护了一个状态信息(和对等方的),实验程序如下:

     1 #include <unistd.h>
     2 #include <sys/types.h>
     3 #include <sys/socket.h>
     4 #include <netinet/in.h>
     5 #include <arpa/inet.h>
     6 #include <stdlib.h>
     7 #include <stdio.h>
     8 #include <errno.h>
     9 #include <string.h>
    10 
    11 #define ERR_EXIT(m) 
    12         do 
    13         { 
    14                 perror(m); 
    15                 exit(EXIT_FAILURE); 
    16         } while(0)
    17 
    18 void echo_cli(int sock)
    19 {
    20     struct sockaddr_in servaddr;
    21     memset(&servaddr, 0, sizeof(servaddr));
    22     servaddr.sin_family = AF_INET;
    23     servaddr.sin_port = htons(8002);
    24     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    25 
    26     //3 udp 也可以 调用connet
    27     //udp调用connet,并没有三次握手,只是维护了一个状态信息(和对等方的)。。。
    28     connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr));
    29 
    30     int ret;
    31     char sendbuf[1024] = {0};
    32     char recvbuf[1024] = {0};
    33     while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
    34     {    
    35         //2如果    connect 已经指定了对方的地址。
    36         //send可以这样写 sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);
    37         
    38         //1sendto第一次发送的时候,会绑定地址
    39         sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
    40         /*sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);*/
    41         
    42         //一但调用connect,就可以使用send函数
    43         //send(sock, sendbuf, strlen(sendbuf), 0);
    44         ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
    45         if (ret == -1)    
    46         {
    47             if (errno == EINTR) 
    48                 continue;
    49             ERR_EXIT("recvfrom");
    50         }
    51 
    52         fputs(recvbuf, stdout);
    53         memset(sendbuf, 0, sizeof(sendbuf));
    54         memset(recvbuf, 0, sizeof(recvbuf));
    55     }
    56 
    57     close(sock);
    58 
    59 
    60 }
    61 
    62 int main(void)
    63 {
    64     int sock;
    65     if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
    66         ERR_EXIT("socket");
    67 
    68     echo_cli(sock);
    69 
    70     return 0;
    71 }

    一旦调用了connect,就可以使用send函数发送数据了,不在必须使用sendto。使用send发送数据时,目标地址是connect中绑定的地址。只启动客户端,执行结果如下:

    上述程序我们只是在28行加上了connect,如果不加这个函数,客户端会阻塞在recvfrem处。调用了connect后,情况就有点不一样了,sendto还是正常发送数据,但是执行到recvfrom处接收到了ICMP报文,UDP接收到这个异常报文后,给recvfrom返回错误,显示连接拒绝,直接退出客户端。

  • 相关阅读:
    数据库实例: STOREBOOK > 用户 > 编辑 用户: SYSTEM
    数据库实例: STOREBOOK > 用户 > 编辑 用户: SYSMAN
    数据库实例: STOREBOOK > 用户 > 编辑 用户: SYS
    [慢查优化]建索引时注意字段选择性 & 范围查询注意组合索引的字段顺序(转)
    面试常考知识
    TCP、UDP和HTTP详解
    TCP流量控制与拥塞控制
    主键与唯一性索引
    进程与线程的区别
    WEB中会话跟踪
  • 原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9420893.html
Copyright © 2020-2023  润新知