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