udpSocket捕获ICMP不可达错误

本文探讨了UDP协议中如何实现连接感知及错误处理。通过对比无连接与有连接UDP的不同行为,解释了为何有连接UDP能更好地捕获ICMP错误,并提供了具体的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        最近得写个udpAgent,由于要做udpServer端的容灾,所以要感知udpAgent发出的UDP包是否被某个udpServer进程接收。但是UDP协议本身是无连接和无状态的,也就是说默认情况下,udpAgent进程是无法感知其发出的UDP包是否被成功接收。

        但是,从TCP/IP协议栈来说,如果一个UDP包没有到达目的地址,发送端会收到一个的ICMP不可达报文。所以现在的问题是如何让进程捕获到这个ICMP不可达错误,结论就是调用connect( )函数,即使用有连接的UDP。


// ================================================================================================


        这里做个简单实验:

        1、无连接的UDP:Socket( )=>SendTo( )

        2、有连接的UDP:Socket( )=>Connect( )=>Send( )

        下面是基础的无连接UDP客户端代码(SendUdp_NoCon.cpp):发送2个UDP包到一个不存在的目标地址(127.0.0.1:8888)


   
   
  1. #include <iostream>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <netinet/in.h>
  6. #include <arpa/inet.h>
  7. #include <netdb.h>
  8. using namespace std;
  9. int main()
  10. {
  11. int i, iSvrSock, iRet;
  12. string strData(”Hello World!”);
  13. struct sockaddr_in sSvrAddr;
  14. memset(&sSvrAddr, 0, sizeof(sSvrAddr));
  15. sSvrAddr.sin_family = AF_INET;
  16. sSvrAddr.sin_addr.s_addr = inet_addr( “127.0.0.1”);
  17. sSvrAddr.sin_port = htons( 8888);
  18. // 1.Socket()
  19. iSvrSock = socket(AF_INET, SOCK_DGRAM, 0);
  20. for (i = 0; i < 2; i ++)
  21. {
  22. // 2.Sendto()
  23. iRet = sendto(iSvrSock, strData.data(), strData.size(), 0, (struct sockaddr*)&sSvrAddr, sizeof(sSvrAddr));
  24. if (iRet < 0) {
  25. cout << “SendUdp Err, Return:” << iRet << ” Info:” << strerror(errno) << endl;
  26. } else {
  27. cout << “SendUdp Suc” << endl;
  28. }
  29. sleep( 1);
  30. }
  31. close(iSvrSock);
  32. return 0;
  33. }
         编译并执行,进程并没有感知UDP包是否被接收:


   
   
  1. [andy@localhost 201309]$ g++ -o NoCon SendUdp_NoCon.cpp </div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">[andy@localhost <span class="hljs-number">201309</span>]$ ./NoCon
  2. SendUdp Suc
  3. SendUdp Suc 

        通过tcpdump抓包可以发现TCP/IP协议栈有收到ICMP不可达报文:


   
   
  1. [root@localhost ~] # tcpdump -i lo "udp or icmp" -n -s1500
  2. tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
  3. listening on lo, link- type EN10MB (Ethernet), capture size 1500 bytes
  4. 17:32:38.963669 IP 127.0.0.1.60835 > 127.0.0.1.ddi-udp-1: UDP, length 12
  5. 17:32:38.963698 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 udp port ddi-udp-1 unreachable, length 48
  6. 17:32:39.963939 IP 127.0.0.1.60835 > 127.0.0.1.ddi-udp-1: UDP, length 12
  7. 17:32:39.963966 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 udp port ddi-udp-1 unreachable, length 48

        下面是有连接UDP客户端代码(SendUdp_Con.cpp):也是发送2个UDP包到一个不存在的目标地址(127.0.0.1:8888)


   
   
  1. #include <iostream>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <netinet/in.h>
  6. #include <arpa/inet.h>
  7. #include <netdb.h>
  8. using namespace std;
  9. int main()
  10. {
  11. int i, iSvrSock, iRet;
  12. string strData("Hello World!");
  13. struct sockaddr_in sSvrAddr;
  14. memset(&sSvrAddr, 0, sizeof(sSvrAddr));
  15. sSvrAddr.sin_family = AF_INET;
  16. sSvrAddr.sin_addr.s_addr = inet_addr( "127.0.0.1");
  17. sSvrAddr.sin_port = htons( 8888);
  18. // 1.Socket()
  19. iSvrSock = socket(AF_INET, SOCK_DGRAM, 0);
  20. // 2.Connect()
  21. connect(iSvrSock, (struct sockaddr*)&sSvrAddr, sizeof(sSvrAddr));
  22. for (i = 0; i < 2; i ++)
  23. {
  24. // 3.Send()
  25. iRet = send(iSvrSock, strData.data(), strData.size(), 0);
  26. if (iRet < 0) {
  27. cout << "SendUdp Err, Return:" << iRet << " Info:" << strerror(errno) << endl;
  28. } else {
  29. cout << "SendUdp Suc" << endl;
  30. }
  31. sleep( 1);
  32. }
  33. close(iSvrSock);
  34. return 0;
  35. }
         编译并执行,这次进程感知到了发送的UDP错误 Connection refused:

   
   
  1. [andy@localhost 201309]$ g++ -o Con SendUdp_Con.cpp </div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">[andy@localhost <span class="hljs-number">201309</span>]$ ./Con
  2. SendUdp Suc
  3. SendUdp Err, Return: -1 Info:Connection refused

         由上面的输出可知,是在Socket第2次sent( )的时候才捕获到这个Connection refused错误。这里并不是说第1次send( )的UDP包被成功接收了,而是因为UDP的send( )/sendto( )只是将UDP包的数据拷贝到发送缓冲区就立即返回了,等到第2次send( )的时候错误才被捕获,这种现象为异步错误。


