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,提示你的应用程序现在没有数据可读请稍后再试。

参考:
[EAGAIN、EWOULDBLOCK、EINTR与非阻塞 长连接]
[IT牛人博客聚合]

你可能感兴趣的:(AsynSocket 源码解析之二)