AsynSocket 源码解析之二
CocoaAsyncSocket 源码学习摘要:
GCDAsynSocket 读取socket数据(接收对方发送过来的数据)调用:read(socketFD, buffer, (size_t)bytesToRead)
。写入socket数据(向对方发送数据)调用 write(socketFD, buffer, (size_t)bytesToWrite)
。一般调用read/write 方法的时候需要用result变量存储返回结果如:ssize_t result = read(socketFD, buffer, (size_t)bytesToRead)
;想知道result都有可能是什么值?
我们先简单看下read函数(write函数基本类似)说明:size_t read(int fd,void *buf,size_t nbyte)
。read函数是负责从fd中读取内容。
- 返回值 > 0,表示读取成功。read返回实际读取到的字节数。
- 返回值 = 0,表示已经读取到文件的结束了(此时应该关闭socket或者仅关闭读流操作)。
- 返回值 < 0,表示是读取错误。如果错误是EINTR表示在写的时候出现了中断错误;如果是EPIPE表示网络连接出现了问题;如果是EWOULDBLOCK or EAGAIN(non-blocking IO的时候才会发生),继续尝试读取。
想了解更多相关错误码,请参考:
[EAGAIN、EWOULDBLOCK、EINTR与非阻塞 长连接]
[IT牛人博客聚合]
所以在GCDAsynSocket源代码中我们经常看到下面代码或者类似代码(我加入了注释):
int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD;//ipv4 or ipv6
//希望读取bytesToRead字节长度的数据到buffer缓存中。
ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);
LogVerbose(@"read from socket = %i", (int)result);
if (result < 0)//表示失败
{
if (errno == EWOULDBLOCK)//异步IO需要处理(GCDAsynSocket用的是异步IO)
//没有读到数据,下次可以继续读。设置waiting = YES
//这种情况下后面可能就会调用resumeReadSource,继续监听socket。
//当有数据可读的时候,继续尝试读取,具体原因后面会简单介绍。
waiting = YES;
else
//后面会执行断开连接逻辑
error = [self errnoErrorWithReason:@"Error in read() function"];
socketFDBytesAvailable = 0;
}else if (result == 0)
{
//读到socket末尾。遇到这种情况,后面会根据GCDSocket的kAllowHalfDuplexConnection设置做对应处理
//如果kAllowHalfDuplexConnection = NO,后面会关闭socket
//如果kAllowHalfDuplexConnection = Yes,后面会关闭读流操作(写流还可以进行),但不一定关闭socket。
socketEOF = YES;
socketFDBytesAvailable = 0;
}
else//表示soket数据读取成功
{
bytesRead = result;//实际从socket获取到的字节数据
if (socketFDBytesAvailable <= bytesRead)
socketFDBytesAvailable = 0;//提供的数据都被读完了。
else
socketFDBytesAvailable -= bytesRead;
if (socketFDBytesAvailable == 0)
{
//都是为了在一些复杂的情况下,防止sokect因为调用了suspendReadSource,导致不能继续工作。
waiting = YES;
}
}
下面简单解释下result 返回不同值的时候GCDAsynSocket会做如何处理
- result > 0,表示从socket读取了result字节数据。socketFDBytesAvailable用于实时记录socket可读数据字节数。socketFDBytesAvailable <= bytesRead表示socket提供的所有数据都已经被读取。当socket所有可提供数据被读取完之后,需要将waiting设置为Yes。一旦调用 waiting == YES,后面会调用resumeReadSource(代码没有贴出来)来继续监听socket提供可读数据字节数。防止因为在一些复杂的情况下,sokect因为调用了suspendReadSource,不能继续监听socket,当socket有数据可读的时候不会回调。
- result < 0,需要检查返回错误码。由于GCDAsynSocket采用的是异步IO(non-blocking IO)需要处理错误码为EWOULDBLOCK的情况。错误码为EWOULDBLOCK表示这次没有读到数据,后面会继续尝试调用read函数(waiting设置为Yes和上面解释一样)。如果是其他非EWOULDBLOCK错误码需要关闭socket。
- result = 0,表示读到socket末尾,遇到这种情况,后面会根据GCDSocket的kAllowHalfDuplexConnection设置做对应处理。如果kAllowHalfDuplexConnection = NO,后面会关闭socket;否则后面会关闭socket读流操作,然后检查socket的写流是否被关闭,如果写流也被关闭,那么就会关闭socket(读流写流操作都已经完成)。
下面简单解释下if (errno == EWOULDBLOCK)这句。
errno是系统库API。只有当一个库函数失败时,errno才会被设置。用来记录系统的最后一次错误代码。代码是一个int型的值,在errno.h中定义。errno详细解释
extern int * __error(void);
#define errno (*__error())
EWOULDBLOCK是一个什么鬼?
系统API 解释:
/* non-blocking and interrupt i/o */
#define EAGAIN 35 /* Resource temporarily unavailable */
#define EWOULDBLOCK EAGAIN /* Operation would block */
什么情况下回返回 EWOULDBLOCK?
在Linux环境下开发经常会碰到很多错误(设置errno),其中EAGAIN是其中比较常见的一个错误(比如用在非阻塞操作中)。从字面上来看,是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。例如,以 O_NONBLOCK的标志打开文件/socket/FIFO,如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返 回,read函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。