swoole_client源码解析之connect过程(二)

前面一篇文章分析了swoole_client的connect过程,从代码可以看出,中间兼容了TCP和UDP的两种不同的协议类型,这个主要是在创建客户端socket时做的统一处理,这篇我们分析下connect过程中创建socket的过程,下面代码是在swoole_client.c文件中connect的流程中调用片段,做这个调用时,传入了swoole_client对象,host信息,host长度信息及端口信息。


    cli = php_swoole_client_new(getThis(), host, host_len, port);
    if (cli == NULL)
    {
        RETURN_FALSE;
    }

下面我们分析php_swoole_client_new的过程。

swClient* php_swoole_client_new(zval *object, char *host, int host_len, int port)
{
    zval *ztype;
    int async = 0;
    char conn_key[SW_LONG_CONNECTION_KEY_LEN];
    int conn_key_len = 0;
    uint64_t tmp_buf;
    int ret;

#if PHP_MAJOR_VERSION < 7
    TSRMLS_FETCH_FROM_CTX(sw_thread_ctx ? sw_thread_ctx : NULL);
#endif

    //读取swoole_client的属性值
    ztype = sw_zend_read_property(Z_OBJCE_P(object), object, SW_STRL("type")-1, 0 TSRMLS_CC);

    if (ztype == NULL || ZVAL_IS_NULL(ztype))//属性type值读取失败
    {
        swoole_php_fatal_error(E_ERROR, "failed to get swoole_client->type.");
        return NULL;
    }

    long type = Z_LVAL_P(ztype);

    //new flag, swoole-1.6.12+
    if (type & SW_FLAG_ASYNC)//判断是否为异步客户端,这里使用位图模式
    {
        async = 1;
    }

    swClient *cli;
    bzero(conn_key, SW_LONG_CONNECTION_KEY_LEN);
    zval *connection_id = sw_zend_read_property(Z_OBJCE_P(object), object, ZEND_STRL("id"), 1 TSRMLS_CC);//读取swoole_client的id属性值,这个id主要是在长连接时使用

    if (connection_id == NULL || ZVAL_IS_NULL(connection_id))//id为null,就是没设置或者设置为空
    {
        //程序自动生成
        conn_key_len = snprintf(conn_key, SW_LONG_CONNECTION_KEY_LEN, "%s:%d", host, port) + 1;
    }
    else//读取到正确的值
    {
        conn_key_len = snprintf(conn_key, SW_LONG_CONNECTION_KEY_LEN, "%s", Z_STRVAL_P(connection_id)) + 1;
    }

    //如果是TCP长连接
    if (type & SW_FLAG_KEEP)
    {
        //从缓存获取client对象,缓存的key为conn_key
        swClient *find = swHashMap_find(php_sw_long_connections, conn_key, conn_key_len);
        if (find == NULL)//未获取到,也就是不存在
        {
            cli = (swClient*) pemalloc(sizeof(swClient), 1);//申请空间
            if (swHashMap_add(php_sw_long_connections, conn_key, conn_key_len, cli) == FAILURE) //添加到缓存中,缓存的key为conn_key
            {
                swoole_php_fatal_error(E_WARNING, "failed to add swoole_client_create_socket to hashtable.");
            }
            goto create_socket;//goto语法,跳转到create_socket标签
        }
        else//从缓存中获取到swClient对象
        {
            cli = find;
            //尝试读取,检查连接有效性和是否已经关闭
            ret = recv(cli->socket->fd, &tmp_buf, sizeof(tmp_buf), MSG_DONTWAIT | MSG_PEEK);
            //读取异常
            if (ret == 0 || (ret < 0 && swConnection_error(errno) == SW_CLOSE))
            {
                cli->close(cli);//关闭连接
                goto create_socket;//goto语法,跳转到create_socket标签
            }

            cli->reuse_count ++;//增加复用次数
            zend_update_property_long(Z_OBJCE_P(object), object, ZEND_STRL("reuseCount"), cli->reuse_count TSRMLS_CC);//修改swoole_client对象的复用信息,php侧可以获取这个信息
        }
    }
    else//不是长连接,为短连接
    {
        cli = (swClient*) emalloc(sizeof(swClient));//申请空间

        create_socket:
        if (swClient_create(cli, php_swoole_socktype(type), async) < 0)//创建swClient对象,如果中间失败
        {
            swoole_php_fatal_error(E_WARNING, "swClient_create() failed. Error: %s [%d]", strerror(errno), errno);
            zend_update_property_long(Z_OBJCE_P(object), object, ZEND_STRL("errCode"), errno TSRMLS_CC);//更新swoole_client的errCode属性信息
            return NULL;
        }

        //don't forget free it
        cli->server_str = sw_strndup(conn_key, conn_key_len);
        cli->server_strlen = conn_key_len;
    }

    zend_update_property_long(Z_OBJCE_P(object), object, ZEND_STRL("sock"), cli->socket->fd TSRMLS_CC);//更新swoole_client的sock属性信息

    if (type & SW_FLAG_KEEP)//更新swClient属性值信息
    {
        cli->keep = 1;
    }

#ifdef SW_USE_OPENSSL
    if (type & SW_SOCK_SSL) //更新swClient属性值信息
    {
        cli->open_ssl = 1;
    }
#endif

    return cli;
}

接着上面分析,我们继续看看swClient的创建过程。

