前言
上一章我们说了客户端的连接 connect
,对于同步客户端来说,连接已经建立成功;但是对于异步客户端来说,此时可能还在进行 DNS
的解析,onConnect
回调函数还未执行。
本节中,我们将继续说明客户端发送数据的流程,同时我们可以看到 TCP
异步客户端执行 onConnect
回调函数的过程。
send
入口
本入口函数逻辑非常简单,从 PHP
函数中获取数据 data
,然后调用 connect
函数。
static PHP_METHOD(swoole_client, send)
{
char *data;
zend_size_t data_len;
zend_long flags = 0;
#ifdef FAST_ZPP
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_STRING(data, data_len)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(flags)
ZEND_PARSE_PARAMETERS_END();
#else
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &data, &data_len, &flags) == FAILURE)
{
return;
}
#endif
swClient *cli = client_get_ptr(getThis() TSRMLS_CC);
//clear errno
SwooleG.error = 0;
int ret = cli->send(cli, data, data_len, flags);
if (ret < 0)
{
swoole_php_sys_error(E_WARNING, "failed to send(%d) %zd bytes.", cli->socket->fd, data_len);
zend_update_property_long(swoole_client_class_entry_ptr, getThis(), SW_STRL("errCode")-1, SwooleG.error TSRMLS_CC);
RETVAL_FALSE;
}
else
{
RETURN_LONG(ret);
}
}
swClient_tcp_send_sync
同步 TCP
客户端
对于同步客户端来说,发送数据是通过 swConnection_send
函数来进行阻塞式调用 send
,当返回的错误是 EAGAIN
的时候调用 swSocket_wait
等待 1s。
static int swClient_tcp_send_sync(swClient *cli, char *data, int length, int flags)
{
int written = 0;
int n;
assert(length > 0);
assert(data != NULL);
while (written < length)
{
n = swConnection_send(cli->socket, data, length - written, flags);
if (n < 0)
{
if (errno == EINTR)
{
continue;
}
else if (errno == EAGAIN)
{
swSocket_wait(cli->socket->fd, 1000, SW_EVENT_WRITE);
continue;
}
else
{
SwooleG.error = errno;
return SW_ERR;
}
}
written += n;
data += n;
}
return written;
}
swClient_tcp_send_async
异步 TCP
客户端
由于异步客户端已经设置为非阻塞,并且加入了 reactor
的监控,因此发送数据只需要 reactor->write
函数即可。当此时的套接字不可写的时候,会自动放入 out_buff
缓冲区中。
当 out_buffer
大于高水线时,会自动调用 onBufferFull
回调函数。
static int swClient_tcp_send_async(swClient *cli, char *data, int length, int flags)
{
int n = length;
if (cli->reactor->write(cli->reactor, cli->socket->fd, data, length) < 0)
{
if (SwooleG.error == SW_ERROR_OUTPUT_BUFFER_OVERFLOW)
{
n = -1;
cli->socket->high_watermark = 1;
}
else
{
return SW_ERR;
}
}
if (cli->onBufferFull && cli->socket->out_buffer && cli->socket->high_watermark == 0
&& cli->socket->out_buffer->length >= cli->buffer_high_watermark)
{
cli->socket->high_watermark = 1;
cli->onBufferFull(cli);
}
return n;
}
swClient_udp_send
UDP
客户端
对于 UDP
客户端来说,如果 Socket
缓存区塞满,并不会像 TCP
进行等待 reactor
可写状态,而是直接返回了结果。对于异步客户端来说,仅仅是非阻塞调用 sendto
函数。
static int swClient_udp_send(swClient *cli, char *data, int len, int flags)
{
int n;
n = sendto(cli->socket->fd, data, len, 0, (struct sockaddr *) &cli->server_addr.addr, cli->server_addr.len);
if (n < 0 || n < len)
{
return SW_ERR;
}
else
{
return n;
}
}
sendto
UDP
客户端
类似于 send
函数,sendto
函数专门针对 UDP
客户端,与 send
函数不同的是,sendto
函数在底层套接字缓冲区塞满的时候,会调用 swSocket_wait
进行阻塞等待。
static PHP_METHOD(swoole_client, sendto)
{
char* ip;
zend_size_t ip_len;
long port;
char *data;
zend_size_t len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sls", &ip, &ip_len, &port, &data, &len) == FAILURE)
{
return;
}
swClient *cli = (swClient *) swoole_get_object(getThis());
int ret;
if (cli->type == SW_SOCK_UDP)
{
ret = swSocket_udp_sendto(cli->socket->fd, ip, port, data, len);
}
else if (cli->type == SW_SOCK_UDP6)
{
ret = swSocket_udp_sendto6(cli->socket->fd, ip, port, data, len);
}
else
{
swoole_php_fatal_error(E_WARNING, "only supports SWOOLE_SOCK_UDP or SWOOLE_SOCK_UDP6.");
RETURN_FALSE;
}
SW_CHECK_RETURN(ret);
}
int swSocket_udp_sendto(int server_sock, char *dst_ip, int dst_port, char *data, uint32_t len)
{
struct sockaddr_in addr;
if (inet_aton(dst_ip, &addr.sin_addr) == 0)
{
swWarn("ip[%s] is invalid.", dst_ip);
return SW_ERR;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(dst_port);
return swSocket_sendto_blocking(server_sock, data, len, 0, (struct sockaddr *) &addr, sizeof(addr));
}
int swSocket_udp_sendto6(int server_sock, char *dst_ip, int dst_port, char *data, uint32_t len)
{
struct sockaddr_in6 addr;
bzero(&addr, sizeof(addr));
if (inet_pton(AF_INET6, dst_ip, &addr.sin6_addr) < 0)
{
swWarn("ip[%s] is invalid.", dst_ip);
return SW_ERR;
}
addr.sin6_port = (uint16_t) htons(dst_port);
addr.sin6_family = AF_INET6;
return swSocket_sendto_blocking(server_sock, data, len, 0, (struct sockaddr *) &addr, sizeof(addr));
}
int swSocket_sendto_blocking(int fd, void *__buf, size_t __n, int flag, struct sockaddr *__addr, socklen_t __addr_len)
{
int n = 0;
while (1)
{
n = sendto(fd, __buf, __n, flag, __addr, __addr_len);
if (n >= 0)
{
break;
}
else
{
if (errno == EINTR)
{
continue;
}
else if (swConnection_error(errno) == SW_WAIT)
{
swSocket_wait(fd, 1000, SW_EVENT_WRITE);
continue;
}
else
{
break;
}
}
}
return n;
}
swClient_onWrite
写就绪状态
当 reactor
监控到套接字进入了写就绪状态时,就会调用 swClient_onWrite
函数。
从上一章我们知道,异步客户端建立连接过程中 swoole
调用了 connect
函数,该返回 0,或者返回错误码 EINPROGRESS
都会对写就绪进行监控。无论哪种情况,swClient_onWrite
被调用就说明此时连接已经建立成功,三次握手已经完成,但是 cli->socket->active
还是 0。
如果 cli->socket->active
为 0,说明此时异步客户端虽然建立了连接,但是还没有调用 onConnect
回调函数,因此这时要调用 execute_onConnect
函数。如果使用了 SSL
隧道加密,还要进行 SSL
握手,并且设置 _socket->ssl_state = SW_SSL_STATE_WAIT_STREAM
。
当 active
为 1 的时候,就可以调用 swReactor_onWrite
来发送数据。
static int swClient_onWrite(swReactor *reactor, swEvent *event)
{
swClient *cli = event->socket->object;
swConnection *_socket = cli->socket;
if (cli->socket->active)
{
#ifdef SW_USE_OPENSSL
if (cli->open_ssl && _socket->ssl_state == SW_SSL_STATE_WAIT_STREAM)
{
if (swClient_ssl_handshake(cli) < 0)
{
goto connect_fail;
}
else if (_socket->ssl_state == SW_SSL_STATE_READY)
{
goto connect_success;
}
else
{
if (_socket->ssl_want_read)
{
cli->reactor->set(cli->reactor, event->fd, SW_FD_STREAM_CLIENT | SW_EVENT_READ);
}
return SW_OK;
}
}
#endif
if (swReactor_onWrite(cli->reactor, event) < 0)
{
return SW_ERR;
}
if (cli->onBufferEmpty && _socket->high_watermark && _socket->out_buffer->length <= cli->buffer_low_watermark)
{
_socket->high_watermark = 0;
cli->onBufferEmpty(cli);
}
return SW_OK;
}
socklen_t len = sizeof(SwooleG.error);
if (getsockopt(event->fd, SOL_SOCKET, SO_ERROR, &SwooleG.error, &len) < 0)
{
swWarn("getsockopt(%d) failed. Error: %s[%d]", event->fd, strerror(errno), errno);
return SW_ERR;
}
//success
if (SwooleG.error == 0)
{
//listen read event
cli->reactor->set(cli->reactor, event->fd, SW_FD_STREAM_CLIENT | SW_EVENT_READ);
//connected
_socket->active = 1;
#ifdef SW_USE_OPENSSL
if (cli->open_ssl)
{
if (swClient_enable_ssl_encrypt(cli) < 0)
{
goto connect_fail;
}
if (swClient_ssl_handshake(cli) < 0)
{
goto connect_fail;
}
else
{
_socket->ssl_state = SW_SSL_STATE_WAIT_STREAM;
}
return SW_OK;
}
connect_success:
#endif
if (cli->onConnect)
{
execute_onConnect(cli);
}
}
else
{
#ifdef SW_USE_OPENSSL
connect_fail:
#endif
_socket->active = 0;
cli->close(cli);
if (cli->onError)
{
cli->onError(cli);
}
}
return SW_OK;
}
static sw_inline void execute_onConnect(swClient *cli)
{
if (cli->timer)
{
swTimer_del(&SwooleG.timer, cli->timer);
cli->timer = NULL;
}
cli->onConnect(cli);
}
client_onConnect
static void client_onConnect(swClient *cli)
{
zval *zobject = (zval *) cli->object;
#ifdef SW_USE_OPENSSL
if (cli->ssl_wait_handshake)
{
client_execute_callback(zobject, SW_CLIENT_CB_onSSLReady);
}
else
#endif
if (!cli->redirect)
{
client_execute_callback(zobject, SW_CLIENT_CB_onConnect);
}
else
{
client_callback *cb = (client_callback *) swoole_get_property(zobject, 0);
if (!cb || !cb->onReceive)
{
swoole_php_fatal_error(E_ERROR, "has no 'onReceive' callback function.");
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。