移动开发网络

iOS 移动开发网络 part5.3:CocoaAsyncSoc

2018-01-03  本文已影响7人  破弓
BOOL result = [_socket connectToHost:@"www.baidu.com" onPort:80 error:nil];
if (result) {
    NSLog(@"连接成功");
}else{
    NSLog(@"连接失败");
}
- (BOOL)connectToHost:(NSString *)inHost
onPort:(uint16_t)port
viaInterface:(NSString *)inInterface
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr
{
    NSString *host = [inHost copy];
    NSString *interface = [inInterface copy];
    
    __block BOOL result = NO;
    __block NSError *preConnectErr = nil;
    
    dispatch_block_t block = ^{ @autoreleasepool {
        
        if ([host length] == 0)
        {
            NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
            preConnectErr = [self badParamError:msg];
            
            return_from_block;
        }
        
        //zc read:确认有代理,有代理队列,否则...
        if (![self preConnectWithInterface:interface error:&preConnectErr])
        {
            return_from_block;
        }
        
        flags |= kSocketStarted;
        
        NSString *hostCpy = [host copy];
        
        int aStateIndex = stateIndex;
        __weak GCDAsyncSocket *weakSelf = self;
        
        dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
            
            //zc read:确认地址信息是IPv4还是IPv6
            NSError *lookupErr = nil;
            NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
            
            __strong GCDAsyncSocket *strongSelf = weakSelf;
            if (strongSelf == nil) return_from_block;
            
            if (lookupErr)
            {
                dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
                    
                    [strongSelf lookup:aStateIndex didFail:lookupErr];
                }});
            }
            else
            {
                NSData *address4 = nil;
                NSData *address6 = nil;
                
                for (NSData *address in addresses)
                {
                    if (!address4 && [[self class] isIPv4Address:address])
                    {
                        address4 = address;
                    }
                    else if (!address6 && [[self class] isIPv6Address:address])
                    {
                        address6 = address;
                    }
                }
                
                dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
                    //zc read:给的地址与配置是否符合(如:IPv4Enabled==NO,给的地址却是IPv4的就不OK了).符合就开始连接
                    [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
                }});
            }
            
#pragma clang diagnostic pop
        }});
        
        [self startConnectTimeout:timeout];
        
        result = YES;
    }};
    
    if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
        block();
        else
            dispatch_sync(socketQueue, block);
            
            if (errPtr) *errPtr = preConnectErr;
                return result;
}

确认host没问题;
确认有代理,有代理队列;

走DNS的lookup,

+ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr

DNS有可能返回不止一个ip地址;所以才会有接下来的参数既有address4又有address6的方法.

- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6;
    - (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr;//bsd socket创建
    - (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex;/bsd socket连接

IPv6的情况我们不多说,接着看- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr.

- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
{
    //zc read1:创建socket
    int socketFD = socket(family, SOCK_STREAM, 0);
    
    if (socketFD == SOCKET_NULL)
    {
        if (errPtr)
            *errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
        
        return socketFD;
    }
    
    //zc read:客户端不走bind
    if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
    {
        [self closeSocket:socketFD];
        
        return SOCKET_NULL;
    }
    
    // Prevent SIGPIPE signals
    
    int nosigpipe = 1;
    //zc read:设置与某个套接字关联的选项
    /*
     1.套接字描述符
     2.级别
     3.选项名
     4.读取指针
     5.读取长度
     
     SO_NOSIGPIPE是为了避免网络错误,而导致进程退出。用这个来避免系统发送signal
     */
    setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
    
    return socketFD;
}
- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex
{
    // If there already is a socket connected, we close socketFD and return
    if (self.isConnected)
    {
        [self closeSocket:socketFD];
        return;
    }
    
    // Start the connection process in a background queue
    
    __weak GCDAsyncSocket *weakSelf = self;
    
    dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globalConcurrentQueue, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
        
        //zc read2:socket连接
        int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
        
        __strong GCDAsyncSocket *strongSelf = weakSelf;
        if (strongSelf == nil) return_from_block;
        
        dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
            
            if (strongSelf.isConnected)
            {
                [strongSelf closeSocket:socketFD];
                return_from_block;
            }
            
            if (result == 0)
            {
                [self closeUnusedSocket:socketFD];
                
                [strongSelf didConnect:aStateIndex];
            }
            else
            {
                [strongSelf closeSocket:socketFD];
                
                // If there are no more sockets trying to connect, we inform the error to the delegate
                if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL)
                {
                    NSError *error = [strongSelf errnoErrorWithReason:@"Error in connect() function"];
                    [strongSelf didNotConnect:aStateIndex error:error];
                }
            }
        }});
        
#pragma clang diagnostic pop
    });
    
    LogVerbose(@"Connecting...");
}

到此为止,bsd socket级别的int socket(int, int, int);int connect(int, const struct sockaddr *, socklen_t);都被调用了.

int socketFD = socket(family, SOCK_STREAM, 0);
int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);

连接成功后调用:

