TCP服务器实现-start函数启动过程-第四篇

版权声明:转载请注明来源 https://blog.csdn.net/u013702678/article/details/81211596

这里继续分析第三篇提到的线程池的消息队列,这个消息队列具体存储什么内容,后续看到内容再讨论,目前的实现存储的内容为void*,所以不影响我们使用。

//基于链表的方式管理队列,size为队列大小,maxlen为队列最大大小,flags用于标记队列类型,即队列是基于共享内存还是堆内存。
swChannel* swChannel_new(size_t size, int maxlen, int flags)
{
    assert(size >= maxlen);//判断size有效性
    int ret;
    void *mem;//指向总内存的指针

    //标记为使用共享内存
    if (flags & SW_CHAN_SHM)
    {
        //基于共享内存申请
        mem = sw_shm_malloc(size + sizeof(swChannel));
    }
    else
    {
        //基于堆内存
        mem = sw_malloc(size + sizeof(swChannel));
    }

    if (mem == NULL)//申请内存失败
    {
        swWarn("swChannel_create: malloc(%ld) failed.", size);
        return NULL;
    }

    swChannel *object = mem;//object指向申请内存的初始位置,其实从后面的分析可以看出来,object用于管理这块内存空间,也就是这块空间的头部
    mem += sizeof(swChannel);//mem指针指向申请内存的初始位置+swChannel的大小
    
    //object对象的初始化,这里总共初始化sizeof(swChannel)的大小
    bzero(object, sizeof(swChannel));

    object->size = size;//设置object的size属性
    object->mem = mem;//设置object的mem属性
    object->maxlen = maxlen;//设置object的maxlen属性
    object->flag = flags;//设置object的flags属性,具体属性的定义可以在下面看到一些

    //锁的初始化,这里的锁用来管理队列的出队入队时的线程同步
    if (flags & SW_CHAN_LOCK)
    {
        //初始化锁,这里锁的初始化和第三篇分析的一样,需要了解的可以在第三篇里面找到相关的分析
        if (swMutex_create(&object->lock, 1) < 0)
        {
            swWarn("mutex init failed.");
            return NULL;
        }
    }
    //通知管道初始化,关于后续管道的使用,具体列篇再分析
    if (flags & SW_CHAN_NOTIFY)
    {
        //管道的初始化,下面展开具体分析
        ret = swPipeNotify_auto(&object->notify_fd, 1, 1);
        if (ret < 0)
        {
            swWarn("notify_fd init failed.");
            return NULL;
        }
    }
    return object;
}

我们继续分析上篇结尾的管道初始化,这里的代码路径在E:\swoole-src-master\include\swoole.h中。

static inline int swPipeNotify_auto(swPipe *p, int blocking, int semaphore)
{
#ifdef HAVE_EVENTFD //判断是否支持eventfd
    return swPipeEventfd_create(p, blocking, semaphore, 0);
#else//在不支持eventfd时,则通过linux的pipe做管道
    return swPipeBase_create(p, blocking);
#endif
}

我们分析linux传统的管道的创建,基于eventfd的后续再分析。

//创建管道,其中swPipe指向管道的封装对象
int swPipeBase_create(swPipe *p, int blocking)
{
    int ret;
    //在堆上申请swPipeBase的空间,其中swPipeBase用于封装管道的读写描述符
    swPipeBase *object = sw_malloc(sizeof(swPipeBase));
    if (object == NULL)//申请失败
    {
        return -1;
    }
    //设置swPipe的blocking属性
    p->blocking = blocking;
    ret = pipe(object->pipes);//初始化linux管道
    if (ret < 0)//初始化失败
    {
        swWarn("pipe() failed. Error: %s[%d]", strerror(errno), errno);
        sw_free(object);
        return -1;
    }
    else
    {
        //管道的读写操作默认是阻塞的,这里设置为非阻塞的
        swSetNonBlock(object->pipes[0]);//设置读
        swSetNonBlock(object->pipes[1]);//设置写
        p->timeout = -1;//IO多路复用时的超时时间
        p->object = object;//设置swPipe的object属性,object属性设置为swPipeBase
        //下属的回调函数实现比较简单,我只分析swPipe的读的回调函数实现,这里管道的操作也是通过IO复用的方式实现的,这部分展开讨论下。
        p->read = swPipeBase_read;//设置swPipe的读回调函数
        p->write = swPipeBase_write;//设置swPipe的写回调函数
        p->getFd = swPipeBase_getFd;//设置swPipe的获取管道描述的回调函数
        p->close = swPipeBase_close;//设置swPipe的关闭的回调函数
    }
    return 0;
}

//管道读的实现,其中p指向管道,data为存放数据的地址,length为数据长度信息
static int swPipeBase_read(swPipe *p, void *data, int length)
{
    swPipeBase *object = p->object;
    //判断条件,是否设置了blocking和timeout值,blocking用来表示是否使用IO复用来控制读,timeout用来控制IO多路复用的超时时间
    if (p->blocking == 1 && p->timeout > 0)
    {
        //用IO多路复用来判断是否可读
        if (swSocket_wait(object->pipes[0], p->timeout * 1000, SW_EVENT_READ) < 0)
        {
            return SW_ERR;
        }
    }
    //调用linux API进行读的操作,如果不用IO多路复用来读,在管道没有数据可以读的时候,会阻塞主流程。
    return read(object->pipes[0], data, length);
}

接着我们分析swSocket_wait的实现,这个函数在代码E:\swoole-src-master\src\core\Socket.c里面。

//fd管道描述符,timeout_ms用来控制IO多路复用的超时时间,events表示事件集合
int swSocket_wait(int fd, int timeout_ms, int events)
{
    struct pollfd event;//这里采用poll来做IO多路复用器
    event.fd = fd;//设置需要监控的文件描述符
    event.events = 0;

    //设置多事件
    if (events & SW_EVENT_READ)
    {
        event.events |= POLLIN;
    }

    //设置写事件
    if (events & SW_EVENT_WRITE)
    {
        event.events |= POLLOUT;
    }

    while (1)
    {
        //linux poll函数的调用
        int ret = poll(&event, 1, timeout_ms);
        if (ret == 0)//连接关闭或者超时
        {
            return SW_ERR;
        }
        else if (ret < 0 && errno != EINTR)//其他错误
        {
            swWarn("poll() failed. Error: %s[%d]", strerror(errno), errno);
            return SW_ERR;
        }
        else//正常返回,表示有数据可以读
        {
            return SW_OK;
        }
    }
    return SW_OK;
}

猜你喜欢

转载自blog.csdn.net/u013702678/article/details/81211596