Skip to content

Commit 6f0c457

Browse files
Copilotdustturtle
andcommitted
Add ObjC server socket API demo (demo 6) with acceptOnPort, acceptOnInterface, acceptOnUrl tests
- Add didAcceptNewSocket: delegate to DemoSocketDelegate - Demo 6a: acceptOnPort: with port 0 (system-assigned) - Demo 6b: Double-accept error handling - Demo 6c: Client→Server connection, verify didAcceptNewSocket: - Demo 6d: Disconnect server, verify listener stops - Demo 6e: acceptOnInterface:port: on localhost - Demo 6f: acceptOnUrl: Unix Domain Socket - Demo 6g: acceptOnUrl: non-file URL error rejection - Update CI workflow for new demo option Co-authored-by: dustturtle <2305214+dustturtle@users.noreply.github.com>
1 parent cc061d1 commit 6f0c457

2 files changed

Lines changed: 155 additions & 3 deletions

File tree

.github/workflows/demo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
4444
- name: Run ObjC Demo (all demos)
4545
run: |
46-
printf 'a\n\n\n\n\n\nq\n' | ./ObjCDemo
46+
printf 'a\n\n\n\n\n\n\nq\n' | ./ObjCDemo
4747
4848
swift-tests:
4949
name: Swift Tests (macOS)

ObjC/ObjCDemo/main.m

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
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+
544692
static 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("\nGoodbye! 👋\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

Comments
 (0)