UNIX域套接字和Cocoa

8
我想在一个Cocoa应用程序中使用UNIX域套接字来实现IPC,但我对此没有经验。我找到了苹果公司的CFLocalServer示例项目,但它是用C语言编写的,看起来相当复杂(是的,我已经阅读了大部分内容)。
CFLocalServer演示的技术仍然是最先进的技术吗?还是有一种使用Cocoa/Foundation中的Objective-C来实现UNIX域套接字的方法?
我正在尝试使用NSSocketPortNSFileHandle(这些提供了很好的抽象,非常适合这个项目),并在Mike Bean's Networking in Cocoa中找到了一些相关的代码,但还没有能够使它们全部工作。
有人以前做过这个吗?
4个回答

10

UNIX Domain Sockets是一个难以掌握的领域。对于那些没有涉足过它但有兴趣的人,可以试一试。完成后会有一种成就感。然而,即使有Beej和这个网站,甚至是苹果提供的信息,仍然存在许多不连贯的地方。我在这里提供了一个启用了ARC的Cocoa定稿样本。

我在这里提供了一个具有三个接口的头文件和.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 <NSObject>
@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 <NSObject>
@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 <CommSocketClientDelegate> { id <CommSocketServerDelegate> 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 <CommSocketClientDelegate> 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 <sys/un.h>
#import <sys/socket.h> 

#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中实例化这些(未经测试)。

希望这个完整的工作示例能够帮助到某个人。 阿尔文


太棒了!!!非常感谢您提供这样干净的代码。这正是官方文档中缺少的。 - codingFriend1
如果路径超过103个字符,那么您的-sockAddress方法似乎会在结构体的末尾之外进行strcpy()。我猜修复这个问题的方法是创建一个大的NSMutableData,复制路径,然后将数据调整为SUN_LEN(&address)。对于这种情况,sun_len字段应该是什么并不清楚;它受到unsigned char大小的限制。 - Michael Tsai
strcpy 在这里明显是不安全的。看来套接字路径必须要比 104 个字节短,这就是限制之一。从这段代码中学到了很多,感谢分享。 - pronebird
能否提供一个演示,展示如何使用它会很有帮助。比如一个小的“hello world”应用程序。看起来这里所有的委托都指向代码的其他部分。如果我错过了,请原谅,但我没有看到您在哪里插入需要使用Unix套接字的应用程序,无论是作为客户端还是服务器端。例如,一个回显客户端和服务器,我该如何注册一个什么也不做,只是将消息回显到“/tmp/dummyfile1”的服务器,然后创建一个连接到“/tmp/dummyfile1”的客户端,并说:“你好,我的朋友!”如果我完全误解了这个问题,请原谅! - jpswain

6
最终,我使用了UNIX域套接字,并且它们运行得非常好。我已经设置了launchd服务器的套接字(但是成功编写了代码来自己创建它),并让客户端连接到它。
我学到的东西:
- 您可以将连接的两端都包装在NSFileHandle中 - 在客户端上确实需要使用connect()来创建与套接字的连接 - 您应该使客户端和服务器都忽略SIGPIPE - 如果您收到零长度数据的回调,则意味着套接字另一端的东西已断开连接(即服务器/客户端已退出)。在这种情况下,请确保关闭和释放套接字的端口 - 不要再次尝试从中读取数据,或者只是获得另一个零长度的读取回调(永远) - 您负责分隔和组装通过套接字发送的消息(单个消息在一端发送可能会被拆分成多个,在另一端合并多个消息)
我很乐意分享或发布代码,只是还没有时间整理出适合公开使用的代码。如果有人感兴趣,请告诉我。

@ParagBafna 可能需要几天/几周才能把它整理好。我会尽快完成的。 - s4y
如果另一端挂断,您应该能够重新连接套接字。我认为您不需要从头开始重新创建它。明天会进行测试。 - pronebird

0

谢谢你的回答!据我所知,命名管道有两个端口——而对于这个项目,我需要一个服务器对多个客户端的关系。 - s4y

-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接