From 6f62be0d6ec114fea74cd6c07688cdcd5681850c Mon Sep 17 00:00:00 2001 From: Gavin Conway Date: Sat, 12 Aug 2017 22:18:39 +1000 Subject: [PATCH] Updated CocoaAsyncSocket. Added SO_REUSEPORT --- ios/CocoaAsyncSocket/GCDAsyncUdpSocket.h | 214 +++++++++++++---------- ios/CocoaAsyncSocket/GCDAsyncUdpSocket.m | 207 ++++++++++++++++++---- ios/UdpSocketClient.m | 1 + 3 files changed, 296 insertions(+), 126 deletions(-) diff --git a/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.h b/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.h index 77f6367..b0b244d 100644 --- a/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.h +++ b/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.h @@ -13,14 +13,14 @@ #import #import +NS_ASSUME_NONNULL_BEGIN extern NSString *const GCDAsyncUdpSocketException; extern NSString *const GCDAsyncUdpSocketErrorDomain; extern NSString *const GCDAsyncUdpSocketQueueName; extern NSString *const GCDAsyncUdpSocketThreadName; -enum GCDAsyncUdpSocketError -{ +typedef NS_ENUM(NSInteger, GCDAsyncUdpSocketError) { GCDAsyncUdpSocketNoError = 0, // Never used GCDAsyncUdpSocketBadConfigError, // Invalid configuration GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed @@ -28,7 +28,59 @@ enum GCDAsyncUdpSocketError GCDAsyncUdpSocketClosedError, // The socket was closed GCDAsyncUdpSocketOtherError, // Description provided in userInfo }; -typedef enum GCDAsyncUdpSocketError GCDAsyncUdpSocketError; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@class GCDAsyncUdpSocket; + +@protocol GCDAsyncUdpSocketDelegate +@optional + +/** + * By design, UDP is a connectionless protocol, and connecting is not needed. + * However, you may optionally choose to connect to a particular host for reasons + * outlined in the documentation for the various connect methods listed above. + * + * This method is called if one of the connect methods are invoked, and the connection is successful. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address; + +/** + * By design, UDP is a connectionless protocol, and connecting is not needed. + * However, you may optionally choose to connect to a particular host for reasons + * outlined in the documentation for the various connect methods listed above. + * + * This method is called if one of the connect methods are invoked, and the connection fails. + * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error; + +/** + * Called when the datagram with the given tag has been sent. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag; + +/** + * Called if an error occurs while trying to send a datagram. + * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error; + +/** + * Called when the socket has received the requested datagram. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data + fromAddress:(NSData *)address + withFilterContext:(nullable id)filterContext; + +/** + * Called when the socket is closed. +**/ +- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError * _Nullable)error; + +@end /** * You may optionally set a receive filter for the socket. @@ -78,7 +130,7 @@ typedef enum GCDAsyncUdpSocketError GCDAsyncUdpSocketError; * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; * **/ -typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id *context); +typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id __nullable * __nonnull context); /** * You may optionally set a send filter for the socket. @@ -126,24 +178,24 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * * The delegate queue and socket queue can optionally be the same. **/ -- (id)init; -- (id)initWithSocketQueue:(dispatch_queue_t)sq; -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq; -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq; +- (instancetype)init; +- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq; +- (instancetype)initWithDelegate:(nullable id )aDelegate delegateQueue:(nullable dispatch_queue_t)dq; +- (instancetype)initWithDelegate:(nullable id )aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq; #pragma mark Configuration -- (id)delegate; -- (void)setDelegate:(id)delegate; -- (void)synchronouslySetDelegate:(id)delegate; +- (nullable id )delegate; +- (void)setDelegate:(nullable id )delegate; +- (void)synchronouslySetDelegate:(nullable id )delegate; -- (dispatch_queue_t)delegateQueue; -- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)delegateQueue; +- (nullable dispatch_queue_t)delegateQueue; +- (void)setDelegateQueue:(nullable dispatch_queue_t)delegateQueue; +- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue; -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr; -- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; +- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr; +- (void)setDelegate:(nullable id )delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; +- (void)synchronouslySetDelegate:(nullable id )delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; /** * By default, both IPv4 and IPv6 are enabled. @@ -179,7 +231,7 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, /** * Gets/Sets the maximum size of the buffer that will be allocated for receive operations. - * The default maximum size is 9216 bytes. + * The default maximum size is 65535 bytes. * * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. @@ -199,12 +251,27 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, - (uint32_t)maxReceiveIPv6BufferSize; - (void)setMaxReceiveIPv6BufferSize:(uint32_t)max; +/** + * Gets/Sets the maximum size of the buffer that will be allocated for send operations. + * The default maximum size is 65535 bytes. + * + * Given that a typical link MTU is 1500 bytes, a large UDP datagram will have to be + * fragmented, and that’s both expensive and risky (if one fragment goes missing, the + * entire datagram is lost). You are much better off sending a large number of smaller + * UDP datagrams, preferably using a path MTU algorithm to avoid fragmentation. + * + * You must set it before the sockt is created otherwise it won't work. + * + **/ +- (uint16_t)maxSendBufferSize; +- (void)setMaxSendBufferSize:(uint16_t)max; + /** * User data allows you to associate arbitrary information with the socket. * This data is not used internally in any way. **/ -- (id)userData; -- (void)setUserData:(id)arbitraryUserData; +- (nullable id)userData; +- (void)setUserData:(nullable id)arbitraryUserData; #pragma mark Diagnostics @@ -217,16 +284,16 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * Note: Address info may not be available until after the socket has been binded, connected * or until after data has been sent. **/ -- (NSData *)localAddress; -- (NSString *)localHost; +- (nullable NSData *)localAddress; +- (nullable NSString *)localHost; - (uint16_t)localPort; -- (NSData *)localAddress_IPv4; -- (NSString *)localHost_IPv4; +- (nullable NSData *)localAddress_IPv4; +- (nullable NSString *)localHost_IPv4; - (uint16_t)localPort_IPv4; -- (NSData *)localAddress_IPv6; -- (NSString *)localHost_IPv6; +- (nullable NSData *)localAddress_IPv6; +- (nullable NSString *)localHost_IPv6; - (uint16_t)localPort_IPv6; /** @@ -239,8 +306,8 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * will not be available unless the socket is explicitly connected to a remote host/port. * If the socket is not connected, these methods will return nil / 0. **/ -- (NSData *)connectedAddress; -- (NSString *)connectedHost; +- (nullable NSData *)connectedAddress; +- (nullable NSString *)connectedHost; - (uint16_t)connectedPort; /** @@ -319,7 +386,7 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * On success, returns YES. * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. **/ -- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr; +- (BOOL)bindToPort:(uint16_t)port interface:(nullable NSString *)interface error:(NSError **)errPtr; /** * Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. @@ -418,10 +485,21 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * On success, returns YES. * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. **/ -- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr; +- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; - (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr; -- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr; +- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; + +#pragma mark Reuse Port + +/** + * By default, only one socket can be bound to a given IP address + port at a time. + * To enable multiple processes to simultaneously bind to the same address+port, + * you need to enable this functionality in the socket. All processes that wish to + * use the address+port simultaneously must all enable reuse port on the socket + * bound to that port. + **/ +- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr; #pragma mark Broadcast @@ -597,7 +675,7 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below), * passing YES for the isAsynchronous parameter. **/ -- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue; +- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; /** * The receive filter can be run via dispatch_async or dispatch_sync. @@ -612,8 +690,8 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. * For example, you can't query properties on the socket. **/ -- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock - withQueue:(dispatch_queue_t)filterQueue +- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock + withQueue:(nullable dispatch_queue_t)filterQueue isAsynchronous:(BOOL)isAsynchronous; #pragma mark Receiving @@ -729,7 +807,7 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below), * passing YES for the isAsynchronous parameter. **/ -- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue; +- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; /** * The receive filter can be run via dispatch_async or dispatch_sync. @@ -744,8 +822,8 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. * For example, you can't query properties on the socket. **/ -- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock - withQueue:(dispatch_queue_t)filterQueue +- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock + withQueue:(nullable dispatch_queue_t)filterQueue isAsynchronous:(BOOL)isAsynchronous; #pragma mark Closing @@ -897,8 +975,8 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * However, if you need one for any reason, * these methods are a convenient way to get access to a safe instance of one. **/ -- (CFReadStreamRef)readStream; -- (CFWriteStreamRef)writeStream; +- (nullable CFReadStreamRef)readStream; +- (nullable CFWriteStreamRef)writeStream; /** * This method is only available from within the context of a performBlock: invocation. @@ -931,66 +1009,16 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * Extracting host/port/family information from raw address data. **/ -+ (NSString *)hostFromAddress:(NSData *)address; ++ (nullable NSString *)hostFromAddress:(NSData *)address; + (uint16_t)portFromAddress:(NSData *)address; + (int)familyFromAddress:(NSData *)address; + (BOOL)isIPv4Address:(NSData *)address; + (BOOL)isIPv6Address:(NSData *)address; -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address; -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address; - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@protocol GCDAsyncUdpSocketDelegate -@optional - -/** - * By design, UDP is a connectionless protocol, and connecting is not needed. - * However, you may optionally choose to connect to a particular host for reasons - * outlined in the documentation for the various connect methods listed above. - * - * This method is called if one of the connect methods are invoked, and the connection is successful. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address; - -/** - * By design, UDP is a connectionless protocol, and connecting is not needed. - * However, you may optionally choose to connect to a particular host for reasons - * outlined in the documentation for the various connect methods listed above. - * - * This method is called if one of the connect methods are invoked, and the connection fails. - * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error; - -/** - * Called when the datagram with the given tag has been sent. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag; - -/** - * Called if an error occurs while trying to send a datagram. - * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error; - -/** - * Called when the socket has received the requested datagram. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data - fromAddress:(NSData *)address - withFilterContext:(id)filterContext; - -/** - * Called when the socket is closed. -**/ -- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error; ++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr fromAddress:(NSData *)address; ++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr family:(int * __nullable)afPtr fromAddress:(NSData *)address; @end +NS_ASSUME_NONNULL_END diff --git a/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.m b/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.m index d37e1af..58a5fdd 100644 --- a/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.m +++ b/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.m @@ -166,6 +166,8 @@ enum GCDAsyncUdpSocketConfig uint16_t max4ReceiveSize; uint32_t max6ReceiveSize; + + uint16_t maxSendSize; int socket4FD; int socket6FD; @@ -350,14 +352,14 @@ enum GCDAsyncUdpSocketConfig return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; } -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq +- (id)initWithDelegate:(id )aDelegate delegateQueue:(dispatch_queue_t)dq { LogTrace(); return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; } -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq +- (id)initWithDelegate:(id )aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq { LogTrace(); @@ -373,9 +375,11 @@ enum GCDAsyncUdpSocketConfig #endif } - max4ReceiveSize = 9216; - max6ReceiveSize = 9216; + max4ReceiveSize = 65535; + max6ReceiveSize = 65535; + maxSendSize = 65535; + socket4FD = SOCKET_NULL; socket6FD = SOCKET_NULL; @@ -488,7 +492,7 @@ enum GCDAsyncUdpSocketConfig } } -- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously +- (void)setDelegate:(id )newDelegate synchronously:(BOOL)synchronously { dispatch_block_t block = ^{ delegate = newDelegate; @@ -505,12 +509,12 @@ enum GCDAsyncUdpSocketConfig } } -- (void)setDelegate:(id)newDelegate +- (void)setDelegate:(id )newDelegate { [self setDelegate:newDelegate synchronously:NO]; } -- (void)synchronouslySetDelegate:(id)newDelegate +- (void)synchronouslySetDelegate:(id )newDelegate { [self setDelegate:newDelegate synchronously:YES]; } @@ -566,7 +570,7 @@ enum GCDAsyncUdpSocketConfig [self setDelegateQueue:newDelegateQueue synchronously:YES]; } -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr +- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr { if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { @@ -588,7 +592,7 @@ enum GCDAsyncUdpSocketConfig } } -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously +- (void)setDelegate:(id )newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously { dispatch_block_t block = ^{ @@ -613,12 +617,12 @@ enum GCDAsyncUdpSocketConfig } } -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +- (void)setDelegate:(id )newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue { [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; } -- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +- (void)synchronouslySetDelegate:(id )newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue { [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; } @@ -864,6 +868,37 @@ enum GCDAsyncUdpSocketConfig dispatch_async(socketQueue, block); } +- (void)setMaxSendBufferSize:(uint16_t)max +{ + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); + + maxSendSize = max; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (uint16_t)maxSendBufferSize +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + result = maxSendSize; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} - (id)userData { @@ -906,9 +941,9 @@ enum GCDAsyncUdpSocketConfig { LogTrace(); - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) { - id theDelegate = delegate; NSData *address = [anAddress copy]; // In case param is NSMutableData dispatch_async(delegateQueue, ^{ @autoreleasepool { @@ -922,10 +957,9 @@ enum GCDAsyncUdpSocketConfig { LogTrace(); - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) { - id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate udpSocket:self didNotConnect:error]; @@ -937,10 +971,9 @@ enum GCDAsyncUdpSocketConfig { LogTrace(); - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) { - id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate udpSocket:self didSendDataWithTag:tag]; @@ -952,10 +985,9 @@ enum GCDAsyncUdpSocketConfig { LogTrace(); - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) { - id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error]; @@ -969,10 +1001,9 @@ enum GCDAsyncUdpSocketConfig SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:); - if (delegateQueue && [delegate respondsToSelector:selector]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:selector]) { - id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context]; @@ -984,10 +1015,9 @@ enum GCDAsyncUdpSocketConfig { LogTrace(); - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) { - id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate udpSocketDidClose:self withError:error]; @@ -1204,9 +1234,17 @@ enum GCDAsyncUdpSocketConfig } else if (res->ai_family == AF_INET6) { - // Found IPv6 address - // Wrap the native address structure and add to list - + + // Fixes connection issues with IPv6, it is the same solution for udp socket. + // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 + struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)res->ai_addr; + in_port_t *portPtr = &sockaddr->sin6_port; + if ((portPtr != NULL) && (*portPtr == 0)) { + *portPtr = htons(port); + } + + // Found IPv6 address + // Wrap the native address structure and add to list [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; } } @@ -1982,6 +2020,39 @@ enum GCDAsyncUdpSocketConfig close(socketFD); return SOCKET_NULL; } + + /** + * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. + * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. + * + * The default maximum size of the UDP buffer in iOS is 9216 bytes. + * + * This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and + * #535(GCDAsyncUDPSocket can not send data when data is greater than 9K) + * + * + * Enlarge the maximum size of UDP packet. + * I can not ensure the protocol type now so that the max size is set to 65535 :) + **/ + + status = setsockopt(socketFD, SOL_SOCKET, SO_SNDBUF, (const char*)&maxSendSize, sizeof(int)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"]; + close(socketFD); + return SOCKET_NULL; + } + + status = setsockopt(socketFD, SOL_SOCKET, SO_RCVBUF, (const char*)&maxSendSize, sizeof(int)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"]; + close(socketFD); + return SOCKET_NULL; + } + return socketFD; }; @@ -3453,6 +3524,70 @@ enum GCDAsyncUdpSocketConfig return result; } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Reuse port +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + int value = flag ? 1 : 0; + if (socket4FD != SOCKET_NULL) + { + int error = setsockopt(socket4FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); + + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + result = YES; + } + + if (socket6FD != SOCKET_NULL) + { + int error = setsockopt(socket6FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); + + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + result = YES; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Broadcast //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -3525,6 +3660,8 @@ enum GCDAsyncUdpSocketConfig LogWarn(@"Ignoring attempt to send nil/empty data."); return; } + + GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; @@ -4293,7 +4430,9 @@ enum GCDAsyncUdpSocketConfig struct sockaddr_in sockaddr4; socklen_t sockaddr4len = sizeof(sockaddr4); - size_t bufSize = MIN(max4ReceiveSize, socket4FDBytesAvailable); + // #222: GCD does not necessarily return the size of an entire UDP packet + // from dispatch_source_get_data(), so we must use the maximum packet size. + size_t bufSize = max4ReceiveSize; void *buf = malloc(bufSize); result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len); @@ -4328,7 +4467,9 @@ enum GCDAsyncUdpSocketConfig struct sockaddr_in6 sockaddr6; socklen_t sockaddr6len = sizeof(sockaddr6); - size_t bufSize = MIN(max6ReceiveSize, socket6FDBytesAvailable); + // #222: GCD does not necessarily return the size of an entire UDP packet + // from dispatch_source_get_data(), so we must use the maximum packet size. + size_t bufSize = max6ReceiveSize; void *buf = malloc(bufSize); result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len); diff --git a/ios/UdpSocketClient.m b/ios/UdpSocketClient.m index 19bad9e..918964e 100644 --- a/ios/UdpSocketClient.m +++ b/ios/UdpSocketClient.m @@ -96,6 +96,7 @@ NSString *const RCTUDPErrorDomain = @"RCTUDPErrorDomain"; [_udpSocket setMaxReceiveIPv4BufferSize:UINT16_MAX]; [_udpSocket setMaxReceiveIPv6BufferSize:UINT16_MAX]; + [_udpSocket enableReusePort:true error:error]; BOOL result; if (address) {