我想在使用UNIX域套接字的Cocoa应用程序中实现IPC,我没有经验.我找到了Apple的CFLocalServer示例项目,但它是用C语言编写的,看起来相当复杂(是的,我已经阅读了大部分内容).
CFLocalServer中演示的技术是否仍然是最先进的,或者有没有办法在Objective-C中使用Cocoa/Foundation实现UNIX域套接字?
我正在玩NSSocketPort
s和NSFileHandle
s(它提供了一个健康的抽象量,这对于这个项目非常有用)并且在Mike Bean的Cocoa网络中发现了一些非常相关的代码,但是还没能完成所有工作.
有人这样做过吗?
UNIX Domain Sockets是一个难以捉摸的难题.对于任何尚未这样做的人,并且有兴趣那么去吧.之后会有成就感.然而,即使有来自Beej和本网站的信息,甚至来自Apple的信息也有很多断开连接.我在这里介绍一个已启用ARC的Cocoa的确凿样本.我在等待Sidnicious和他的样品,但从未见过任何东西,所以我决定自己解决这个问题.
我这里有一个带有三个接口的头文件和.m实现文件.超类接口,然后是继承的服务器和客户端接口.我做了一些有限的测试,似乎工作得很好.但是,一直在寻找改进,请告诉我...
头文件:
typedef enum _CommSocketServerStatus { CommSocketServerStatusUnknown = 0, CommSocketServerStatusRunning = 1, CommSocketServerStatusStopped = 2, CommSocketServerStatusStarting = 3, CommSocketServerStatusStopping = 4 } CommSocketServerStatus; typedef enum _CommSocketClientStatus { CommSocketClientStatusUnknown = 0, CommSocketClientStatusLinked = 1, CommSocketClientStatusDisconnected = 2, CommSocketClientStatusLinking = 3, CommSocketClientStatusDisconnecting = 4 } CommSocketClientStatus; @class CommSocketServer, CommSocketClient; @protocol CommSocketServerDelegate@optional - (void) handleSocketServerStopped:(CommSocketServer *)server; - (void) handleSocketServerMsgURL:(NSURL *)aURL fromClient:(CommSocketClient *)client; - (void) handleSocketServerMsgString:(NSString *)aString fromClient:(CommSocketClient *)client; - (void) handleSocketServerMsgNumber:(NSNumber *)aNumber fromClient:(CommSocketClient *)client; - (void) handleSocketServerMsgArray:(NSArray *)aArray fromClient:(CommSocketClient *)client; - (void) handleSocketServerMsgDict:(NSDictionary *)aDict fromClient:(CommSocketClient *)client; @end @protocol CommSocketClientDelegate @optional - (void) handleSocketClientDisconnect:(CommSocketClient *)client; - (void) handleSocketClientMsgURL:(NSURL *)aURL client:(CommSocketClient *)client; - (void) handleSocketClientMsgString:(NSString *)aString client:(CommSocketClient *)client; - (void) handleSocketClientMsgNumber:(NSNumber *)aNumber client:(CommSocketClient *)client; - (void) handleSocketClientMsgArray:(NSArray *)aArray client:(CommSocketClient *)client; - (void) handleSocketClientMsgDict:(NSDictionary *)aDict client:(CommSocketClient *)client; @end @interface CommSocket : NSObject @property (readonly, nonatomic, getter=isSockRefValid) BOOL sockRefValid; @property (readonly, nonatomic, getter=isSockConnected) BOOL sockConnected; @property (readonly, nonatomic) CFSocketRef sockRef; @property (readonly, strong, nonatomic) NSURL *sockURL; @property (readonly, strong, nonatomic) NSData *sockAddress; @property (readonly, strong, nonatomic) NSString *sockLastError; @end @interface CommSocketServer : CommSocket { id delegate; } @property (readwrite, strong, nonatomic) id delegate; @property (readonly, strong, nonatomic) NSSet *sockClients; @property (readonly, nonatomic) CommSocketServerStatus sockStatus; @property (readonly, nonatomic) BOOL startServer; @property (readonly, nonatomic) BOOL stopServer; - (id) initWithSocketURL:(NSURL *)socketURL; + (id) initAndStartServer:(NSURL *)socketURL; - (void) addConnectedClient:(CFSocketNativeHandle)handle; - (void) messageClientsURL:(NSURL *)aURL; - (void) messageClientsString:(NSString *)aString; - (void) messageClientsNumber:(NSNumber *)aNumber; - (void) messageClientsArray:(NSArray *)aArray; - (void) messageClientsDict:(NSDictionary *)aDict; @end @interface CommSocketClient : CommSocket { id delegate; } @property (readwrite, strong, nonatomic) id delegate; @property (readonly, nonatomic) CommSocketClientStatus sockStatus; @property (readonly, nonatomic) CFRunLoopSourceRef sockRLSourceRef; @property (readonly, nonatomic) BOOL startClient; @property (readonly, nonatomic) BOOL stopClient; - (id) initWithSocketURL:(NSURL *)socketURL; - (id) initWithSocket:(CFSocketNativeHandle)handle; + (id) initAndStartClient:(NSURL *)socketURL; + (id) initWithSocket:(CFSocketNativeHandle)handle; - (void) messageReceived:(NSData *)data; - (BOOL) messageURL:(NSURL *)aURL; - (BOOL) messageString:(NSString *)aString; - (BOOL) messageNumber:(NSNumber *)aNumber; - (BOOL) messageArray:(NSArray *)aArray; - (BOOL) messageDict:(NSDictionary *)aDict; @end
实施档案:(我将分三部分介绍)
第一节(超类)
#import "CommSocket.h" #import#import #pragma mark Socket Superclass: @interface CommSocket () @property (readwrite, nonatomic) CFSocketRef sockRef; @property (readwrite, strong, nonatomic) NSURL *sockURL; @end @implementation CommSocket @synthesize sockConnected; @synthesize sockRef, sockURL; - (BOOL) isSockRefValid { if ( self.sockRef == nil ) return NO; return (BOOL)CFSocketIsValid( self.sockRef ); } - (NSData *) sockAddress { struct sockaddr_un address; address.sun_family = AF_UNIX; strcpy( address.sun_path, [[self.sockURL path] fileSystemRepresentation] ); address.sun_len = SUN_LEN( &address ); return [NSData dataWithBytes:&address length:sizeof(struct sockaddr_un)]; } - (NSString *) sockLastError { return [NSString stringWithFormat:@"%s (%d)", strerror( errno ), errno ]; } @end
第二节(服务器)
注意:服务器会为连接到自身的客户端重用客户端代码.面向对象编程,一定要喜欢它!
#pragma mark - Socket: Server #pragma mark - @interface CommSocketServer () @property (readonly, nonatomic) BOOL startServerCleanup; @property (readwrite, nonatomic) CommSocketServerStatus sockStatus; @property (readwrite, strong, nonatomic) NSSet *sockClients; static void SocketServerCallback (CFSocketRef sock, CFSocketCallBackType type, CFDataRef address, const void *data, void *info); @end #pragma mark - Server Implementation: @implementation CommSocketServer @synthesize delegate; @synthesize sockStatus; @synthesize sockClients; #pragma mark - Helper Methods: - (BOOL) socketServerCreate { if ( self.sockRef != nil ) return NO; CFSocketNativeHandle sock = socket( AF_UNIX, SOCK_STREAM, 0 ); CFSocketContext context = { 0, (__bridge void *)self, nil, nil, nil }; CFSocketRef refSock = CFSocketCreateWithNative( nil, sock, kCFSocketAcceptCallBack, SocketServerCallback, &context ); if ( refSock == nil ) return NO; int opt = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt)); setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&opt, sizeof(opt)); self.sockRef = refSock; CFRelease( refSock ); return YES; } - (BOOL) socketServerBind { if ( self.sockRef == nil ) return NO; unlink( [[self.sockURL path] fileSystemRepresentation] ); if ( CFSocketSetAddress(self.sockRef, (__bridge CFDataRef)self.sockAddress) != kCFSocketSuccess ) return NO; return YES; } #pragma mark - Connected Clients: - (void) disconnectClients { for ( CommSocketClient *client in self.sockClients ) [client stopClient]; self.sockClients = [NSSet set]; } - (void) disconnectClient:(CommSocketClient *)client { @synchronized( self ) { NSMutableSet *clients = [NSMutableSet setWithSet:self.sockClients]; if ( [clients containsObject:client] ) { if ( client.isSockRefValid ) [client stopClient]; [clients removeObject:client]; self.sockClients = clients; } } } - (void) addConnectedClient:(CFSocketNativeHandle)handle { @synchronized( self ) { CommSocketClient *client = [CommSocketClient initWithSocket:handle]; client.delegate = self; NSMutableSet *clients = [NSMutableSet setWithSet:self.sockClients]; if ( client.isSockConnected ) { [clients addObject:client]; self.sockClients = clients; } } } #pragma mark - Connected Client Protocols: - (void) handleSocketClientDisconnect:(CommSocketClient *)client { [self disconnectClient:client]; } - (void) handleSocketClientMsgURL:(NSURL *)aURL client:(CommSocketClient *)client { if ( [self.delegate respondsToSelector:@selector(handleSocketServerMsgURL:server:fromClient:)] ) [self.delegate handleSocketServerMsgURL:aURL fromClient:client]; } - (void) handleSocketClientMsgString:(NSString *)aString client:(CommSocketClient *)client { if ( [self.delegate respondsToSelector:@selector(handleSocketServerMsgString:fromClient:)] ) [self.delegate handleSocketServerMsgString:aString fromClient:client]; } - (void) handleSocketClientMsgNumber:(NSNumber *)aNumber client:(CommSocketClient *)client { if ( [self.delegate respondsToSelector:@selector(handleSocketServerMsgNumber:fromClient:)] ) [self.delegate handleSocketClientMsgNumber:aNumber client:client]; } - (void) handleSocketClientMsgArray:(NSArray *)aArray client:(CommSocketClient *)client { if ( [self.delegate respondsToSelector:@selector(handleSocketServerMsgArray:fromClient:)] ) [self.delegate handleSocketServerMsgArray:aArray fromClient:client]; } - (void) handleSocketClientMsgDict:(NSDictionary *)aDict client:(CommSocketClient *)client { if ( [self.delegate respondsToSelector:@selector(handleSocketServerMsgDict:fromClient:)] ) [self.delegate handleSocketServerMsgDict:aDict fromClient:client]; } #pragma mark - Connected Client Messaging: - (void) messageClientsURL:(NSURL *)aURL { for ( CommSocketClient *client in self.sockClients) [client messageURL:aURL]; } - (void) messageClientsString:(NSString *)aString { for ( CommSocketClient *client in self.sockClients) [client messageString:aString]; } - (void) messageClientsNumber:(NSNumber *)aNumber { for ( CommSocketClient *client in self.sockClients) [client messageNumber:aNumber]; } - (void) messageClientsArray:(NSArray *)aArray { for ( CommSocketClient *client in self.sockClients) [client messageArray:aArray]; } - (void) messageClientsDict:(NSDictionary *)aDict { for ( CommSocketClient *client in self.sockClients) [client messageDict:aDict]; } #pragma mark - Start / Stop Server: - (BOOL) startServerCleanup { [self stopServer]; return NO; } - (BOOL) startServer { if ( self.sockStatus == CommSocketServerStatusRunning ) return YES; self.sockStatus = CommSocketServerStatusStarting; if ( ![self socketServerCreate] ) return self.startServerCleanup; if ( ![self socketServerBind] ) return self.startServerCleanup; CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource( kCFAllocatorDefault, self.sockRef, 0 ); CFRunLoopAddSource( CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes ); CFRelease( sourceRef ); self.sockStatus = CommSocketServerStatusRunning; return YES; } - (BOOL) stopServer { self.sockStatus = CommSocketServerStatusStopping; [self disconnectClients]; if ( self.sockRef != nil ) { CFSocketInvalidate(self.sockRef); self.sockRef = nil; } unlink( [[self.sockURL path] fileSystemRepresentation] ); if ( [self.delegate respondsToSelector:@selector(handleSocketServerStopped:)] ) [self.delegate handleSocketServerStopped:self]; self.sockStatus = CommSocketServerStatusStopped; return YES; } #pragma mark - Server Validation: - (BOOL) isSockConnected { if ( self.sockStatus == CommSocketServerStatusRunning ) return self.isSockRefValid; return NO; } #pragma mark - Initialization: + (id) initAndStartServer:(NSURL *)socketURL { CommSocketServer *server = [[CommSocketServer alloc] initWithSocketURL:socketURL]; [server startServer]; return server; } - (id) initWithSocketURL:(NSURL *)socketURL { if ( (self = [super init]) ) { self.sockURL = socketURL; self.sockStatus = CommSocketServerStatusStopped; self.sockClients = [NSSet set]; } return self; } - (void) dealloc { [self stopServer]; } #pragma mark - Server Callback: static void SocketServerCallback (CFSocketRef sock, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { CommSocketServer *server = (__bridge CommSocketServer *)info; if ( kCFSocketAcceptCallBack == type ) { CFSocketNativeHandle handle = *(CFSocketNativeHandle *)data; [server addConnectedClient:handle]; } } @end
第三节(客户)
#pragma mark - Socket: Client #pragma mark - @interface CommSocketClient () @property (readonly, nonatomic) BOOL startClientCleanup; @property (readwrite, nonatomic) CommSocketClientStatus sockStatus; @property (readwrite, nonatomic) CFRunLoopSourceRef sockRLSourceRef; static void SocketClientCallback (CFSocketRef sock, CFSocketCallBackType type, CFDataRef address, const void *data, void *info); @end #pragma mark - Client Implementation: @implementation CommSocketClient static NSTimeInterval const kCommSocketClientTimeout = 5.0; @synthesize delegate; @synthesize sockStatus; @synthesize sockRLSourceRef; #pragma mark - Helper Methods: - (BOOL) socketClientCreate:(CFSocketNativeHandle)sock { if ( self.sockRef != nil ) return NO; CFSocketContext context = { 0, (__bridge void *)self, nil, nil, nil }; CFSocketCallBackType types = kCFSocketDataCallBack; CFSocketRef refSock = CFSocketCreateWithNative( nil, sock, types, SocketClientCallback, &context ); if ( refSock == nil ) return NO; int opt = 1; setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&opt, sizeof(opt)); self.sockRef = refSock; CFRelease( refSock ); return YES; } - (BOOL) socketClientBind { if ( self.sockRef == nil ) return NO; if ( CFSocketConnectToAddress(self.sockRef, (__bridge CFDataRef)self.sockAddress, (CFTimeInterval)kCommSocketClientTimeout) != kCFSocketSuccess ) return NO; return YES; } #pragma mark - Client Messaging: - (void) messageReceived:(NSData *)data { id msg = [NSKeyedUnarchiver unarchiveObjectWithData:data]; if ( [msg isKindOfClass:[NSURL class]] ) { if ( [self.delegate respondsToSelector:@selector(handleSocketClientMsgURL:client:)] ) [self.delegate handleSocketClientMsgURL:(NSURL *)msg client:self]; } else if ( [msg isKindOfClass:[NSString class]] ) { if ( [self.delegate respondsToSelector:@selector(handleSocketClientMsgString:client:)] ) [self.delegate handleSocketClientMsgString:(NSString *)msg client:self]; } else if ( [msg isKindOfClass:[NSNumber class]] ) { if ( [self.delegate respondsToSelector:@selector(handleSocketClientMsgNumber:client:)] ) [self.delegate handleSocketClientMsgNumber:(NSNumber *)msg client:self]; } else if ( [msg isKindOfClass:[NSArray class]] ) { if ( [self.delegate respondsToSelector:@selector(handleSocketClientMsgArray:client:)] ) [self.delegate handleSocketClientMsgArray:(NSArray *)msg client:self]; } else if ( [msg isKindOfClass:[NSDictionary class]] ) { if ( [self.delegate respondsToSelector:@selector(handleSocketClientMsgDict:client:)] ) [self.delegate handleSocketClientMsgDict:(NSDictionary *)msg client:self]; } } - (BOOL) messageData:(NSData *)data { if ( self.isSockConnected ) { if ( kCFSocketSuccess == CFSocketSendData(self.sockRef, nil, (__bridge CFDataRef)data, kCommSocketClientTimeout) ) return YES; } return NO; } - (BOOL) messageURL:(NSURL *)aURL { return [self messageData:[NSKeyedArchiver archivedDataWithRootObject:aURL]]; } - (BOOL) messageString:(NSString *)aString { return [self messageData:[NSKeyedArchiver archivedDataWithRootObject:aString]]; } - (BOOL) messageNumber:(NSNumber *)aNumber { return [self messageData:[NSKeyedArchiver archivedDataWithRootObject:aNumber]]; } - (BOOL) messageArray:(NSArray *)aArray { return [self messageData:[NSKeyedArchiver archivedDataWithRootObject:aArray]]; } - (BOOL) messageDict:(NSDictionary *)aDict { return [self messageData:[NSKeyedArchiver archivedDataWithRootObject:aDict]]; } #pragma mark - Start / Stop Client: - (BOOL) startClientCleanup { [self stopClient]; return NO; } - (BOOL) startClient { if ( self.sockStatus == CommSocketClientStatusLinked ) return YES; self.sockStatus = CommSocketClientStatusLinking; CFSocketNativeHandle sock = socket( AF_UNIX, SOCK_STREAM, 0 ); if ( ![self socketClientCreate:sock] ) return self.startClientCleanup; if ( ![self socketClientBind] ) return self.startClientCleanup; CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource( kCFAllocatorDefault, self.sockRef, 0 ); CFRunLoopAddSource( CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes ); self.sockRLSourceRef = sourceRef; CFRelease( sourceRef ); self.sockStatus = CommSocketClientStatusLinked; return YES; } - (BOOL) stopClient { self.sockStatus = CommSocketClientStatusDisconnecting; if ( self.sockRef != nil ) { if ( self.sockRLSourceRef != nil ) { CFRunLoopSourceInvalidate( self.sockRLSourceRef ); self.sockRLSourceRef = nil; } CFSocketInvalidate(self.sockRef); self.sockRef = nil; } if ( [self.delegate respondsToSelector:@selector(handleSocketClientDisconnect:)] ) [self.delegate handleSocketClientDisconnect:self]; self.sockStatus = CommSocketClientStatusDisconnected; return YES; } #pragma mark - Client Validation: - (BOOL) isSockConnected { if ( self.sockStatus == CommSocketClientStatusLinked ) return self.isSockRefValid; return NO; } #pragma mark - Initialization: + (id) initAndStartClient:(NSURL *)socketURL { CommSocketClient *client = [[CommSocketClient alloc] initWithSocketURL:socketURL]; [client startClient]; return client; } + (id) initWithSocket:(CFSocketNativeHandle)handle { CommSocketClient *client = [[CommSocketClient alloc] initWithSocket:handle]; return client; } - (id) initWithSocketURL:(NSURL *)socketURL { if ( (self = [super init]) ) { self.sockURL = socketURL; self.sockStatus = CommSocketClientStatusDisconnected; } return self; } - (id) initWithSocket:(CFSocketNativeHandle)handle { if ( (self = [super init]) ) { self.sockStatus = CommSocketClientStatusLinking; if ( ![self socketClientCreate:handle] ) [self startClientCleanup]; else { CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource( kCFAllocatorDefault, self.sockRef, 0 ); CFRunLoopAddSource( CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes ); self.sockRLSourceRef = sourceRef; CFRelease( sourceRef ); self.sockStatus = CommSocketClientStatusLinked; } } return self; } - (void) dealloc { [self stopClient]; } #pragma mark - Client Callback: static void SocketClientCallback (CFSocketRef sock, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { CommSocketClient *client = (__bridge CommSocketClient *)info; if ( kCFSocketDataCallBack == type ) { NSData *objData = (__bridge NSData *)data; if ( [objData length] == 0 ) [client stopClient]; else [client messageReceived:objData]; } } @end
好吧,就是这样,它应该来自不同的过程.
只需使用它来创建一个服务器,其中包含放置套接字文件的路径.
(id)initAndStartServer:(NSURL*)socketURL;
同样,使用它来创建客户端连接
(id)initAndStartClient:(NSURL*)socketURL;
其余的应该是我所包含的委托方法的直截了当.最后,保持url路径较小(没有添加任何实际检查)并且您可以在单独的NSOperationQueue中实例化这些(尽管未经测试).
希望这可以帮助那些人作为一个完整的工作样本.阿尔文
最后,我确实使用了UNIX域套接字,并且它们运行良好.我已经启动为我的服务器设置套接字(但是编写代码,成功编写代码,自己创建),并让客户端连接到它.
我学到的东西:
您可以在NSFileHandle中包装连接的两端
您需要connect()
在客户端使用以创建与套接字的连接
您应该使客户端和服务器都忽略SIGPIPE
如果使用零长度数据回调,则意味着套接字另一端的东西已断开连接(即退出服务器/客户端).在这种情况下,确保关闭并释放插槽的末端 - 不要再尝试从中读取数据,或者只是获得另一个零长度读取回调(永远和永远)
您负责分隔和组合通过套接字发送的消息(一端发送的单个消息可能在另一端发送到多个消息,或者可能合并多个消息)
我很乐意分享或发布代码,只是没有时间清理它以供公众使用.如果有人有兴趣,请告诉我.