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", 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\n", ret);
30         printf("send success\n");
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返回错误,显示连接拒绝,直接退出客户端。

posted on 2018-08-05 08:05  周伯通789  阅读(317)  评论(0编辑  收藏  举报