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

客户端:

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

运行结果如下:

用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()
  16.  
  17. int main(void)
  18. {
  19. int sock;
  20. if ((sock = socket(PF_INET, SOCK_DGRAM, )) < )
  21. ERR_EXIT("socket");
  22.  
  23. struct sockaddr_in servaddr;
  24. memset(&servaddr, , sizeof(servaddr));
  25. servaddr.sin_family = AF_INET;
  26. servaddr.sin_port = htons();
  27. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  28.  
  29. if (bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
  30. ERR_EXIT("bind");
  31.  
  32. sendto(sock, "ABCD", , , (struct sockaddr*)&servaddr, sizeof(servaddr));
  33.  
  34. //数据报方式。。。。不是字节流
  35. //如果接受数据时,指定的缓冲区的大小,较小;
  36. //剩余部分将要截断,扔掉
  37. char recvbuf[];
  38. int n;
  39. int i;
  40. for (i=; i<; i++)
  41. {
  42. n = recvfrom(sock, recvbuf, sizeof(recvbuf), , NULL, NULL);
  43. if (n == -)
  44. {
  45. if (errno == EINTR)
  46. continue;
  47. ERR_EXIT("recvfrom");
  48. }
  49. else if(n > )
  50. printf("n=%d %c\n", n, recvbuf[]);
  51. }
  52. return ;
  53. }

这是一个自己发自己收的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. void echo_cli(int sock)
  9. {
  10. struct sockaddr_in servaddr;
  11. memset(&servaddr, , sizeof(servaddr));
  12.  
  13. servaddr.sin_family = AF_INET;
  14. servaddr.sin_port = htons();
  15.  
  16. servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  17.  
  18. int ret = ;
  19. char sendbuf[] = {};
  20. char recvbuf[] = {};
  21.  
  22. while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
  23. {
  24. ret = sendto(sock, sendbuf, strlen(sendbuf), , (struct sockaddr*)&servaddr,
  25. sizeof(servaddr)
  26. );
  27. printf("sendto %d bytes\n", ret);
  28. printf("send success\n");
  29.  
  30. ret = recvfrom(sock, recvbuf, sizeof(recvbuf), , NULL, NULL);
  31.  
  32. if(ret == -)
  33. {
  34. if(errno == EINTR)
  35. continue;
  36. else
  37. {
  38. perror("recvfrom error");
  39. exit();
  40. }
  41. }
  42.  
  43. fputs(recvbuf, stdout);
  44. memset(sendbuf, , sizeof(sendbuf));
  45. memset(recvbuf, , sizeof(recvbuf));
  46.  
  47. }
  48.  
  49. close(sock);
  50. }
  51.  
  52. int main()
  53. {
  54. int sock;
  55. sock = socket(AF_INET, SOCK_DGRAM, );
  56.  
  57. if(sock < )
  58. {
  59. perror("socket error");
  60. exit();
  61. }
  62.  
  63. echo_cli(sock);
  64.  
  65. return ;
  66. }

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

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()
  17.  
  18. void echo_cli(int sock)
  19. {
  20. struct sockaddr_in servaddr;
  21. memset(&servaddr, , sizeof(servaddr));
  22. servaddr.sin_family = AF_INET;
  23. servaddr.sin_port = htons();
  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[] = {};
  32. char recvbuf[] = {};
  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), , (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), , NULL, NULL);
  45. if (ret == -)
  46. {
  47. if (errno == EINTR)
  48. continue;
  49. ERR_EXIT("recvfrom");
  50. }
  51.  
  52. fputs(recvbuf, stdout);
  53. memset(sendbuf, , sizeof(sendbuf));
  54. memset(recvbuf, , sizeof(recvbuf));
  55. }
  56.  
  57. close(sock);
  58.  
  59. }
  60.  
  61. int main(void)
  62. {
  63. int sock;
  64. if ((sock = socket(PF_INET, SOCK_DGRAM, )) < )
  65. ERR_EXIT("socket");
  66.  
  67. echo_cli(sock);
  68.  
  69. return ;
  70. }

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

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