// ================================================================================================


        为什么无连接UDP没有把ICMP错误通知给进程?

        我的理解是,由于无连接UDP你可以连续调用多个sendto( )函数,将UDP包发送给多个目的地址。由于ICMP不可达错误对于进程来说为异步错误,所以如果就算进程捕获了这个错误,也不知道是哪个目的地址不可达。如下面的例子,连续调用3次sendto将UDP包发给3个目的地址:


   
   
  1. iRet1 = sendto(iSvrSock, strData.data(), strData.size(), 0, (struct sockaddr*)&sSvrAddr1, sizeof(sSvrAddr1));
  2. iRet2 = sendto(iSvrSock, strData.data(), strData.size(), 0, (struct sockaddr*)&sSvrAddr2, sizeof(sSvrAddr2));
  3. iRet3 = sendto(iSvrSock, strData.data(), strData.size(), 0, (struct sockaddr*)&sSvrAddr3, sizeof(sSvrAddr3));
        如果iRet3返回了Connection refused错误,也无法判断是sSvrAddr1还是sSvrAddr2不可达。

        udpSocket调用connect( )并不会进行“3次握手”连接

        tcpSocket调用connect( )的时候,实际上是TCP/IP协议栈“3次握手”建立连接的过程。但是对于udpSocket,调用connect( )只是在进程绑定目的地址而已,不会向目的地址发送任何数据。实际上,udpocket调用connect( )之后,其只是在TCP/IP协议栈内绑定一个(协议/源IP/源端口/目的IP/目的端口)的五元组,一直维护到连接结束。


       无连接UDP调用sendto( )和有连接UDP调用send( )的联系和区别

       实际上,无连接UDP调用1次sendto( )发送UDP包,系统要做3件事:连接=>发送=>断开连接。而有连接UDP的send( )由于已经连接好了,只需完成”发送”这一步,故有连接UDP在性能上要由于无连接UDP。


源链接:https://blog.youkuaiyun.com/yyyiran/article/details/12197365

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值