我遇到使用GCDAsyncUdpSocket的问题。我使用iPad作为用户界面应用程序与另一个应用程序交互 - 称之为Host,后者在另一台Windows机器上运行。两台计算机都在自己的专用网络上,因此它们位于自己的子网中。在某些点上,主机向iPad发送UDP数据包以指示它向用户显示哪个屏幕,并且iPad通过UDP数据包将用户响应发送给主机。最后,iPad定期(以2 Hz为单位)向主机发送简单的“心跳”消息。
这一切都很好 - 一段时间。然后,显然,iPad突然停止接受来自主机的UDP数据包 - 后者经历“连接重置同步”错误,而它(iPad)仍然成功发送,主机接收心跳消息。
我认为问题来自于我对Grand Central Dispatch(GCD)如何运作的困惑。我的iPad应用很简单;我基于iOS编程教程(我是初学者,但对Windows,Linux,嵌入式/实时和网络非常有经验)。它基本上由一个主屏幕组成,它不时创建第二个屏幕。所以基本结构是这样的:
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];
}
现在找到sems失败的代码。首先,这是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模块是否重要?
使用主调度队列是否合适,我认为我在做什么?的确,我这样做了,并且正确吗?
我完全不知所措,所以当然,任何建议都会受到很大的影响!无需回答所有问题 - 特别是如果您的答案是解决方案的话!
谢谢!
答案 0 :(得分:4)
这篇文章结束了我在POSIX / GCDAsyncSocket / NSStream / NSNetService众神手中收到的大约8个小时的折磨。对于遇到这种情况的任何人:我的GCDAsyncSocket连接由对等/远程对等断开连接错误重置的原因只是我在LAN上通过IP连接,而不是使用主机名。 (即使用connectToAddress系列方法而不是connectToHost系列方法)。我解雇了WireShark,下载了X11,所有爵士乐并确认我面临着Bob看到的同样问题 - 围绕断开时间的ARP活动。试图连接到主机而不是地址确认了Seth的情况 - 路由器重新分配IP地址的问题。
这对于SO而言可能不是一个更加无害的问题 - 问题和回答是0赞成,但是你们两个人合起来提供的信息远远超过了我认为难以解决的问题。非常感谢!
答案 1 :(得分:0)
听起来接收UDP套接字正在关闭或转移到不同的地址/端口对。
如果它被关闭,传出数据包仍可以工作的唯一方法是实际上有两个套接字。也许接收端口绑定到端口12349,发送端口绑定到端口0.然后GCD在一段时间后关闭接收端口。鉴于您对ARP的后续评论,这似乎不太可能,但值得记住。
ARP活动表明iPad的IP地址可能正在发生变化。如果它要改变,或者它要从WiFi接口切换到蜂窝接口,那么它发送的数据包仍然可以通过,但发送给它的数据包将完全失败,如上所述。
无论接收这些心跳消息是什么,请检查recvfrom地址并确保它发回的任何消息都转到该确切的地址。当然,请务必注意endian(主机与网络字节顺序)。
我假设两台设备之间没有防火墙或NAT。如果有这样的事情,那么就会开启一个完全不同的可能世界。