美文网首页移动开发网络
iOS 移动开发网络 part5.3:CocoaAsyncSoc

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

作者: 破弓 | 来源:发表于2018-01-03 11:25 被阅读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的创建与连接的过程就全部结束.

    相关文章

      网友评论

        本文标题:iOS 移动开发网络 part5.3:CocoaAsyncSoc

        本文链接:https://www.haomeiwen.com/subject/otkhnxtx.html