【发布时间】: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 函数似乎在一些随机时间(以及随机发送/接收的消息数)内正常工作。我想知道几件事:
我可以从第二个屏幕发送 UDP 消息吗?我想是的,并且发送永远不会失败 - 即使在接收失败后它也会继续工作。
didReceiveData 是如何被调用的,它又是如何被破坏的?如果我在 Linux 或 RTOS 中,我可能会创建一个显式线程来等待数据包; GCD 框架如何决定一个数据包应该去哪里?
为什么我的应用会突然停止监听端口?如何检测/调试它?
GCDAsyncUdpSocket 对象属于主屏幕,而不是 Delegate.m 模块,这重要吗?
像我想的那样使用主调度队列是否合适?确实,我这样做是正确的吗?
我完全不知所措,所以当然,任何建议都会非常感激!无需回答所有问题 - 特别是如果您的答案就是解决方案!
谢谢!
【问题讨论】:
-
使用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