9.1 UDP协议的更多相关文章

  1. TODO:Golang语言TCP/UDP协议重用地址端口

    TODO:Golang语言TCP/UDP协议重用地址端口 这是一个简单的包来解决重用地址的问题. go net包(据我所知)不允许设置套接字选项. 这在尝试进行TCP NAT时尤其成问题,其需要在同一 ...

  2. 闲来无事,写个基于UDP协议的Socket通讯Demo

    项目一期已经做完,二期需求还没定稿,所以最近比较闲. 上一篇写的是TCP协议,今天写一下UDP协议.TCP是有连接协议,所以发送和接收消息前客户端和服务端需要建立连接:UDP是无连接协议,所以发送消息 ...

  3. UDP协议开发

    UDP是用户数据报协议(User Datagram Protocol,UDP)的简称,其主要作用是将网络数据流量压缩成数据报形式,提供面向事务的简单信息传送服务.与TCP协议不同,UDP协议直接利用I ...

  4. 基于UDP协议模拟的一个TCP协议传输系统

    TCP协议以可靠性出名,这其中包括三次握手建立连接,流控制和拥塞控制等技术.详细介绍如下: 1. TCP协议将需要发送的数据分割成数据块.数据块大小是通过MSS(maximum segment siz ...

  5. TCP协议与UDP协议的区别

    TCP协议与UDP协议的区别(转) 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UDP协议的区别,我觉得这是没有从本质上弄清楚网络通信! ...

  6. 采用UDP协议的PIC32MZ ethernet bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). 经过千辛万苦,今天终于 ...

  7. 采用UDP协议实现PIC18F97J60 ethernet bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). TCP/IP Stac ...

  8. 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程

    Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...

  9. Linux内核--网络栈实现分析(九)--传输层之UDP协议(下)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7549340 更多请查看专栏,地 ...

  10. Linux内核--网络栈实现分析(五)--传输层之UDP协议(上)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明出处http://blog.csdn.net/yming0221/article/details/7532512 更多请看专栏, ...

随机推荐

  1. robot_pose的类型

    http://docs.ros.org/api/geometry_msgs/html/msg/Pose.html

  2. Codeforces 496D - Tennis Game

    496D - Tennis Game 思路:枚举每个t,求出对应的满足条件的s. 代码: #include<bits/stdc++.h> using namespace std; #def ...

  3. sgu 116 Index of super-prime

    题意:用最少的super-prime组成n; 找出所有的super-prime数,只有202个.用完全背包记录能取到n值的最少数量.再找出7要哪些元素. #include <iostream&g ...

  4. Oracle11g温习-第六章:控制文件

    2013年4月27日 星期六 10:33  .控制文件的功能和特点 1) [定义数据库当前物理状态] 2) [维护数据的一致性]  如果控制文件中的检查点与数据文件中的一致,则说明数据一致,可以启动到 ...

  5. Oracle 11g 物理Dataguard日常操作维护(二)

    Oracle 11g 物理Dataguard日常操作维护(二) 2017年8月25日 14:34 3.3 3.3.1 查看备库进程状态 SYS(125_7)@fpyj123> select pr ...

  6. python读写csv时中文乱码问题解决办法

    https://www.cnblogs.com/shengulong/p/7097869.html 参考1 参考2 参考3 CSV是英文Comma Separate Values(逗号分隔值)的缩写, ...

  7. SQL基础分页存储过程(案例一)

    --分页 存储过程 案例 -- 所执行的存储过程 create proc pageForUsers @currPage int, --当前页数 @pageSize int, --每页多少条记录 @co ...

  8. kill word out 1

    1● de 使~~ 成为 :离开 ,去掉,向下,变慢   2● dif 不,分开 ,否定,离开     3● deci 十分之一   4● deca 向下,离开   5● deca 十   6● di ...

  9. VC++ 报错:Heap corruption detected

    今天在写代码时,发现莫名其妙的错误: std::string strName = L“testtest”; char* pOutString = new char(len + 1); Decrypt( ...

  10. cas AuthenticationFilter

    AuthenticationFilter *** 这个类的作用:判断是否已经登录,如果没有登录则根据配置的信息来决定将跳转到什么地方 *** casServerLoginUrl:定义cas 服务器的登 ...