int swClient_create(swClient *cli, int type, int async)
{
    int _domain;
    int _type;

    bzero(cli, sizeof(swClient));
    switch (type)
    {
    case SW_SOCK_TCP:
        _domain = AF_INET;
        _type = SOCK_STREAM;
        break;
    case SW_SOCK_TCP6:
        _domain = AF_INET6;
        _type = SOCK_STREAM;
        break;
    case SW_SOCK_UNIX_STREAM:
        _domain = AF_UNIX;
        _type = SOCK_STREAM;
        break;
    case SW_SOCK_UDP:
        _domain = AF_INET;
        _type = SOCK_DGRAM;
        break;
    case SW_SOCK_UDP6:
        _domain = AF_INET6;
        _type = SOCK_DGRAM;
        break;
    case SW_SOCK_UNIX_DGRAM:
        _domain = AF_UNIX;
        _type = SOCK_DGRAM;
        break;
    default:
        return SW_ERR;
    }

#ifdef SOCK_CLOEXEC
    int sockfd = socket(_domain, _type | SOCK_CLOEXEC, 0);//创建socket,SOCK_CLOEXEC用于分离父子进程关系的标记,不知道为什么这里要用这个
#else
    int sockfd = socket(_domain, _type, 0);//创建socket
#endif
    if (sockfd < 0)//创建失败
    {
        swWarn("socket() failed. Error: %s[%d]", strerror(errno), errno);
        return SW_ERR;
    }

    if (async)//如果是异步的
    {
        if (swIsMaster() && SwooleTG.type == SW_THREAD_REACTOR)//如果是主进程,且是线程类的reactor,这里逻辑没看懂,感觉客户端不会走到这里
        {
            cli->reactor = SwooleTG.reactor;
        }
        else //不是主进程或者不是线程类的reactor
        {
            cli->reactor = SwooleG.main_reactor;
        }
        //设置reactor对应的socket属性信息,这里socket是对一个连接的抽象
        cli->socket = swReactor_get(cli->reactor, sockfd);
    }
    else//如果是同步的
    {
        cli->socket = sw_malloc(sizeof(swConnection));//申请swConnection空间,并且设置cli的socket属性信息,这里socket是对一个连接的抽象
    }

    cli->buffer_input_size = SW_CLIENT_BUFFER_SIZE;

    if (!cli->socket)//申请空间失败
    {
        swWarn("malloc(%d) failed.", (int ) sizeof(swConnection));
        close(sockfd);
        return SW_ERR;
    }

    bzero(cli->socket, sizeof(swConnection));//初始化空间
    cli->socket->fd = sockfd;//设置相应的属性
    cli->socket->object = cli;//设置相应的属性

    if (async)//如果是异步的
    {
        swSetNonBlock(cli->socket->fd);//设置连接对应描述符的异步属性
        if (!swReactor_handle_isset(cli->reactor, SW_FD_STREAM_CLIENT))//如果reactor不为TCP类型
        {
            cli->reactor->setHandle(cli->reactor, SW_FD_STREAM_CLIENT | SW_EVENT_READ, swClient_onStreamRead);//设置reactor相应的回调函数信息
            cli->reactor->setHandle(cli->reactor, SW_FD_DGRAM_CLIENT | SW_EVENT_READ, swClient_onDgramRead);//设置reactor相应的回调函数信息
            cli->reactor->setHandle(cli->reactor, SW_FD_STREAM_CLIENT | SW_EVENT_WRITE, swClient_onWrite);//设置reactor相应的回调函数信息
            cli->reactor->setHandle(cli->reactor, SW_FD_STREAM_CLIENT | SW_EVENT_ERROR, swClient_onError);//设置reactor相应的回调函数信息
        }
    }

    if (swSocket_is_stream(type))//如果是TCP类型
    {
        cli->recv = swClient_tcp_recv_no_buffer;
        if (async)//异步
        {
            cli->connect = swClient_tcp_connect_async;//设置swClient的函数实现,这里是解决客户端执行connect操作时,实际会执行的函数逻辑
            cli->send = swClient_tcp_send_async;//设置swClient的函数实现,这里是解决客户端执行send操作时,实际会执行的函数逻辑
            cli->sendfile = swClient_tcp_sendfile_async;//设置swClient的函数实现,这里是解决客户端执行sendfile操作时,实际会执行的函数逻辑
            cli->pipe = swClient_tcp_pipe;//设置swClient的函数实现,这里是解决客户端执行pipe操作时,实际会执行的函数逻辑
            cli->socket->dontwait = 1;
        }
        else//同步的
        {
            cli->connect = swClient_tcp_connect_sync;设置swClient的函数实现,这里是解决客户端执行connect操作时,实际会执行的函数逻辑
            cli->send = swClient_tcp_send_sync;设置swClient的函数实现,这里是解决客户端执行send操作时,实际会执行的函数逻辑
            cli->sendfile = swClient_tcp_sendfile_sync;//设置swClient的函数实现,这里是解决客户端执行sendfile操作时,实际会执行的函数逻辑
        }

        cli->reactor_fdtype = SW_FD_STREAM_CLIENT;//设置为TCP类型
    }
    else //UDP协议
    {
        cli->connect = swClient_udp_connect;
        cli->recv = swClient_udp_recv;
        cli->send = swClient_udp_send;
        cli->reactor_fdtype = SW_FD_DGRAM_CLIENT;//设置为UDP类型
    }

    cli->_sock_domain = _domain;//sock_domain属性信息设置
    cli->_sock_type = _type;//sock_type属性信息设置

    cli->close = swClient_close;//close函数实现
    cli->type = type;//type属性信息设置,
    cli->async = async;//同步异步属性信息设置

    cli->protocol.package_length_type = 'N';//协议类参数初始化
    cli->protocol.package_length_size = 4;//协议类参数初始化
    cli->protocol.package_body_offset = 0;//协议类参数初始化
    cli->protocol.package_max_length = SW_BUFFER_INPUT_SIZE;//协议类参数初始化
    cli->protocol.onPackage = swClient_onPackage;//协议类onPackage回调函数设置

    return SW_OK;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值