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

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

作者: 破弓 | 来源:发表于2018-01-03 11:26 被阅读5次

    写入的部分会比读取的部代码简单很多.

    - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
    

    CocoaAsyncSocket提供的写入方法只有上面这一个.

    按照已经知道的读取数据的套路也可以大概推出写入的大概过程.写入:建GCDAsyncWritePacket的包加入writeQueue,调用maybeDequeueWrite.maybeDequeueWrite进而再调用doWriteData.

    doWriteDatadoReadData一样,在正式做读取之前,都会有一些规避判断(如:flag表示现在正处于开启加密的阶段,则要调用ssl_continueSSLHandshake),这些在doReadData内已经说过了,在这就不再赘述了.

    doWriteData的写入也会被分成三个模块:正常写入,CFStream For TLS写入,SSL For TLS写入.按照前面的套路我们也就知道SSL For TLS写入是最复杂的.

    三个模块进行之后的善后判断也是一样的,如图:

    CocoaAsyncSocket doWriteData.png

    1.正常写入

    int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
    
    const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
    
    NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
    
    if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
    {
        bytesToWrite = SIZE_MAX;
    }
    //zc read3:socket写
    ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite);
    LogVerbose(@"wrote to socket = %zd", result);
    
    // Check results
    if (result < 0)
    {
        if (errno == EWOULDBLOCK)
        {
            waiting = YES;
        }
        else
        {
            error = [self errnoErrorWithReason:@"Error in write() function"];
        }
    }
    else
    {
        bytesWritten = result;
    }
    

    单个包写入完毕的标准是:包所带的数据量等于写入的数据量.

    write()后:
    
    result < 0:
      (errno == EWOULDBLOCK)==>waiting = YES,需要等待doWriteData的再一次调用,进入善后判断;
      其他==>生成报错,进入善后判断.
    result >= 0:
      bytesWritten = result,进入善后判断.
    

    等待doWriteData的再一次调用也就是writeSource的监听再一次触发,开启监听代码如下:

    //取消可以接受数据的标记,开启对writeSource的监听(有空间可写立刻调用doWriteData)
    flags &= ~kSocketCanAcceptBytes;
    
    if (![self usingCFStreamForTLS])
    {
        [self resumeWriteSource];
    }
    

    2.CFStream For TLS写入

    const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
    
    NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
    
    if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
    {
        bytesToWrite = SIZE_MAX;
    }
    
    CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite);
    LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result);
    
    if (result < 0)
    {
        error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream);
    }
    else
    {
        bytesWritten = (size_t)result;
        
        // We always set waiting to true in this scenario.
        // CFStream may have altered our underlying socket to non-blocking.
        // Thus if we attempt to write without a callback, we may end up blocking our queue.
        waiting = YES;
    }
    
    CFWriteStreamWrite()后:
    
    result < 0:
      生成报错,进入善后判断.
    result >= 0:
      bytesWritten = result,waiting = YES,进入善后判断.
    

    CFStream For TLS写入不用writeSourcebsd socket进行监听,所以不用开启writeSource的监听.

    if (![self usingCFStreamForTLS])
    {
        [self resumeWriteSource];
    }
    

    可以看到CFStream写入的逻辑特别简单.这是因为CFStream内部会自己控制bsd socket传输数据,我们在外围只调用一次CFWriteStreamWrite()就够了,所以CFStream写入数据非常非常简单.

    3.SSL For TLS写入

    OSStatus result;
    
    BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0);
    BOOL hasNewDataToWrite = YES;
    
    if (hasCachedDataToWrite)
    {
        size_t processed = 0;
        
        result = SSLWrite(sslContext, NULL, 0, &processed);
        
        if (result == noErr)
        {
            bytesWritten = sslWriteCachedLength;
            sslWriteCachedLength = 0;
            
            if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten))
            {
                // We've written all data for the current write.
                hasNewDataToWrite = NO;
            }
        }
        else
        {
            if (result == errSSLWouldBlock)
            {
                waiting = YES;
            }
            else
            {
                error = [self sslError:result];
            }
            
            // Can't write any new data since we were unable to write the cached data.
            hasNewDataToWrite = NO;
        }
    }
    
    if (hasNewDataToWrite)
    {
        const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes]
        + currentWrite->bytesDone
        + bytesWritten;
        
        NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten;
        
        if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
        {
            bytesToWrite = SIZE_MAX;
        }
        
        size_t bytesRemaining = bytesToWrite;
        
        BOOL keepLooping = YES;
        while (keepLooping)
        {
            const size_t sslMaxBytesToWrite = 32768;
            size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite);
            size_t sslBytesWritten = 0;
            
            result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten);
            
            if (result == noErr)
            {
                buffer += sslBytesWritten;
                bytesWritten += sslBytesWritten;
                bytesRemaining -= sslBytesWritten;
                
                keepLooping = (bytesRemaining > 0);
            }
            else
            {
                if (result == errSSLWouldBlock)
                {
                    waiting = YES;
                    sslWriteCachedLength = sslBytesToWrite;//没写给对面,写入了sslContext内,需要记录sslWriteCachedLength
                }
                else
                {
                    error = [self sslError:result];
                }
                
                keepLooping = NO;
            }
            
        } // while (keepLooping)
        
    } // if (hasNewDataToWrite)
    

    SSL For TLS写入的代码很清晰的分成了两块:写入缓存的数据,写入包内的数据(写入都有缓存,厉害了吧!).我们先说写入包内的数据,再说写入缓存的数据.

    • 写入包内的数据

    单次调用SSLWrite()写入数据的大小是MIN(bytesRemaining, sslMaxBytesToWrite)(包内所剩数据量ssl能写的最大数据量小的那一个).

    SSLWrite()后:
    没报错:就循环调用SSLWrite()做写入,直到写完;
    拥塞报错:把sslBytesToWrite的计给sslWriteCachedLength,停止循环,进入善后判断,需要等待doWriteData的再一次调用;(这就是ssl缓存的由来)
    其他报错:停止循环,生成报错,进入善后判断.
    

    正常写入SSL For TLS写入用的都是writeSourcebsd socket的监听(上篇文章反复说,这就不赘述了).

    • 写入缓存的数据
    SSLWrite()后:
    没报错:写入的数据量等于sslWriteCachedLength,再进行写入包内的数据;
    拥塞报错:不再写入包内的数据,waiting = YES,需要等待doWriteData的再一次调用,进入善后判断;
    其他报错:不再写入包内的数据,生成报错,进入善后判断;
    

    SSLWrite()方法流程如下:

    SSLWrite()
      SSLWriteFunction()
        - (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength
    
    - (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength
    {
        if (!(flags & kSocketCanAcceptBytes))
        {
            // Unable to write.
            // 
            // Need to wait for writeSource to fire and notify us of
            // available space in the socket's internal write buffer.
            
            [self resumeWriteSource];
            
            *bufferLength = 0;
            return errSSLWouldBlock;
        }
        
        size_t bytesToWrite = *bufferLength;
        size_t bytesWritten = 0;
        
        BOOL done = NO;
        BOOL socketError = NO;
        
        int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
        
        ssize_t result = write(socketFD, buffer, bytesToWrite);
        
        if (result < 0)
        {
            if (errno != EWOULDBLOCK)
            {
                socketError = YES;
            }
            
            flags &= ~kSocketCanAcceptBytes;
        }
        else if (result == 0)
        {
            flags &= ~kSocketCanAcceptBytes;
        }
        else
        {
            bytesWritten = result;
            
            done = (bytesWritten == bytesToWrite);
        }
        
        *bufferLength = bytesWritten;
        
        if (done)
            return noErr;
        
        if (socketError)
            return errSSLClosedAbort;
        
        return errSSLWouldBlock;
    }
    

    SSL For TLS写入最终转换成bsd socket的写入,代码也是一目了然,就不多做解释了.

    相关文章

      网友评论

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

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