简体   繁体   中英

“Connection reset by peer” errors with GCDAsyncUdpSocket on iOS6

I am having a problem with using GCDAsyncUdpSocket. I am using the iPad as a user interface app that interacts with another app - call it Host, the latter running on a separate Windows machine. Both machines are on their own private network, so they are on their own subnet. At certain points, the Host sends UDP packets to the iPad to instruct it which screen to show to the user, and the iPad sends user responses via UDP packets to the Host. Finally, the iPad periodically (at 2 Hz) sends simple "heartbeat" messages to the Host.

This all works fine - for a while. Then, apparently, the iPad abruptly stops accepting the UDP packets from the Host - the latter experiences "Connection reset by peer" errors, while it (the iPad) is still successfully sending, and the Host receiving, the heartbeat messages.

I'm thinking the problem comes from my confusion with respect to how Grand Central Dispatch (GCD) works. My iPad app is pretty simple; I based it off a tutorial on iOS programming (I'm a beginner here, but very experienced with Windows, Linux, embedded/real-time, and networking). It basically consists of a main screen, which creates a second screen from time to time. So the basic structure is this:

  • main.m
  • Delegate.m
  • MainViewController.m
  • PopupViewController.m

The main.m and Delegate.m were created automagically by the Xcode during the tutorial, and have nothing special in them. The MainViewController.m is my "main screen", and owns the GCDAsyncUdpSocket that is used by the iPad app. The final file, PopupViewController.m, is the second screen, that is used like this:

# 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];
}

Now for the code that sems to fail. First, here is the @interface section of MainViewController.m:

# From MainViewController.m
@interface MainViewController () 
{
    GCDAsyncUdpSocket *udpSocket;
}
@end

Here is how/where I create the UDP object:

# 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;
}

Here is where I bind to the port:

# 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;
}

Here's the code that receives packets. Apparently, this function works fine for awhile, sometimes dozens of times, then unexpectedly fails.

# 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;
            }
        }
    }
}

I am at a loss as to why the didReceiveData function seems to work correctly for some random amount of time (and random number of messages sent/received). I wonder a couple of things:

  1. Is it valid for me to send a UDP message from the second screen? I think so, and sending never fails - it continues to work even after receiving fails.

  2. How does the didReceiveData get called anyway, and how could that get broken? If I was in Linux or an RTOS, I would probably have created an explicit thread that waits on packets; how does the GCD framework decide where a packet should go?

  3. Why would my app suddenly stop listening on the port? How do I detect/debug that?

  4. Does it matter that the GCDAsyncUdpSocket object is owned by the main screen, as opposed to the Delegate.m module?

  5. Is it appropriate to use the main dispatch queue, as I think I am doing? Indeed, am I doing that, and correctly?

I'm totally at a loss, so of course, any advice would be greatly appreaciated! No need to answer all the questions - especially if your answer to one is the solution!

Thanks!

This post ended about eight hours of torture that I received at the hands of the POSIX/GCDAsyncSocket/NSStream/NSNetService gods. For anyone coming across this: the cause of my GCDAsyncSocket connection reset by peer/remote peer disconnected errors was simply that I was connecting by IP on the LAN , instead of using a host name. (ie using the connectToAddress family of methods instead of the connectToHost family of methods). I fired up WireShark, downloaded X11, all that jazz and confirmed I was facing the same issue Bob was seeing – ARP activity around the disconnect time. Trying to connect to a host instead of an address confirmed Seth's take on the situation – it was issues with the router reassigning IP addresses.

This couldn't have been a more innocuous question on SO – question and answer with 0 upvotes, but both of you combined to give more more than enough information to solve what I thought was an intractable problem. Thanks so much!

It sounds like the receiving UDP socket is either being closed or shifted to a different address/port pair.

  • If its being closed, the only way outgoing packets could still work is if there were actually two sockets. Perhaps the receive one bound to port 12349 and the send one bound to port 0. Then maybe GCD is closing the receive one after some period of time. Given your followup comments about ARP, this seems less likely but is worth keeping in mind.

  • The ARP activity suggests that the iPad's IP address may be changing. If it were to change, or if it were to switch from the WiFi interface to a cellular interface, then the packets it sends would still get through, but packets sent to it would fail exactly as described.

Whatever is receiving those heart-beat messages, have it check the recvfrom address and make sure that any messages it sends back go to that exact address. And, of course, make sure to pay attention to endian (host vs network byte order).

I am assuming that there are no firewalls or NAT in between the two devices. If there are such things, then a whole different world of possibilities opens up.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM