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

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

经过前面两篇的分析,已经清楚了swoole_server对象的创建和在该对象上注册回调函数的实现,接下来我们讨论swoole_server的start过程,对应的PHP代码,为下图最后一行代码。

//创建Server对象,监听 127.0.0.1:9501端口
$serv = new swoole_server("127.0.0.1", 9501); 

//监听连接进入事件
$serv->on('connect', function ($serv, $fd) {  
    echo "Client: Connect.\n";
});

//监听数据接收事件
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
    $serv->send($fd, "Server: ".$data);
});

//监听连接关闭事件
$serv->on('close', function ($serv, $fd) {
    echo "Client: Close.\n";
});

//启动服务器
$serv->start(); 

我们开始分析swoole里面的实现,结合前面两篇文章的分析,可以很快的定位到PHP的start方法在swoole里面的实现,也即在swoole_server.c里面,大家可以按函数名称做下搜索,代码如下图所示:

PHP_METHOD(swoole_server, start)
{
    zval *zobject = getThis();//获取swoole_server对象信息
    int ret;
    //从内部缓存里面获取swoole里面swoole_server的对应的对象swServer
    swServer *serv = swoole_get_object(getThis());
    if (serv->gs->start > 0)//服务已经启动,则中断程序,也就是同一个进程中,只能启动一个swoole_server对象
    {
        swoole_php_fatal_error(E_WARNING, "server is running. unable to execute swoole_server->start.");
        RETURN_FALSE;
    }

    //serv对象初始化回调函数,基于2篇中的回调函数缓存,serv也初始化类似的回调函数,比如swoole_server注册了名称为onStart的回调函数,则这里会初始化serv对象的onStart的回调函数,只是serv用到的回调函数实现都是已经定义好的,比如serv->onStart = php_swoole_onStart,其中php_swoole_onStart的定义是固定的,后面具体调用到回调函数时再分析。
    php_swoole_register_callback(serv);
    
    //php_sw_server_callbacks缓存了swoole_server对象上的回调函数,具体实现可以看第2篇中的分析
    if (php_sw_server_callbacks[SW_SERVER_CB_onReceive] == NULL && php_sw_server_callbacks[SW_SERVER_CB_onPacket] == NULL)//SW_SERVER_CB_onReceive和SW_SERVER_CB_onPacket分别表示接受到TCP包的数据和接收到UDP数据包,这里是判断这两个回调事件不能都为空,否则,中断程序。
    {
        swoole_php_fatal_error(E_ERROR, "require onReceive/onPacket callback");
        RETURN_FALSE;
    }

    //serv对象的onReceive回调函数初始化为php_swoole_onReceive,这个回调函数的实现后面再分析。
    serv->onReceive = php_swoole_onReceive;
    
    //swoole_server启动前的流程,单独分析,这里大家注意下这个函数的输入,其中zobject为PHP侧的swoole_server对象,serv为swoole内部的serv对象。
    php_swoole_server_before_start(serv, zobject TSRMLS_CC);

    //serv的启动,单独分析,如果启动失败,则流程结束。
    ret = swServer_start(serv);
    if (ret < 0)
    {
        swoole_php_fatal_error(E_ERROR, "failed to start server. Error: %s", sw_error);
        RETURN_LONG(ret);
    }
    RETURN_TRUE;
}
void php_swoole_server_before_start(swServer *serv, zval *zobject TSRMLS_DC)
{
    //创建swoole server对象,如果创建失败,则打印错误日志,返回。
    if (swServer_create(serv) < 0)
    {
        swoole_php_fatal_error(E_ERROR, "failed to create the server. Error: %s", sw_error);
        return;
    }
    
    //打印普通日志,记录server对应的host,port,mode,type等信息。
    swTrace("Create swoole_server host=%s, port=%d, mode=%d, type=%d", serv->listen_list->host, (int) serv->listen_list->port, serv->factory_mode, (int) serv->listen_list->type);
    
    //增加zobject对象的引用计数
    sw_zval_add_ref(&zobject);
    //复制zobject值到serv_ptr2中,这里类似文件,增加了zobject的引用计数1次
    serv->ptr2 = sw_zval_dup(zobject);

    //协程相关的逻辑,如果有协程,则创建协程的缓存区,并且注册onClose回调函数,关于协程后续再分析
#ifdef SW_COROUTINE
    coro_init(TSRMLS_C);
    if (serv->send_yield)
    {
        send_coroutine_map = swHashMap_new(SW_HASHMAP_INIT_BUCKET_N, NULL);
        if (send_coroutine_map == NULL)
        {
            swoole_php_fatal_error(E_ERROR, "failed to create send_coroutine_map. Error: %s", sw_error);
        }
        if (serv->onClose == NULL)
        {
            serv->onClose = php_swoole_onClose;
        }
    }
#endif

    //PHP扩展的API,用于设置zobject的master_pid属性值,这个值表示master进程的PID,通过getpid获取到的就是当前进程的进程ID,也就是说目前的流程是在master进程中,因zobject指向的是PHP侧的swoole_server对象,也就是这里更新了swoole_server的master_pid属性,这个属性在流程执行到这里的时候,获取到的值就是正确的值。
    zend_update_property_long(swoole_server_class_entry_ptr, zobject, ZEND_STRL("master_pid"), getpid() TSRMLS_CC);
    //读取zobject的setting属性
    zval *zsetting = sw_zend_read_property(swoole_server_class_entry_ptr, zobject, ZEND_STRL("setting"), 1 TSRMLS_CC);
    if (zsetting == NULL || ZVAL_IS_NULL(zsetting))//如果setting属性值为空
    {
        SW_ALLOC_INIT_ZVAL(zsetting);//申请空间,这一步和下一步也是PHP扩展中创建数组的标准做法
        array_init(zsetting);//数组的初始化
#ifdef HT_ALLOW_COW_VIOLATION
        HT_ALLOW_COW_VIOLATION(Z_ARRVAL_P(zsetting));
#endif
        //设置zobject的setting属性值,这里只是分配了空间而已,内容还是空的
        zend_update_property(swoole_server_class_entry_ptr, zobject, ZEND_STRL("setting"), zsetting TSRMLS_CC);
    }

    //因setting是个数组,这里判断setting是否存在worker_num属性,下面的几块类似,不在单独分析。
    if (!zend_hash_str_exists(Z_ARRVAL_P(zsetting), ZEND_STRL("worker_num")))
    {
        //创建数组元素,数组元素为setting,key为worker_num,值为serv带的worker_num
        add_assoc_long(zsetting, "worker_num", serv->worker_num);
    }
    if (!zend_hash_str_exists(Z_ARRVAL_P(zsetting), ZEND_STRL("task_worker_num")))
    {
        add_assoc_long(zsetting, "task_worker_num", serv->task_worker_num);
    }
    if (!zend_hash_str_exists(Z_ARRVAL_P(zsetting), ZEND_STRL("buffer_output_size")))
    {
        add_assoc_long(zsetting, "buffer_output_size", serv->buffer_output_size);
    }
    if (!zend_hash_str_exists(Z_ARRVAL_P(zsetting), ZEND_STRL("max_connection")))
    {
        add_assoc_long(zsetting, "max_connection", serv->max_connection);
    }

#ifdef HAVE_PTRACE //判断系统是否有ptrace系统调用
    //如果serv有慢日志路径和慢日志追踪标志或者有task进程,则注册serv的钩子函数,钩子函数里面实现相应的日志追踪,serv上的钩子函数是通过单链表的方式去管理,在这里追踪日志时,记录的是worker进程的一些执行情况,细节不再展开。
    if (serv->request_slowlog_file && (serv->trace_event_worker || serv->task_worker_num > 0))
    {
        serv->manager_alarm = serv->request_slowlog_timeout;
        if (swServer_add_hook(serv, SW_SERVER_HOOK_MANAGER_TIMER, php_swoole_trace_check, 1) < 0)
        {
            swoole_php_fatal_error(E_ERROR, "Unable to add server hook.");
            return;
        }
    }
#endif
    //下面的逻辑用来判断多端口对象server_port_list.zobjects[i]是否有setting属性值,如果没有的话,复制swoole_server对象的setting设置,这里更多的是PHP扩展的操作,关于多端口对象,后续再看。
    int i;
    zval *retval = NULL;
    zval *port_object;
    zval *port_setting;

    for (i = 1; i < server_port_list.num; i++)
    {
        port_object = server_port_list.zobjects[i];
        port_setting = sw_zend_read_property(swoole_server_port_class_entry_ptr, port_object, ZEND_STRL("setting"), 1 TSRMLS_CC);
        //use swoole_server->setting
        if (port_setting == NULL || ZVAL_IS_NULL(port_setting))
        {
            sw_zval_add_ref(&port_setting);
            sw_zval_add_ref(&port_object);
            sw_zend_call_method_with_1_params(&port_object, swoole_server_port_class_entry_ptr, NULL, "set", &retval, zsetting);
            if (retval != NULL)
            {
                sw_zval_ptr_dtor(&retval);
            }
        }
    }
}

猜你喜欢

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