【问题标题】:"Connection reset by peer" errors with GCDAsyncUdpSocket on iOS6iOS6 上的 GCDAsyncUdpSocket 出现“对等连接重置”错误
【发布时间】:2013-05-05 11:32:30
【问题描述】:

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

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

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

  • main.m
  • 代表.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. 像我想的那样使用主调度队列是否合适?确实,我这样做是正确的吗?

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

谢谢!

【问题讨论】:

  • 使用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 可能遇到的任何错误,我完全被困在这个问题上。
  • 我在使用 VVOSC 库的应用程序中遇到了类似的问题,在不一致的时间段(几分钟或几小时)之后将停止接收其“OSCInport”类(从 udp 读取)的数据socket),并且只能通过重新启动 iPad 来“重置”。在这个传入的 UDP 套接字停止工作之前,我看到了类似的 ARP 活动模式。

标签: ios udp grand-central-dispatch gcdasyncsocket


【解决方案1】:

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

在 SO 上,这简直是一个无伤大雅的问题 - 0 票赞成的问答,但你们两个结合起来提供了足够多的信息来解决我认为是一个棘手的问题。非常感谢!

【讨论】:

    【解决方案2】:

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-09
      • 1970-01-01
      相关资源
      最近更新 更多