最近得写个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 [andy@localhost 201309]$ ./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 [andy@localhost 201309]$ ./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。