Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

Reconnect sockets on iOS when resuming from background #292

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 112 additions & 12 deletions GCDWebServer/Core/GCDWebServer.m
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ @interface GCDWebServer () {
dispatch_source_t _source6;
CFNetServiceRef _registrationService;
CFNetServiceRef _resolutionService;
int ipv4ListeningSocket;
int ipv6ListeningSocket;
DNSServiceRef _dnsService;
CFSocketRef _dnsSocket;
CFRunLoopSourceRef _dnsSource;
Expand Down Expand Up @@ -463,6 +465,11 @@ - (int)_createListeningSocket:(BOOL)useIPv6
error:(NSError**)error {
int listeningSocket = socket(useIPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listeningSocket > 0) {
if (!useIPv6) {
ipv4ListeningSocket = listeningSocket;
} else {
ipv6ListeningSocket = listeningSocket;
}
int yes = 1;
setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));

Expand Down Expand Up @@ -570,12 +577,7 @@ - (BOOL)_start:(NSError**)error {
}
}

struct sockaddr_in6 addr6;
bzero(&addr6, sizeof(addr6));
addr6.sin6_len = sizeof(addr6);
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(port);
addr6.sin6_addr = bindToLocalhost ? in6addr_loopback : in6addr_any;
struct sockaddr_in6 addr6 = [self generateAddressWithPort:port bindToLocalhost:bindToLocalhost];
int listeningSocket6 = [self _createListeningSocket:YES localAddress:&addr6 length:sizeof(addr6) maxPendingConnections:maxPendingConnections error:error];
if (listeningSocket6 <= 0) {
close(listeningSocket4);
Expand Down Expand Up @@ -740,6 +742,81 @@ - (void)_stop {

#if TARGET_OS_IPHONE

- (void)resetIpv4SocketIfError {
int error = 0;
socklen_t len = sizeof(error);
int retval = getsockopt(ipv4ListeningSocket, SOL_SOCKET, SO_ERROR, &error, &len);

if (retval != 0) {
/* there was a problem getting the error code */
GWS_LOG_ERROR(@"error getting socket error code: %s\n", strerror(retval));
return;
}

if (error != 0) {
/* socket has a non zero error status */
GWS_LOG_INFO(@"Socket error: %s on socket %d\n", strerror(error), ipv4ListeningSocket);
dispatch_source_cancel(_source4);
_source4 = nil;
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
BOOL bindToLocalhost = [_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue];
NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];

struct sockaddr_in addr4 = [self generateIpv4AddressWithPort:port bindToLocalhost:bindToLocalhost];
NSError* nsError = nil;
int listeningSocket4 = [self _createListeningSocket:NO
localAddress:&addr4
length:sizeof(addr4)
maxPendingConnections:maxPendingConnections
error:&nsError];

if (listeningSocket4 <= 0) {
GWS_LOG_ERROR(@"Failed to create the IPv4 socket\n");
}
_source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
// Need to investigate
dispatch_resume(_source4);
}
}

- (void)resetIpv6SocketIfError {
int error = 0;
NSError* nsError = nil;
socklen_t len = sizeof(error);
int retval = getsockopt(ipv6ListeningSocket, SOL_SOCKET, SO_ERROR, &error, &len);

if (retval != 0) {
/* there was a problem getting the error code */
GWS_LOG_ERROR(@"error getting socket error code: %s\n", strerror(retval));
return;
}

if (error != 0) {
/* socket has a non zero error status */
GWS_LOG_INFO(@"Socket error: %s on socket %d\n", strerror(error), ipv6ListeningSocket);

dispatch_source_cancel(_source6);
_source6 = nil;
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
BOOL bindToLocalhost = [_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue];
NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];

struct sockaddr_in6 addr6 = [self generateAddressWithPort:port bindToLocalhost:bindToLocalhost];
int listeningSocket6 = [self _createListeningSocket:YES
localAddress:&addr6
length:sizeof(addr6)
maxPendingConnections:maxPendingConnections
error:&nsError];
if (listeningSocket6 <= 0) {
GWS_LOG_ERROR(@"Failed to create the IPv6 socket\n");
}
_source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];

// Need to investigate
dispatch_resume(_source6);
}
}

- (void)_didEnterBackground:(NSNotification*)notification {
GWS_DCHECK([NSThread isMainThread]);
GWS_LOG_DEBUG(@"Did enter background");
Expand All @@ -751,13 +828,39 @@ - (void)_didEnterBackground:(NSNotification*)notification {
- (void)_willEnterForeground:(NSNotification*)notification {
GWS_DCHECK([NSThread isMainThread]);
GWS_LOG_DEBUG(@"Will enter foreground");
if (!_source4) {

if (_suspendInBackground && !_source4) {
[self _start:NULL]; // TODO: There's probably nothing we can do on failure
}

if ([self isRunning]) {
[self resetIpv4SocketIfError];
[self resetIpv6SocketIfError];
}
}

#endif

- (struct sockaddr_in)generateIpv4AddressWithPort:(NSInteger)port bindToLocalhost:(BOOL)bindToLocalhost {
struct sockaddr_in addr4;
bzero(&addr4, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
addr4.sin_port = htons(port);
addr4.sin_addr.s_addr = bindToLocalhost ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
return addr4;
}

- (struct sockaddr_in6)generateAddressWithPort:(NSInteger)port bindToLocalhost:(BOOL)bindToLocalhost {
struct sockaddr_in6 addr6;
bzero(&addr6, sizeof(addr6));
addr6.sin6_len = sizeof(addr6);
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(port);
addr6.sin6_addr = bindToLocalhost ? in6addr_loopback : in6addr_any;
return addr6;
}

- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error {
if (_options == nil) {
_options = options ? [options copy] : @{};
Expand All @@ -774,8 +877,8 @@ - (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error {
#if TARGET_OS_IPHONE
if (_suspendInBackground) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
#endif
return YES;
} else {
Expand All @@ -791,10 +894,7 @@ - (BOOL)isRunning {
- (void)stop {
if (_options) {
#if TARGET_OS_IPHONE
if (_suspendInBackground) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
#endif
if (_source4) {
[self _stop];
Expand Down