使用GCDAsyncUdpSocket在iOS6上发生“对等连接重置”错误

问题描述:

我在使用GCDAsyncUdpSocket时遇到问题。我将iPad用作与其他应用程序交互的用户界面应用程序 - 将其称为主机,后者在单独的Windows计算机上运行。两台机器都在自己的专用网络上,因此它们位于自己的子网上。在某些情况下,主机向iPad发送UDP数据包以指示它向用户显示哪个屏幕,并且iPad通过UDP数据包向用户发送用户响应。最后,iPad定期(以2赫兹)向主机发送简单的“心跳”消息。使用GCDAsyncUdpSocket在iOS6上发生“对等连接重置”错误

这一切工作正常 - 一段时间。然后,显然,iPad突然停止接受来自主机的UDP数据包 - 后者遇到“连接重置对等”错误,而它(iPad)仍在成功发送,而主机接收心跳消息。

我在想这个问题来自我对大中央调度(GCD)如何工作的困惑。我的iPad应用程序非常简单;我将其基于iOS编程教程(我在这里是初学者,但在Windows,Linux,嵌入式/实时和网络方面非常有经验)。它基本上由一个主屏幕组成,它不时创建第二个屏幕。因此,基本结构是这样的:

  • 的main.m
  • Delegate.m
  • MainViewController.m
  • PopupViewController.m

的main.m文件和Delegate.m创建在教程中由Xcode自动完成,并且没有什么特别之处。 MainViewController.m是我的“主屏幕”,并拥有iPad应用程序使用的GCDAsyncUdpSocket。最终的文件,PopupViewController.m,是第二个屏幕,即使用这样的:

# MainViewController.m 
- (IBAction)sendResponseOne:(id)sender { 
    // Send a message to Host 
    [self sendUdpMessage:1]; 

    // Switch to other view 
    PopupViewController *vc = [[PopupViewController alloc] init]; 
    [vc setMainScreen:self]; // Used to access UDP from 2nd screen 
    [self presentViewController:vc animated:NO completion:nil]; 
} 

# PopupViewController.m 
- (IBAction)confirmAnswers:(id)sender 
{ 
    // Send a message to Host - calls same function as above main screen 
    [self->mainScr sendUdpMessage:2]; 
    [self dismissViewControllerAnimated:NO completion:nil]; 
} 

现在对于中小企业失败的代码。首先,这里是MainViewController.m的@interface部分:

# From MainViewController.m 
@interface MainViewController() 
{ 
    GCDAsyncUdpSocket *udpSocket; 
} 
@end 

这里是如何/在我创建UDP对象:

# From MainViewController.m 
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 
{ 
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) 
    { 
     // Setup our socket, using the main dispatch queue 
     udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; 
    } 
    return self; 
} 

这里就是我绑定端口:

# From MainViewController.m 
- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    // Start UDP server 
    int port = 12349; 
    NSError *error = nil; 

    if (![udpSocket bindToPort:port error:&error]) 
    { 
     NSLog(@"Error starting server (bind): %@", error); 
     return; 
    } 
    if (![udpSocket beginReceiving:&error]) 
    { 
     [udpSocket close]; 
     NSLog(@"Error starting server (recv): %@", error); 
     return; 
    } 
    [self startPingTimer]; 
    isRunning = YES; 
} 

下面是接收数据包的代码。显然,这个函数一段时间工作正常,有时几十次,然后意外失败。

# From MainViewController.m 
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data 
     fromAddress:(NSData *)address 
withFilterContext:(id)filterContext 
{ 
    if (data.length == sizeof(MyMessage)) { 
     MyMessage msg; 
     [data getBytes:&msg length:sizeof(MyMessage)]; 
     msg.magic = ntohl(msg.magic); 
     msg.msgId = ntohl(msg.msgId); 
     for (int i = 0; i < 4; ++i) { 
      msg.values[i] = ntohl(msg.values[i]); 
     } 
     if (msg.magic == 0xdeadcafe) { 
      switch (msg.msgId) { 
       case imiStateControl: 
        self->iceState = (IceState)msg.values[0]; 
        break; 

       default: 
        break; 
      } 
     } 
    } 
} 

