摘要:當此時的套接字不可寫的時候,會自動放入緩沖區(qū)中。當大于高水線時,會自動調(diào)用回調(diào)函數(shù)。寫就緒狀態(tài)當監(jiān)控到套接字進入了寫就緒狀態(tài)時,就會調(diào)用函數(shù)。如果為,說明此時異步客戶端雖然建立了連接,但是還沒有調(diào)用回調(diào)函數(shù),因此這時要調(diào)用函數(shù)。
前言
上一章我們說了客戶端的連接 connect,對于同步客戶端來說,連接已經(jīng)建立成功;但是對于異步客戶端來說,此時可能還在進行 DNS 的解析,onConnect 回調(diào)函數(shù)還未執(zhí)行。
本節(jié)中,我們將繼續(xù)說明客戶端發(fā)送數(shù)據(jù)的流程,同時我們可以看到 TCP 異步客戶端執(zhí)行 onConnect 回調(diào)函數(shù)的過程。
send 入口本入口函數(shù)邏輯非常簡單,從 PHP 函數(shù)中獲取數(shù)據(jù) data,然后調(diào)用 connect 函數(shù)。
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 客戶端
對于同步客戶端來說,發(fā)送數(shù)據(jù)是通過 swConnection_send 函數(shù)來進行阻塞式調(diào)用 send,當返回的錯誤是 EAGAIN 的時候調(diào)用 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 客戶端
由于異步客戶端已經(jīng)設置為非阻塞,并且加入了 reactor 的監(jiān)控,因此發(fā)送數(shù)據(jù)只需要 reactor->write 函數(shù)即可。當此時的套接字不可寫的時候,會自動放入 out_buff 緩沖區(qū)中。
當 out_buffer 大于高水線時,會自動調(diào)用 onBufferFull 回調(diào)函數(shù)。
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 緩存區(qū)塞滿,并不會像 TCP 進行等待 reactor 可寫狀態(tài),而是直接返回了結(jié)果。對于異步客戶端來說,僅僅是非阻塞調(diào)用 sendto 函數(shù)。
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 函數(shù),sendto 函數(shù)專門針對 UDP 客戶端,與 send 函數(shù)不同的是,sendto 函數(shù)在底層套接字緩沖區(qū)塞滿的時候,會調(diào)用 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 寫就緒狀態(tài)
當 reactor 監(jiān)控到套接字進入了寫就緒狀態(tài)時,就會調(diào)用 swClient_onWrite 函數(shù)。
從上一章我們知道,異步客戶端建立連接過程中 swoole 調(diào)用了 connect 函數(shù),該返回 0,或者返回錯誤碼 EINPROGRESS 都會對寫就緒進行監(jiān)控。無論哪種情況,swClient_onWrite 被調(diào)用就說明此時連接已經(jīng)建立成功,三次握手已經(jīng)完成,但是 cli->socket->active 還是 0。
如果 cli->socket->active 為 0,說明此時異步客戶端雖然建立了連接,但是還沒有調(diào)用 onConnect 回調(diào)函數(shù),因此這時要調(diào)用 execute_onConnect 函數(shù)。如果使用了 SSL 隧道加密,還要進行 SSL 握手,并且設置 _socket->ssl_state = SW_SSL_STATE_WAIT_STREAM。
當 active 為 1 的時候,就可以調(diào)用 swReactor_onWrite 來發(fā)送數(shù)據(jù)。
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."); } } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/29498.html
摘要:新建可以看到,自動采用包長檢測的方法該函數(shù)主要功能是設置各種回調(diào)函數(shù)值得注意的是第三個參數(shù)代表是否異步。發(fā)送數(shù)據(jù)函數(shù)并不是直接發(fā)送數(shù)據(jù),而是將數(shù)據(jù)存儲在,等著寫事件就緒之后調(diào)用發(fā)送數(shù)據(jù)。 swReactorThread_dispatch 發(fā)送數(shù)據(jù) reactor 線程會通過 swReactorThread_dispatch 發(fā)送數(shù)據(jù),當采用 stream 發(fā)送數(shù)據(jù)的時候,會調(diào)用 sw...
前言 作為一個網(wǎng)絡框架,最為核心的就是消息的接受與發(fā)送。高效的 reactor 模式一直是眾多網(wǎng)絡框架的首要選擇,本節(jié)主要講解 swoole 中的 reactor 模塊。 UNP 學習筆記——IO 復用 Reactor 的數(shù)據(jù)結(jié)構(gòu) Reactor 的數(shù)據(jù)結(jié)構(gòu)比較復雜,首先 object 是具體 Reactor 對象的首地址,ptr 是擁有 Reactor 對象的類的指針, event_nu...
摘要:兩個函數(shù)是可選回調(diào)函數(shù)。附帶了一組可信任證書。應該注意的是,驗證失敗并不意味著連接不能使用。在對證書進行驗證時,有一些安全性檢查并沒有執(zhí)行,包括證書的失效檢查和對證書中通用名的有效性驗證。 前言 swoole_client 提供了 tcp/udp socket 的客戶端的封裝代碼,使用時僅需 new swoole_client 即可。 swoole 的 socket client 對比...
摘要:對于服務端來說,緩存默認是不能使用的,可以通過調(diào)用函數(shù)來進行設置生效。在回調(diào)函數(shù)中,首先申請一個大數(shù)數(shù)據(jù)結(jié)構(gòu),然后將其設定為,該值表示公鑰指數(shù),然后利用函數(shù)生成秘鑰。此時需要調(diào)用函數(shù)將新的連接與綁定。 前言 上一篇文章我們講了 OpenSSL 的原理,接下來,我們來說說如何利用 openssl 第三方庫進行開發(fā),來為 tcp 層進行 SSL 隧道加密 OpenSSL 初始化 在 sw...
摘要:之后如果仍然有剩余未發(fā)送的數(shù)據(jù),那么就如果已經(jīng)沒有剩余數(shù)據(jù)了,繼續(xù)去取下一個數(shù)據(jù)包。拿到后,要用函數(shù)轉(zhuǎn)化為相應的類型即可得到包長值。 swPort_onRead_check_eof EOF 自動分包 我們前面說過,swPort_onRead_raw 是最簡單的向 worker 進程發(fā)送數(shù)據(jù)包的方法,swoole 會將從客戶端接受到的數(shù)據(jù)包,立刻發(fā)送給 worker 進程,用戶自己把...
閱讀 3057·2021-10-13 09:39
閱讀 2720·2021-09-27 13:34
閱讀 2086·2019-08-30 15:55
閱讀 3281·2019-08-30 15:43
閱讀 3675·2019-08-30 11:16
閱讀 1787·2019-08-26 18:28
閱讀 1327·2019-08-26 13:56
閱讀 948·2019-08-26 13:35