-
Notifications
You must be signed in to change notification settings - Fork 0
Fix ObjC build error and add server socket API demo coverage #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
24cdc10
f10cb1c
1bbfbd0
cc061d1
6f0c457
89dc739
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -38,12 +38,14 @@ @interface GCDAsyncSocket () | |||||||||||||||||||
|
|
||||||||||||||||||||
| #if NW_FRAMEWORK_AVAILABLE | ||||||||||||||||||||
| @property (nonatomic, assign) nw_connection_t connection; | ||||||||||||||||||||
| @property (nonatomic, assign, nullable) nw_listener_t listener; | ||||||||||||||||||||
| #endif | ||||||||||||||||||||
|
|
||||||||||||||||||||
| @property (nonatomic, strong) dispatch_queue_t socketQueue; | ||||||||||||||||||||
| @property (nonatomic, strong) NWStreamBuffer *buffer; | ||||||||||||||||||||
| @property (nonatomic, strong) NSMutableArray<NWReadRequest *> *readQueue; | ||||||||||||||||||||
| @property (nonatomic, assign) BOOL isReadingContinuously; | ||||||||||||||||||||
| @property (atomic, assign) BOOL isListening; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // SSE / streaming text mode | ||||||||||||||||||||
| @property (nonatomic, strong, nullable) NWSSEParser *sseParser; | ||||||||||||||||||||
|
|
@@ -222,7 +224,11 @@ - (uint16_t)connectedPort { | |||||||||||||||||||
| - (uint16_t)localPort { | ||||||||||||||||||||
| __block uint16_t port = 0; | ||||||||||||||||||||
| [self performSyncOnSocketQueue:^{ | ||||||||||||||||||||
| port = _isConnected ? _localPort : 0; | ||||||||||||||||||||
| if (_isListening) { | ||||||||||||||||||||
| port = _localPort; | ||||||||||||||||||||
| } else { | ||||||||||||||||||||
| port = _isConnected ? _localPort : 0; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| }]; | ||||||||||||||||||||
| return port; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
@@ -237,6 +243,9 @@ - (NSString *)localHost { | |||||||||||||||||||
|
|
||||||||||||||||||||
| - (void)dealloc { | ||||||||||||||||||||
| #if NW_FRAMEWORK_AVAILABLE | ||||||||||||||||||||
| if (_listener) { | ||||||||||||||||||||
| nw_listener_cancel(_listener); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| if (_connection) { | ||||||||||||||||||||
| nw_connection_cancel(_connection); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
@@ -270,13 +279,230 @@ - (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)e | |||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr { | ||||||||||||||||||||
| (void)port; | ||||||||||||||||||||
| return [self acceptOnInterface:nil port:port error:errPtr]; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - (BOOL)acceptOnInterface:(NSString *)interface port:(uint16_t)port error:(NSError **)errPtr { | ||||||||||||||||||||
| #if NW_FRAMEWORK_AVAILABLE | ||||||||||||||||||||
| if (self.isListening) { | ||||||||||||||||||||
| if (errPtr) { | ||||||||||||||||||||
| *errPtr = [NSError errorWithDomain:GCDAsyncSocketErrorDomain | ||||||||||||||||||||
| code:GCDAsyncSocketErrorAlreadyConnected | ||||||||||||||||||||
| userInfo:@{NSLocalizedDescriptionKey: @"Socket is already listening."}]; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return NO; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_parameters_t parameters = nw_parameters_create_secure_tcp( | ||||||||||||||||||||
| NW_PARAMETERS_DISABLE_PROTOCOL, | ||||||||||||||||||||
| NW_PARAMETERS_DEFAULT_CONFIGURATION | ||||||||||||||||||||
| ); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (interface.length > 0) { | ||||||||||||||||||||
| // Bind to a specific interface/address | ||||||||||||||||||||
| NSString *portStr = [NSString stringWithFormat:@"%u", port]; | ||||||||||||||||||||
| nw_endpoint_t localEndpoint = nw_endpoint_create_host(interface.UTF8String, portStr.UTF8String); | ||||||||||||||||||||
| nw_parameters_set_local_endpoint(parameters, localEndpoint); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_listener_t listener = nw_listener_create_with_port([NSString stringWithFormat:@"%u", port].UTF8String, parameters); | ||||||||||||||||||||
| if (!listener) { | ||||||||||||||||||||
| if (errPtr) { | ||||||||||||||||||||
| *errPtr = [NSError errorWithDomain:GCDAsyncSocketErrorDomain | ||||||||||||||||||||
| code:GCDAsyncSocketErrorConnectionFailed | ||||||||||||||||||||
| userInfo:@{NSLocalizedDescriptionKey: @"Failed to create listener."}]; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return NO; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| self.listener = listener; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| __weak typeof(self) weakSelf = self; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_listener_set_state_changed_handler(listener, ^(nw_listener_state_t state, nw_error_t _Nullable error) { | ||||||||||||||||||||
| __strong typeof(weakSelf) strongSelf = weakSelf; | ||||||||||||||||||||
| if (!strongSelf) return; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| switch (state) { | ||||||||||||||||||||
| case nw_listener_state_ready: { | ||||||||||||||||||||
| strongSelf.isListening = YES; | ||||||||||||||||||||
| uint16_t assignedPort = nw_listener_get_port(listener); | ||||||||||||||||||||
| strongSelf.localPort = assignedPort; | ||||||||||||||||||||
| break; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| case nw_listener_state_failed: { | ||||||||||||||||||||
| strongSelf.isListening = NO; | ||||||||||||||||||||
| NSError *nsError = [strongSelf socketErrorWithCode:GCDAsyncSocketErrorConnectionFailed | ||||||||||||||||||||
| description:@"Listener failed." | ||||||||||||||||||||
| reason:@"NW listener entered failed state" | ||||||||||||||||||||
| nwError:error]; | ||||||||||||||||||||
| [strongSelf disconnectInternalWithError:nsError]; | ||||||||||||||||||||
| break; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| case nw_listener_state_cancelled: { | ||||||||||||||||||||
| strongSelf.isListening = NO; | ||||||||||||||||||||
| break; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| default: | ||||||||||||||||||||
| break; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_listener_set_new_connection_handler(listener, ^(nw_connection_t newConnection) { | ||||||||||||||||||||
| __strong typeof(weakSelf) strongSelf = weakSelf; | ||||||||||||||||||||
| if (!strongSelf) return; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Create a new GCDAsyncSocket for the accepted connection. | ||||||||||||||||||||
| // The new socket inherits the listener's delegate – this matches | ||||||||||||||||||||
| // GCDAsyncSocket from CocoaAsyncSocket. The user may reassign the | ||||||||||||||||||||
| // delegate on newSocket inside socket:didAcceptNewSocket: if needed. | ||||||||||||||||||||
| GCDAsyncSocket *newSocket = [[GCDAsyncSocket alloc] initWithDelegate:strongSelf.delegate | ||||||||||||||||||||
| delegateQueue:strongSelf.delegateQueue | ||||||||||||||||||||
| socketQueue:nil]; | ||||||||||||||||||||
| newSocket.connection = newConnection; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // State change handler for the accepted connection | ||||||||||||||||||||
| nw_connection_set_state_changed_handler(newConnection, ^(nw_connection_state_t state, nw_error_t _Nullable error) { | ||||||||||||||||||||
| [newSocket handleStateChange:state error:error]; | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_connection_set_queue(newConnection, newSocket.socketQueue); | ||||||||||||||||||||
| nw_connection_start(newConnection); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| dispatch_async(strongSelf.delegateQueue, ^{ | ||||||||||||||||||||
| id delegate = strongSelf.delegate; | ||||||||||||||||||||
| if ([delegate respondsToSelector:@selector(socket:didAcceptNewSocket:)]) { | ||||||||||||||||||||
| [delegate socket:strongSelf didAcceptNewSocket:newSocket]; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_listener_set_queue(listener, self.socketQueue); | ||||||||||||||||||||
| nw_listener_start(listener); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return YES; | ||||||||||||||||||||
| #else | ||||||||||||||||||||
| if (errPtr) { | ||||||||||||||||||||
| *errPtr = [NSError errorWithDomain:GCDAsyncSocketErrorDomain | ||||||||||||||||||||
| code:GCDAsyncSocketErrorConnectionFailed | ||||||||||||||||||||
| userInfo:@{NSLocalizedDescriptionKey: @"Network.framework is not available on this platform."}]; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return NO; | ||||||||||||||||||||
| #endif | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr { | ||||||||||||||||||||
| #if NW_FRAMEWORK_AVAILABLE | ||||||||||||||||||||
| if (self.isListening) { | ||||||||||||||||||||
| if (errPtr) { | ||||||||||||||||||||
| *errPtr = [NSError errorWithDomain:GCDAsyncSocketErrorDomain | ||||||||||||||||||||
| code:GCDAsyncSocketErrorAlreadyConnected | ||||||||||||||||||||
| userInfo:@{NSLocalizedDescriptionKey: @"Socket is already listening."}]; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return NO; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
Comment on lines
+394
to
+404
|
||||||||||||||||||||
| if (!url.isFileURL) { | ||||||||||||||||||||
| if (errPtr) { | ||||||||||||||||||||
| *errPtr = [NSError errorWithDomain:GCDAsyncSocketErrorDomain | ||||||||||||||||||||
| code:GCDAsyncSocketErrorInvalidParameter | ||||||||||||||||||||
| userInfo:@{NSLocalizedDescriptionKey: @"URL must be a file URL for Unix Domain Socket."}]; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return NO; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_parameters_t parameters = nw_parameters_create_secure_tcp( | ||||||||||||||||||||
| NW_PARAMETERS_DISABLE_PROTOCOL, | ||||||||||||||||||||
| NW_PARAMETERS_DEFAULT_CONFIGURATION | ||||||||||||||||||||
| ); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Remove existing socket file if present | ||||||||||||||||||||
| NSString *path = url.path; | ||||||||||||||||||||
| [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Construct a unix:// URL for the endpoint (Network.framework expects this scheme) | ||||||||||||||||||||
| NSString *unixURLString = [NSString stringWithFormat:@"unix://%@", path]; | ||||||||||||||||||||
| nw_endpoint_t localEndpoint = nw_endpoint_create_url(unixURLString.UTF8String); | ||||||||||||||||||||
| nw_parameters_set_local_endpoint(parameters, localEndpoint); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_listener_t listener = nw_listener_create(parameters); | ||||||||||||||||||||
| if (!listener) { | ||||||||||||||||||||
| if (errPtr) { | ||||||||||||||||||||
| *errPtr = [NSError errorWithDomain:GCDAsyncSocketErrorDomain | ||||||||||||||||||||
| code:GCDAsyncSocketErrorConnectionFailed | ||||||||||||||||||||
| userInfo:@{NSLocalizedDescriptionKey: @"Failed to create Unix Domain Socket listener."}]; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return NO; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| self.listener = listener; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| __weak typeof(self) weakSelf = self; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_listener_set_state_changed_handler(listener, ^(nw_listener_state_t state, nw_error_t _Nullable error) { | ||||||||||||||||||||
| __strong typeof(weakSelf) strongSelf = weakSelf; | ||||||||||||||||||||
| if (!strongSelf) return; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| switch (state) { | ||||||||||||||||||||
| case nw_listener_state_ready: { | ||||||||||||||||||||
| strongSelf.isListening = YES; | ||||||||||||||||||||
| break; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| case nw_listener_state_failed: { | ||||||||||||||||||||
| strongSelf.isListening = NO; | ||||||||||||||||||||
| NSError *nsError = [strongSelf socketErrorWithCode:GCDAsyncSocketErrorConnectionFailed | ||||||||||||||||||||
| description:@"Unix Domain Socket listener failed." | ||||||||||||||||||||
| reason:@"NW listener entered failed state" | ||||||||||||||||||||
| nwError:error]; | ||||||||||||||||||||
| [strongSelf disconnectInternalWithError:nsError]; | ||||||||||||||||||||
| break; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| case nw_listener_state_cancelled: { | ||||||||||||||||||||
| strongSelf.isListening = NO; | ||||||||||||||||||||
| break; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| default: | ||||||||||||||||||||
| break; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_listener_set_new_connection_handler(listener, ^(nw_connection_t newConnection) { | ||||||||||||||||||||
| __strong typeof(weakSelf) strongSelf = weakSelf; | ||||||||||||||||||||
| if (!strongSelf) return; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // See acceptOnInterface:port:error: for rationale on delegate sharing. | ||||||||||||||||||||
| GCDAsyncSocket *newSocket = [[GCDAsyncSocket alloc] initWithDelegate:strongSelf.delegate | ||||||||||||||||||||
| delegateQueue:strongSelf.delegateQueue | ||||||||||||||||||||
| socketQueue:nil]; | ||||||||||||||||||||
| newSocket.connection = newConnection; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_connection_set_state_changed_handler(newConnection, ^(nw_connection_state_t state, nw_error_t _Nullable error) { | ||||||||||||||||||||
| [newSocket handleStateChange:state error:error]; | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_connection_set_queue(newConnection, newSocket.socketQueue); | ||||||||||||||||||||
| nw_connection_start(newConnection); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| dispatch_async(strongSelf.delegateQueue, ^{ | ||||||||||||||||||||
| id delegate = strongSelf.delegate; | ||||||||||||||||||||
| if ([delegate respondsToSelector:@selector(socket:didAcceptNewSocket:)]) { | ||||||||||||||||||||
| [delegate socket:strongSelf didAcceptNewSocket:newSocket]; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| nw_listener_set_queue(listener, self.socketQueue); | ||||||||||||||||||||
| nw_listener_start(listener); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return YES; | ||||||||||||||||||||
| #else | ||||||||||||||||||||
| if (errPtr) { | ||||||||||||||||||||
| *errPtr = [NSError errorWithDomain:GCDAsyncSocketErrorDomain | ||||||||||||||||||||
| code:GCDAsyncSocketErrorInvalidParameter | ||||||||||||||||||||
| userInfo:@{NSLocalizedDescriptionKey: @"acceptOnPort:error: is not supported in NWAsyncSocketObjC."}]; | ||||||||||||||||||||
| code:GCDAsyncSocketErrorConnectionFailed | ||||||||||||||||||||
| userInfo:@{NSLocalizedDescriptionKey: @"Network.framework is not available on this platform."}]; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return NO; | ||||||||||||||||||||
| #endif | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - (BOOL)connectToHost:(NSString *)host | ||||||||||||||||||||
|
|
@@ -362,7 +588,18 @@ - (BOOL)connectToHost:(NSString *)host | |||||||||||||||||||
| - (void)disconnect { | ||||||||||||||||||||
| __weak typeof(self) weakSelf = self; | ||||||||||||||||||||
| dispatch_async(self.socketQueue, ^{ | ||||||||||||||||||||
| [weakSelf disconnectInternalWithError:nil]; | ||||||||||||||||||||
| __strong typeof(weakSelf) strongSelf = weakSelf; | ||||||||||||||||||||
| if (!strongSelf) return; | ||||||||||||||||||||
| #if NW_FRAMEWORK_AVAILABLE | ||||||||||||||||||||
| // Stop listener if in server mode | ||||||||||||||||||||
| if (strongSelf.listener) { | ||||||||||||||||||||
| nw_listener_cancel(strongSelf.listener); | ||||||||||||||||||||
| strongSelf.listener = nil; | ||||||||||||||||||||
| strongSelf.isListening = NO; | ||||||||||||||||||||
| strongSelf.localPort = 0; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| #endif | ||||||||||||||||||||
|
Comment on lines
+593
to
+601
|
||||||||||||||||||||
| #if NW_FRAMEWORK_AVAILABLE | |
| // Stop listener if in server mode | |
| if (strongSelf.listener) { | |
| nw_listener_cancel(strongSelf.listener); | |
| strongSelf.listener = nil; | |
| strongSelf.isListening = NO; | |
| strongSelf.localPort = 0; | |
| } | |
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,6 +53,9 @@ typedef NS_ENUM(NSInteger, GCDAsyncSocketError) { | |
| /// Whether the socket is currently disconnected. | ||
| @property (atomic, readonly) BOOL isDisconnected; | ||
|
|
||
| /// Whether the socket is currently listening for incoming connections (server mode). | ||
| @property (atomic, readonly) BOOL isListening; | ||
|
|
||
|
Comment on lines
53
to
+58
|
||
| /// Whether the socket is using a secure TLS transport. | ||
| @property (atomic, readonly) BOOL isSecure; | ||
|
|
||
|
|
@@ -109,8 +112,17 @@ typedef NS_ENUM(NSInteger, GCDAsyncSocketError) { | |
| error:(NSError **)errPtr; | ||
|
|
||
| /// Compatibility API for CocoaAsyncSocket server mode. | ||
| /// Listen for incoming TCP connections on all interfaces on the given port. | ||
| /// Pass port 0 to let the system assign an available port (query via `localPort`). | ||
| - (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr; | ||
|
|
||
| /// Listen for incoming TCP connections on a specific interface/address and port. | ||
| /// Pass @"localhost" or @"127.0.0.1" to restrict connections to the local machine. | ||
| - (BOOL)acceptOnInterface:(nullable NSString *)interface port:(uint16_t)port error:(NSError **)errPtr; | ||
|
|
||
| /// Listen for incoming connections on a Unix Domain Socket at the given file URL. | ||
| - (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr; | ||
|
|
||
| // MARK: - Disconnect | ||
|
|
||
| /// Disconnect the socket gracefully. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
acceptOnInterface:port:error:only checksself.isListeningto reject a second listen. During the window afterself.listeneris created but before the state handler setsisListening = YES, a second call can succeed and replace the listener. Also, calling accept whileisConnectedis true should be rejected. Consider guarding on(self.listener != NULL) || self.isListening || self.isConnected(and returning an appropriate error).