摘要:當此時的套接字不可寫的時候,會自動放入緩沖區中。當大于高水線時,會自動調用回調函數。寫就緒狀態當監控到套接字進入了寫就緒狀態時,就會調用函數。如果為,說明此時異步客戶端雖然建立了連接,但是還沒有調用回調函數,因此這時要調用函數。
前言
上一章我們說了客戶端的連接 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://m.specialneedsforspecialkids.com/yun/29498.html
摘要:新建可以看到,自動采用包長檢測的方法該函數主要功能是設置各種回調函數值得注意的是第三個參數代表是否異步。發送數據函數并不是直接發送數據,而是將數據存儲在,等著寫事件就緒之后調用發送數據。 swReactorThread_dispatch 發送數據 reactor 線程會通過 swReactorThread_dispatch 發送數據,當采用 stream 發送數據的時候,會調用 sw...
前言 作為一個網絡框架,最為核心的就是消息的接受與發送。高效的 reactor 模式一直是眾多網絡框架的首要選擇,本節主要講解 swoole 中的 reactor 模塊。 UNP 學習筆記——IO 復用 Reactor 的數據結構 Reactor 的數據結構比較復雜,首先 object 是具體 Reactor 對象的首地址,ptr 是擁有 Reactor 對象的類的指針, event_nu...
摘要:兩個函數是可選回調函數。附帶了一組可信任證書。應該注意的是,驗證失敗并不意味著連接不能使用。在對證書進行驗證時,有一些安全性檢查并沒有執行,包括證書的失效檢查和對證書中通用名的有效性驗證。 前言 swoole_client 提供了 tcp/udp socket 的客戶端的封裝代碼,使用時僅需 new swoole_client 即可。 swoole 的 socket client 對比...
摘要:對于服務端來說,緩存默認是不能使用的,可以通過調用函數來進行設置生效。在回調函數中,首先申請一個大數數據結構,然后將其設定為,該值表示公鑰指數,然后利用函數生成秘鑰。此時需要調用函數將新的連接與綁定。 前言 上一篇文章我們講了 OpenSSL 的原理,接下來,我們來說說如何利用 openssl 第三方庫進行開發,來為 tcp 層進行 SSL 隧道加密 OpenSSL 初始化 在 sw...
摘要:之后如果仍然有剩余未發送的數據,那么就如果已經沒有剩余數據了,繼續去取下一個數據包。拿到后,要用函數轉化為相應的類型即可得到包長值。 swPort_onRead_check_eof EOF 自動分包 我們前面說過,swPort_onRead_raw 是最簡單的向 worker 進程發送數據包的方法,swoole 會將從客戶端接受到的數據包,立刻發送給 worker 進程,用戶自己把...
閱讀 3001·2021-10-13 09:39
閱讀 2697·2021-09-27 13:34
閱讀 2036·2019-08-30 15:55
閱讀 3266·2019-08-30 15:43
閱讀 3641·2019-08-30 11:16
閱讀 1756·2019-08-26 18:28
閱讀 1293·2019-08-26 13:56
閱讀 918·2019-08-26 13:35