写入的部分会比读取的部代码简单很多.
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
CocoaAsyncSocket
提供的写入方法只有上面这一个.
按照已经知道的读取数据的套路也可以大概推出写入的大概过程.写入:建GCDAsyncWritePacket
的包加入writeQueue
,调用maybeDequeueWrite
.maybeDequeueWrite
进而再调用doWriteData
.
doWriteData
与doReadData
一样,在正式做读取之前,都会有一些规避判断(如:flag
表示现在正处于开启加密的阶段,则要调用ssl_continueSSLHandshake
),这些在doReadData
内已经说过了,在这就不再赘述了.
doWriteData
的写入也会被分成三个模块:正常写入
,CFStream For TLS写入
,SSL For TLS写入
.按照前面的套路我们也就知道SSL For TLS写入
是最复杂的.
三个模块进行之后的善后判断
也是一样的,如图:
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写入
不用writeSource
对bsd 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写入
用的都是writeSource
对bsd 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
的写入,代码也是一目了然,就不多做解释了.
网友评论