前面一篇文章分析了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;
}