1

目前,当我将 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 数据包”?

4

1 回答 1

0

The original UNIX socket implementation had no way to communicate errors asynchronously (the whole API is synchronous). So if a socket ran into an asynchronous error (an error not a direct consequence of calling send() or recv()), it tagged itself as having data to read and when the program was trying to read the data, the read call would fail and the set error would be the asynchronous error.

In your case the error is asynchronous, as the send call did not fail. The packet was correctly sent out to the destination. Yet the destination refused to accept that packet and replied with an ICMP message, that the port is not available. This message arrives asynchronously at your host, long after your send call has already returned.

So when a socket signals you that you can read data, then there are two possibilities:

  1. There is really data available to read.
  2. An asynchronous error took place and the read call gives you the error code.

When using kCFSocketDataCallBack, you cannot distinguish these two cases, since this the API will always try to directly read data when the socket says it there is data available for reading and if that read fails, you will get an empty CFDataRef object.

See CFSocket implementation:

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);
    }

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

The line //??? should return error if <0 says it all. Yes, this should return an error, yet it just returns an empty data blob.

Instead you have to use kCFSocketReadCallBack. Now your callback is performed whenever the socket says that it has data available for reading, yet no data has been read so far. You will then have to read the data yourself using POSIX socket 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
于 2020-08-20T10:11:48.210 回答