目前,当我将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数据包”?
答案 0 :(得分:0)
原始的UNIX套接字实现无法异步传递错误(整个API是同步的)。因此,如果套接字遇到异步错误(该错误不是调用send()
或recv()
的直接结果),它会将自己标记为具有要读取的数据,并且在程序尝试读取数据时,读取调用将失败,并且设置错误将是异步错误。
在您的情况下,错误是异步的,因为发送调用未失败。数据包已正确发送到目的地。但是目的地拒绝接受该数据包,并用ICMP消息答复该端口不可用。在您的发送呼叫已经返回很久之后,该消息异步到达您的主机。
因此,当套接字通知您可以读取数据时,有两种可能性:
使用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