- (void)didConnect:(int)aStateIndex
{
    LogTrace();
    
    NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
    
    
    if (aStateIndex != stateIndex)
    {
        LogInfo(@"Ignoring didConnect, already disconnected");
        
        // The connect operation has been cancelled.
        // That is, socket was disconnected, or connection has already timed out.
        return;
    }
    
    flags |= kConnected;
    
    [self endConnectTimeout];
    
    #if TARGET_OS_IPHONE
    // The endConnectTimeout method executed above incremented the stateIndex.
    aStateIndex = stateIndex;
    #endif
    
    // Setup read/write streams (as workaround for specific shortcomings in the iOS platform)
    // 
    // Note:
    // There may be configuration options that must be set by the delegate before opening the streams.
    // The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream.
    // 
    // Thus we wait until after the socket:didConnectToHost:port: delegate method has completed.
    // This gives the delegate time to properly configure the streams if needed.
    
    dispatch_block_t SetupStreamsPart1 = ^{
        #if TARGET_OS_IPHONE
        
        if (![self createReadAndWriteStream])
        {
            [self closeWithError:[self otherError:@"Error creating CFStreams"]];
            return;
        }
        
        if (![self registerForStreamCallbacksIncludingReadWrite:NO])
        {
            [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
            return;
        }
        
        #endif
    };
    dispatch_block_t SetupStreamsPart2 = ^{
        #if TARGET_OS_IPHONE
        
        if (aStateIndex != stateIndex)
        {
            // The socket has been disconnected.
            return;
        }
        
        if (![self addStreamsToRunLoop])
        {
            [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
            return;
        }
        
        if (![self openStreams])
        {
            [self closeWithError:[self otherError:@"Error creating CFStreams"]];
            return;
        }
        
        #endif
    };
    
    // Notify delegate
    
    NSString *host = [self connectedHost];
    uint16_t port = [self connectedPort];
    NSURL *url = [self connectedUrl];
    
    __strong id theDelegate = delegate;

    if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
    {
        SetupStreamsPart1();
        
        //zc read:delegateQueue与socketQueue的分工可见一斑
        dispatch_async(delegateQueue, ^{ @autoreleasepool {
            
            [theDelegate socket:self didConnectToHost:host port:port];
            
            dispatch_async(socketQueue, ^{ @autoreleasepool {
                
                SetupStreamsPart2();
            }});
        }});
    }
    else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)])
    {
        SetupStreamsPart1();
        
        dispatch_async(delegateQueue, ^{ @autoreleasepool {
            
            [theDelegate socket:self didConnectToUrl:url];
            
            dispatch_async(socketQueue, ^{ @autoreleasepool {
                
                SetupStreamsPart2();
            }});
        }});
    }
    else
    {
        SetupStreamsPart1();
        SetupStreamsPart2();
    }
        
    // Get the connected socket
    
    int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
    
    // Enable non-blocking IO on the socket
    //zc read:使socket支持非阻塞IO
    int result = fcntl(socketFD, F_SETFL, O_NONBLOCK);
    if (result == -1)
    {
        NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)";
        [self closeWithError:[self otherError:errMsg]];
        
        return;
    }
    
    // Setup our read/write sources
    
    [self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD];
    
    // Dequeue any pending read/write requests
    
    [self maybeDequeueRead];
    [self maybeDequeueWrite];
}
1.根据传入的是host+port还是url进行不同的回调;
2.开启[dispatch_source readSource对socketFD有数据可读的监听]和[dispatch_source writeSource对socketFD有空间可写的监听];
3.maybeDequeueRead与maybeDequeueWrite看是否有数据可以读或者可以写;
4.用socketFD创建readStream和writeStream,两个Stream加入RunLoop,持续监听socket.

<<持续监听socket>>
如果你对AFN2.0由一定了解,对开一个专属线程来供网络收发数据的做法一定不陌生.这里也需要持续监听socket,用的也是类似的方法.
创建一个新的线程,线程加入timer(一直存在),保证线程一直存在.这样线程对应的RunLoop也会一直存在;
用socketFD创建readStream和writeStream,两个Stream加入RunLoop;
这样就完成了对socket的持续监听.

<<readStream与writeStream>>
未用到CFStream for TLS的情况下,readStreamwriteStream作用只是为了串起socketFD和RunLoop,没有其他作用.如果你对CFStream有一定了解,CFStream是有方法告诉外围内部封装好的bsd socket有空间可写或者有数据可读的,但是未用到CFStream for TLS的情况下CFStream的代理方法是没有用的,bsd socket有空间可写或者有数据可读都是依靠dispatch_source告诉外围.方法- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite很好的说明了这一点.CFStream for TLS的内容我们会在后说.

- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite
{
    LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO"));
    
    NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
    NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
    
    streamContext.version = 0;
    streamContext.info = (__bridge void *)(self);
    streamContext.retain = nil;
    streamContext.release = nil;
    streamContext.copyDescription = nil;
    
    //zc read:set Client readStream
    CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
    if (includeReadWrite)
        readStreamEvents |= kCFStreamEventHasBytesAvailable;
    
    if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext))
    {
        return NO;
    }
    
    //zc read:set Client writeStream
    CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
    if (includeReadWrite)
        writeStreamEvents |= kCFStreamEventCanAcceptBytes;
    
    if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext))
    {
        return NO;
    }
    
    return YES;
}

到此为止,CocoaAsyncSocket的创建与连接的过程就全部结束.

上一篇下一篇

猜你喜欢

热点阅读