我很茫然,为什么didReceiveData功能似乎某些时间随机量正常工作(和发送邮件的随机数/接收)。我想知道几件事情:

  1. 从第二个屏幕发送UDP消息有效吗?我认为是这样,并且发送永远不会失败 - 即使接收失败,它也会继续工作。

  2. didReceiveData如何被调用,怎么会被打破?如果我在Linux或RTOS中,我可能会创建一个等待数据包的显式线程; GCD框架如何决定数据包应该放在哪里?

  3. 为什么我的应用程序突然停止在端口上收听?我如何检测/调试?

  4. GCDAsyncUdpSocket对象是否由主屏幕拥有,与Delegate.m模块相反,这有什么关系?

  5. 是否适合使用主调度队列,因为我认为我在做什么?的确,我是否正确地做到了这一点?

我完全不知所措,所以当然,任何建议都会大受欢迎!无需回答所有问题 - 特别是如果您的答案是解决方案!

谢谢!

+0

使用Wireshark我找到了失败的原因。一段时间后,iPad发送了大量的ARP数据包,第一个是“谁拥有192.168.1.65?告诉0.0.0.0”,这是奇怪的,因为iPad是192.168.1.65。然后它要求192.168.1.1(告诉192.168.1.65)和169.254.255.255;后者失败5次,则iPad拒绝接受未来数据包 - 堆栈以“目标不可达”ICMP数据包响应外部主机。再一次,我无法访问GCDAsyncUdpSocket可能遇到的任何错误,而且我完全停留在此。 – Bob 2013-07-06 21:53:39

+0

我在使用VVOSC库的应用程序中遇到了类似的问题,在不一致的时间段(分钟或小时)之后,它将停止在其“OSCInport”类(从udp套接字读取)上接收数据,并且只能“重置“iPad。在传入的UDP套接字停止工作之前,我看到了类似的ARP活动模式。 – 2014-03-02 15:17:40

听起来好像接收UDP套接字正在关闭或转移到不同的地址/端口对。

  • 如果被关闭,传出数据包仍然可以工作的唯一方法是,如果实际上有两个套接字。也许接收端口绑定到端口12349,发送端口绑定到端口0.然后GCD在一段时间后关闭接收端口。鉴于您对ARP的后续评论,这似乎不大可能,但值得记住。

  • ARP活动表明iPad的IP地址可能正在改变。如果它改变了,或者如果它从WiFi接口切换到蜂窝接口,那么它发送的数据包仍然会通过,但发送到它的数据包将完全按照所描述的失败。

无论接收到那些心跳消息,请检查recvfrom地址并确保它发回的任何消息都转到该确切地址。当然,请确保注意endian(主机vs网络字节顺序)。

我假设两台设备之间没有防火墙或NAT。如果有这样的事情,那么就会有一个完全不同的可能性世界。

这篇文章结束了我在POSIX/GCDAsyncSocket/NSStream/NSNetService众神手中收到的大约八个小时的折磨。对于任何人遇到这种情况:我的GCDAsyncSocket连接通过对等/远程对等方断开连接错误重置的原因仅仅是我通过局域网上的IP连接,而不是使用主机名。 (即使用connectToAddress系列方法而不是connectToHost系列方法)。我启动了WireShark,下载了X11,全部是爵士乐,并确认我面临着Bob正在看到的同样的问题 - 在断开时间周围的ARP活动。尝试连接到主机而不是地址确认Seth的处理情况 - 这是路由器重新分配IP地址的问题。

这对于SO来说不是一个更无害的问题 - 0个upvotes的问题和答案,但是你们两个人结合起来给予更多的信息来解决我认为是棘手的问题。非常感谢!