3

我在使用 GCDAsyncUdpSocket 时遇到问题。我将 iPad 用作与另一个应用程序交互的用户界面应用程序 - 称之为主机,后者在单独的 Windows 机器上运行。两台机器都在自己的专用网络上,因此它们在自己的子网上。在某些时候,主机向 iPad 发送 UDP 数据包以指示它向用户显示哪个屏幕,然后 iPad 通过 UDP 数据包向主机发送用户响应。最后,iPad 会定期(以 2 Hz)向主机发送简单的“心跳”消息。

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

我认为问题出在我对 Grand Central Dispatch (GCD) 工作方式的困惑。我的 iPad 应用程序非常简单;我基于 iOS 编程教程(我是这里的初学者,但在 Windows、Linux、嵌入式/实时和网络方面非常有经验)。它基本上由一个主屏幕组成,它不时创建第二个屏幕。所以基本结构是这样的:

  • 主文件
  • 代表.m
  • 主视图控制器.m
  • 弹出视图控制器.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. 像我想的那样使用主调度队列是否合适?确实,我这样做是正确的吗?

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

谢谢!

4

2 回答 2

4

这篇文章结束了我在POSIX/GCDAsyncSocket/NSStream/NSNetService众神手中遭受的大约八个小时的折磨。对于遇到此问题的任何人:GCDAsyncSocket对等/远程对等断开错误重置我的连接的原因仅仅是我在IP上连接LAN,而不是使用主机名。(即使用connectToAddress方法族而不是connectToHost方法族)。我启动了 WireShark,下载了 X11,所有这些爵士乐,并确认我遇到了 Bob 看到的同样问题——断开连接时的 ARP 活动。尝试连接到主机而不是地址证实了 Seth 对这种情况的看法——这是路由器重新分配 IP 地址的问题。

这在 SO 上是再无害的问题了——问答 0 票,但你们俩结合起来提供了足够多的信息来解决我认为是一个棘手的问题。非常感谢!

于 2014-11-10T06:10:46.033 回答
0

听起来接收 UDP 套接字要么被关闭,要么被转移到不同的地址/端口对。

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

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

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

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

于 2014-03-05T19:54:18.503 回答