最近得写个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)
-
#include <iostream>
-
#include <string.h>
-
#include <errno.h>
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
#include <arpa/inet.h>
-
#include <netdb.h>
-
using
namespace
std;
-
-
int main()
-
{
-
int i, iSvrSock, iRet;
-
string strData(”Hello World!”);
-
struct sockaddr_in sSvrAddr;
-
-
memset(&sSvrAddr,
0,
sizeof(sSvrAddr));
-
sSvrAddr.sin_family = AF_INET;
-
sSvrAddr.sin_addr.s_addr = inet_addr(
“127.0.0.1”);
-
sSvrAddr.sin_port = htons(
8888);
-
-
// 1.Socket()
-
iSvrSock = socket(AF_INET, SOCK_DGRAM,
0);
-
-
for (i =
0; i <
2; i ++)
-
{
-
// 2.Sendto()
-
iRet = sendto(iSvrSock, strData.data(), strData.size(),
0, (struct sockaddr*)&sSvrAddr,
sizeof(sSvrAddr));
-
if (iRet <
0) {
-
cout <<
“SendUdp Err, Return:” << iRet <<
” Info:” << strerror(errno) <<
endl;
-
}
else {
-
cout <<
“SendUdp Suc” <<
endl;
-
}
-
sleep(
1);
-
}
-
-
close(iSvrSock);
-
return
0;
-
}
编译并执行,进程并没有感知UDP包是否被接收:
-
[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
-
SendUdp Suc
-
SendUdp Suc
通过tcpdump抓包可以发现TCP/IP协议栈有收到ICMP不可达报文:
-
[root@localhost ~]
# tcpdump -i lo "udp or icmp" -n -s1500
-
tcpdump: verbose output suppressed, use -v
or -vv
for full protocol decode
-
listening on lo, link-
type EN10MB (Ethernet), capture size 1500 bytes
-
17:32:38.963669 IP 127.0.0.1.60835 > 127.0.0.1.ddi-udp-1: UDP, length 12
-
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
-
17:32:39.963939 IP 127.0.0.1.60835 > 127.0.0.1.ddi-udp-1: UDP, length 12
-
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)
-
#include <iostream>
-
#include <string.h>
-
#include <errno.h>
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
#include <arpa/inet.h>
-
#include <netdb.h>
-
using
namespace
std;
-
-
int main()
-
{
-
int i, iSvrSock, iRet;
-
string strData("Hello World!");
-
struct sockaddr_in sSvrAddr;
-
-
memset(&sSvrAddr,
0,
sizeof(sSvrAddr));
-
sSvrAddr.sin_family = AF_INET;
-
sSvrAddr.sin_addr.s_addr = inet_addr(
"127.0.0.1");
-
sSvrAddr.sin_port = htons(
8888);
-
-
// 1.Socket()
-
iSvrSock = socket(AF_INET, SOCK_DGRAM,
0);
-
// 2.Connect()
-
connect(iSvrSock, (struct sockaddr*)&sSvrAddr,
sizeof(sSvrAddr));
-
-
for (i =
0; i <
2; i ++)
-
{
-
// 3.Send()
-
iRet = send(iSvrSock, strData.data(), strData.size(),
0);
-
if (iRet <
0) {
-
cout <<
"SendUdp Err, Return:" << iRet <<
" Info:" << strerror(errno) <<
endl;
-
}
else {
-
cout <<
"SendUdp Suc" <<
endl;
-
}
-
sleep(
1);
-
}
-
-
close(iSvrSock);
-
return
0;
-
}
编译并执行,这次进程感知到了发送的UDP错误 Connection refused:
-
[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
-
SendUdp Suc
-
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个目的地址:
-
iRet1 = sendto(iSvrSock, strData.data(), strData.size(),
0, (struct sockaddr*)&sSvrAddr1,
sizeof(sSvrAddr1));
-
iRet2 = sendto(iSvrSock, strData.data(), strData.size(),
0, (struct sockaddr*)&sSvrAddr2,
sizeof(sSvrAddr2));
-
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