移动开发网络

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

2018-01-03  本文已影响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的写入,代码也是一目了然,就不多做解释了.

上一篇下一篇

猜你喜欢

热点阅读