在Objective C中区分“udp数据包不可达的目的地”和“长度为0的有效载荷的接收数据包”

时间:2013-05-13 19:58:19

标签: objective-c sockets callback udp

目前,当我将udp数据包发送到无法到达的目的地(例如未绑定的本地端口)时,我得到的事件似乎与接收零长度udp数据包无法区分。

我正在创建这样的套接字:

CFSocketContext socketContext = { 0, (__bridge void *)self, CFRetain, CFRelease, NULL };
socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP, kCFSocketDataCallBack, (CFSocketCallBack)onReceivedData, &socketContext);
NSData* localEndPointData = [[IpEndPoint ipEndPointAtUnspecifiedAddressOnPort:specifiedLocalPort] sockaddrData];
CFSocketSetAddress(socket, (__bridge CFDataRef)localEndPointData);
CFSocketConnectToAddress(socket, (__bridge CFDataRef)[remoteEndPoint sockaddrData], -1);

并接收如下事件:

void onReceivedData(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
    if(type == kCFSocketDataCallBack && CFDataGetLength((CFDataRef)data) == 0) {
        NSLog("Received empty packet");
    }
}

如果有一个套接字在specifiedLocalPort监听,那么一切正常。发送数据不会触发接收的事件,接收数据会触发接收的事件。如果specifiedLocalPort没有收听套接字,那么发送数据会触发收到的事件,声称收到了一个空的udp数据包。

我做了一些愚蠢的事情,导致这种行为吗?如何区分“目标无法访问”和“目的地发送给您的空udp数据包”?

1 个答案:

答案 0 :(得分:0)

原始的UNIX套接字实现无法异步传递错误(整个API是同步的)。因此,如果套接字遇到异步错误(该错误不是调用send()recv()的直接结果),它会将自己标记为具有要读取的数据,并且在程序尝试读取数据时,读取调用将失败,并且设置错误将是异步错误。

在您的情况下,错误是异步的,因为发送调用未失败。数据包已正确发送到目的地。但是目的地拒绝接受该数据包,并用ICMP消息答复该端口不可用。在您的发送呼叫已经返回很久之后,该消息异步到达您的主机。

因此,当套接字通知您可以读取数据时,有两种可能性:

  1. 确实有可读取的数据。
  2. 发生异步错误,读取调用会为您提供错误代码。

使用kCFSocketDataCallBack时,您无法区分这两种情况,因为在套接字上说有可读取的数据时,API会始终尝试直接读取数据,如果读取失败,您将得到一个空的CFDataRef对象。

请参见CFSocket实现:

if (__CFSocketReadCallBackType(s) == kCFSocketDataCallBack) {
        
    // ... <snip> ...
        
    if (__CFSocketIsConnectionOriented(s)) {
        buffer = bufferArray;
        recvlen = recvfrom(s->_socket, buffer, MAX_CONNECTION_ORIENTED_DATA_SIZE, 0, (struct sockaddr *)name, (socklen_t *)&namelen);
    } else {
        buffer = malloc(MAX_DATA_SIZE);
        if (buffer) recvlen = recvfrom(s->_socket, buffer, MAX_DATA_SIZE, 0, (struct sockaddr *)name, (socklen_t *)&namelen);

    // ... <snip> ...

    if (0 >= recvlen) {
        //??? should return error if <0
        /* zero-length data is the signal for perform to invalidate */
        data = CFRetain(zeroLengthData);
    } else {
        data = CFDataCreate(CFGetAllocator(s), buffer, recvlen);
    }

来源:https://opensource.apple.com/source/CF/CF-476.18/CFSocket.c.auto.html

//??? should return error if <0行说明了一切。是的,这应该返回一个错误,但它只会返回一个空的数据blob。

相反,您必须使用kCFSocketReadCallBack。现在,只要套接字说它有可读取的数据,就会执行回调,但到目前为止还没有数据被读取。然后,您将必须使用POSIX套接字API自己读取数据:

int rawSocket = CFSocketGetNative(socket);
if (rawSocket < 0) { /* ... handle invalid socket descriptor error ... */ }

CFDataRef data = NULL;
CFErrorRef error = NULL;
for (;;) {
    char buffer[MAX_PACKET_SIZE];
    ssize_t bytesRead = read(rawSocket, &buffer, MAX_PACKET_SIZE);

    if (bytesRead < 0) {
        int errorCode = errno;
        if (errorCode == EINTR) {
            // Read call was just interrupted; that's uncritical, just try again
            continue;
        }
        error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, errorCode, NULL);
        break;
    }

    data = CFDataCreate(NULL, buffer, bytesRead);
    break;
}

// Either data or error is not NULL