1010// 3. UTF-8 Safety — Multi-byte character boundary detection
1111// 4. NWReadRequest — Read-request queue types
1212// 5. GCDAsyncSocket — Connection usage pattern
13+ // 6. GCDAsyncSocket — Server socket API (accept/listen)
1314//
1415// Build (from repository root):
1516// clang -framework Foundation \
@@ -422,10 +423,19 @@ static void demoReadRequest(void) {
422423
423424// Sample delegate class for demonstration
424425@interface DemoSocketDelegate : NSObject <GCDAsyncSocketDelegate>
426+ @property (nonatomic , strong ) NSMutableArray <GCDAsyncSocket *> *acceptedSockets;
425427@end
426428
427429@implementation DemoSocketDelegate
428430
431+ - (instancetype )init {
432+ self = [super init ];
433+ if (self) {
434+ _acceptedSockets = [NSMutableArray array ];
435+ }
436+ return self;
437+ }
438+
429439- (void )socket : (GCDAsyncSocket *)sock didConnectToHost : (NSString *)host port : (uint16_t )port {
430440 NSLog (@" Connected to %@ :%u " , host, port);
431441 // Send an HTTP GET request
@@ -458,6 +468,11 @@ - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error {
458468 NSLog (@" Disconnected: %@ " , error ? error.localizedDescription : @" clean" );
459469}
460470
471+ - (void )socket : (GCDAsyncSocket *)sock didAcceptNewSocket : (GCDAsyncSocket *)newSocket {
472+ NSLog (@" Accepted new connection: %@ " , newSocket);
473+ [self .acceptedSockets addObject: newSocket];
474+ }
475+
461476- (void )socket : (GCDAsyncSocket *)sock didReceiveSSEEvent : (NWSSEEvent *)event {
462477 NSLog (@" SSE Event: type=%@ data=%@ " , event.event , event.data );
463478}
@@ -538,9 +553,142 @@ static void demoGCDAsyncSocketUsage(void) {
538553}
539554
540555// ============================================================================
541- #pragma mark - Main Menu
556+ #pragma mark - 6. GCDAsyncSocket Server API
542557// ============================================================================
543558
559+ static void demoServerSocket (void ) {
560+ printHeader (@" 6. GCDAsyncSocket — Server Socket API" );
561+
562+ DemoSocketDelegate *delegate = [[DemoSocketDelegate alloc ] init ];
563+ dispatch_queue_t queue = dispatch_queue_create (" com.demo.server" , DISPATCH_QUEUE_SERIAL);
564+
565+ // ---- 6a. acceptOnPort: ----
566+ printSubHeader (@" 6a. acceptOnPort: — Listen on a TCP port" );
567+
568+ GCDAsyncSocket *serverSocket = [[GCDAsyncSocket alloc ] initWithDelegate: delegate
569+ delegateQueue: queue];
570+ printf (" Created server socket\n " );
571+ printf (" isListening: %s (expected: NO)\n " , serverSocket.isListening ? " YES" : " NO" );
572+ printf (" isConnected: %s (expected: NO)\n " , serverSocket.isConnected ? " YES" : " NO" );
573+
574+ NSError *err = nil ;
575+ BOOL ok = [serverSocket acceptOnPort: 0 error: &err];
576+ printf (" acceptOnPort:0 → %s (port 0 lets the system choose)\n " , ok ? " YES" : " NO" );
577+ if (err) {
578+ printf (" Error: %s \n " , err.localizedDescription .UTF8String );
579+ }
580+ // Give the listener a moment to start
581+ [[NSRunLoop currentRunLoop ] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.3 ]];
582+ printf (" isListening: %s (expected: YES)\n " , serverSocket.isListening ? " YES" : " NO" );
583+ printf (" localPort: %u (system-assigned)\n " , serverSocket.localPort );
584+
585+ // ---- 6b. Double-accept error ----
586+ printSubHeader (@" 6b. Double-accept error — Calling accept while already listening" );
587+
588+ NSError *doubleErr = nil ;
589+ BOOL doubleOk = [serverSocket acceptOnPort: 0 error: &doubleErr];
590+ printf (" acceptOnPort:0 again → %s (expected: NO)\n " , doubleOk ? " YES" : " NO" );
591+ if (doubleErr) {
592+ printf (" Error: %s (expected: already listening)\n " , doubleErr.localizedDescription .UTF8String );
593+ }
594+ printf (" ✅ Double-accept correctly returns error\n " );
595+
596+ // ---- 6c. Client connects to server ----
597+ printSubHeader (@" 6c. Client → Server connection — Verify didAcceptNewSocket:" );
598+
599+ uint16_t serverPort = serverSocket.localPort ;
600+ GCDAsyncSocket *clientSocket = [[GCDAsyncSocket alloc ] initWithDelegate: delegate
601+ delegateQueue: queue];
602+ NSError *connectErr = nil ;
603+ BOOL connected = [clientSocket connectToHost: @" 127.0.0.1" onPort: serverPort error: &connectErr];
604+ printf (" Client connectToHost:127.0.0.1 onPort:%u → %s \n " , serverPort, connected ? " YES" : " NO" );
605+ if (connectErr) {
606+ printf (" Connect Error: %s \n " , connectErr.localizedDescription .UTF8String );
607+ }
608+
609+ // Pump the run loop to allow the connection and accept to complete
610+ [[NSRunLoop currentRunLoop ] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.5 ]];
611+ printf (" Accepted sockets count: %lu (expected: 1)\n " ,
612+ (unsigned long )delegate.acceptedSockets .count );
613+ if (delegate.acceptedSockets .count > 0 ) {
614+ GCDAsyncSocket *accepted = delegate.acceptedSockets [0 ];
615+ printf (" Accepted socket isConnected: %s \n " , accepted.isConnected ? " YES" : " NO" );
616+ printf (" ✅ Server accepted client connection via didAcceptNewSocket:\n " );
617+ } else {
618+ printf (" ⚠️ No accepted socket yet (listener may need more time)\n " );
619+ }
620+
621+ // ---- 6d. Disconnect server ----
622+ printSubHeader (@" 6d. Disconnect server — Verify listener stops" );
623+
624+ [serverSocket disconnect ];
625+ [[NSRunLoop currentRunLoop ] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.3 ]];
626+ printf (" isListening after disconnect: %s (expected: NO)\n " ,
627+ serverSocket.isListening ? " YES" : " NO" );
628+ printf (" ✅ Server socket disconnected, listener stopped\n " );
629+
630+ // Clean up client
631+ [clientSocket disconnect ];
632+ for (GCDAsyncSocket *s in delegate.acceptedSockets ) {
633+ [s disconnect ];
634+ }
635+
636+ // ---- 6e. acceptOnInterface:port: ----
637+ printSubHeader (@" 6e. acceptOnInterface:port: — Listen on localhost only" );
638+
639+ GCDAsyncSocket *localServer = [[GCDAsyncSocket alloc ] initWithDelegate: delegate
640+ delegateQueue: queue];
641+ NSError *ifErr = nil ;
642+ BOOL ifOk = [localServer acceptOnInterface: @" 127.0.0.1" port: 0 error: &ifErr];
643+ printf (" acceptOnInterface:@\" 127.0.0.1\" port:0 → %s \n " , ifOk ? " YES" : " NO" );
644+ if (ifErr) {
645+ printf (" Error: %s \n " , ifErr.localizedDescription .UTF8String );
646+ }
647+ [[NSRunLoop currentRunLoop ] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.3 ]];
648+ printf (" isListening: %s (expected: YES)\n " , localServer.isListening ? " YES" : " NO" );
649+ printf (" ✅ acceptOnInterface works\n " );
650+ [localServer disconnect ];
651+ [[NSRunLoop currentRunLoop ] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1 ]];
652+
653+ // ---- 6f. acceptOnUrl: (Unix Domain Socket) ----
654+ printSubHeader (@" 6f. acceptOnUrl: — Listen on Unix Domain Socket" );
655+
656+ GCDAsyncSocket *udsServer = [[GCDAsyncSocket alloc ] initWithDelegate: delegate
657+ delegateQueue: queue];
658+ NSString *tmpPath = [NSTemporaryDirectory () stringByAppendingPathComponent: @" nwasyncsocket_demo.sock" ];
659+ NSURL *sockURL = [NSURL fileURLWithPath: tmpPath];
660+ NSError *udsErr = nil ;
661+ BOOL udsOk = [udsServer acceptOnUrl: sockURL error: &udsErr];
662+ printf (" acceptOnUrl:%s → %s \n " , tmpPath.UTF8String , udsOk ? " YES" : " NO" );
663+ if (udsErr) {
664+ printf (" Error: %s \n " , udsErr.localizedDescription .UTF8String );
665+ }
666+ [[NSRunLoop currentRunLoop ] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.3 ]];
667+ printf (" isListening: %s (expected: YES)\n " , udsServer.isListening ? " YES" : " NO" );
668+ printf (" ✅ acceptOnUrl works\n " );
669+ [udsServer disconnect ];
670+
671+ // Clean up socket file
672+ [[NSFileManager defaultManager ] removeItemAtPath: tmpPath error: nil ];
673+ [[NSRunLoop currentRunLoop ] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1 ]];
674+
675+ // ---- 6g. acceptOnUrl: with non-file URL error ----
676+ printSubHeader (@" 6g. acceptOnUrl: error — Non-file URL rejected" );
677+
678+ GCDAsyncSocket *badUds = [[GCDAsyncSocket alloc ] initWithDelegate: delegate
679+ delegateQueue: queue];
680+ NSError *badErr = nil ;
681+ NSURL *httpUrl = [NSURL URLWithString: @" http://example.com" ];
682+ BOOL badOk = [badUds acceptOnUrl: httpUrl error: &badErr];
683+ printf (" acceptOnUrl:http://example.com → %s (expected: NO)\n " , badOk ? " YES" : " NO" );
684+ if (badErr) {
685+ printf (" Error: %s (expected: must be file URL)\n " , badErr.localizedDescription .UTF8String );
686+ }
687+ printf (" ✅ Non-file URL correctly rejected\n " );
688+
689+ waitForUser ();
690+ }
691+
544692static void printMenu (void ) {
545693 printHeader (@" NWAsyncSocket Objective-C Demo" );
546694 printf (
@@ -550,6 +698,7 @@ static void printMenu(void) {
550698 " 3. UTF-8 Safety — Multi-byte character boundary detection\n "
551699 " 4. NWReadRequest — Read-request queue types\n "
552700 " 5. GCDAsyncSocket — Connection usage pattern\n "
701+ " 6. GCDAsyncSocket — Server socket API\n "
553702 " a. Run all demos\n "
554703 " q. Quit\n\n "
555704 );
@@ -580,17 +729,20 @@ int main(int argc, const char * argv[]) {
580729 demoReadRequest ();
581730 } else if ([input isEqualToString: @" 5" ]) {
582731 demoGCDAsyncSocketUsage ();
732+ } else if ([input isEqualToString: @" 6" ]) {
733+ demoServerSocket ();
583734 } else if ([input isEqualToString: @" a" ]) {
584735 demoStreamBuffer ();
585736 demoSSEParser ();
586737 demoUTF8Safety ();
587738 demoReadRequest ();
588739 demoGCDAsyncSocketUsage ();
740+ demoServerSocket ();
589741 } else if ([input isEqualToString: @" q" ]) {
590742 printf (" \n Goodbye! 👋\n " );
591743 running = NO ;
592744 } else {
593- printf (" Invalid choice. Please enter 1-5 , a, or q.\n " );
745+ printf (" Invalid choice. Please enter 1-6 , a, or q.\n " );
594746 }
595747 }
596748 }
0 commit comments