#ifndef HV_TCP_CLIENT_HPP_ #define HV_TCP_CLIENT_HPP_ #include "hsocket.h" #include "hssl.h" #include "hlog.h" #include "EventLoopThread.h" #include "Channel.h" namespace hv { template class TcpClientTmpl { public: typedef std::shared_ptr TSocketChannelPtr; TcpClientTmpl() { connect_timeout = HIO_DEFAULT_CONNECT_TIMEOUT; tls = false; tls_setting = NULL; reconn_setting = NULL; unpack_setting = NULL; } virtual ~TcpClientTmpl() { HV_FREE(tls_setting); HV_FREE(reconn_setting); HV_FREE(unpack_setting); } const EventLoopPtr& loop() { return loop_thread.loop(); } //NOTE: By default, not bind local port. If necessary, you can call system api bind() after createsocket(). //@retval >=0 connfd, <0 error int createsocket(int remote_port, const char* remote_host = "127.0.0.1") { memset(&remote_addr, 0, sizeof(remote_addr)); int ret = sockaddr_set_ipport(&remote_addr, remote_host, remote_port); if (ret != 0) { return -1; } this->remote_host = remote_host; this->remote_port = remote_port; return createsocket(&remote_addr.sa); } int createsocket(struct sockaddr* remote_addr) { int connfd = socket(remote_addr->sa_family, SOCK_STREAM, 0); // SOCKADDR_PRINT(remote_addr); if (connfd < 0) { perror("socket"); return -2; } hio_t* io = hio_get(loop_thread.hloop(), connfd); assert(io != NULL); hio_set_peeraddr(io, remote_addr, SOCKADDR_LEN(remote_addr)); channel.reset(new TSocketChannel(io)); return connfd; } // closesocket thread-safe void closesocket() { setReconnect(NULL); if (channel) { channel->close(true); } } int startConnect() { assert(channel != NULL); if (connect_timeout) { channel->setConnectTimeout(connect_timeout); } if (tls) { channel->enableSSL(); if (tls_setting) { channel->newSslCtx(tls_setting); } if (!is_ipaddr(remote_host.c_str())) { channel->setHostname(remote_host); } } channel->onconnect = [this]() { if (unpack_setting) { channel->setUnpack(unpack_setting); } channel->startRead(); if (onConnection) { onConnection(channel); } if (reconn_setting) { reconn_setting_reset(reconn_setting); } }; channel->onread = [this](Buffer* buf) { if (onMessage) { onMessage(channel, buf); } }; channel->onwrite = [this](Buffer* buf) { if (onWriteComplete) { onWriteComplete(channel, buf); } }; channel->onclose = [this]() { if (onConnection) { onConnection(channel); } // reconnect if (reconn_setting) { startReconnect(); } else { channel = NULL; // NOTE: channel should be destroyed, // so in this lambda function, no code should be added below. } }; return channel->startConnect(); } int startReconnect() { if (!reconn_setting) return -1; if (!reconn_setting_can_retry(reconn_setting)) return -2; uint32_t delay = reconn_setting_calc_delay(reconn_setting); loop_thread.loop()->setTimeout(delay, [this](TimerID timerID){ hlogi("reconnect... cnt=%d, delay=%d", reconn_setting->cur_retry_cnt, reconn_setting->cur_delay); if (createsocket(&remote_addr.sa) < 0) return; startConnect(); }); return 0; } void start(bool wait_threads_started = true) { loop_thread.start(wait_threads_started, std::bind(&TcpClientTmpl::startConnect, this)); } // stop thread-safe void stop(bool wait_threads_stopped = true) { setReconnect(NULL); loop_thread.stop(wait_threads_stopped); } bool isConnected() { if (channel == NULL) return false; return channel->isConnected(); } // send thread-safe int send(const void* data, int size) { if (!isConnected()) return -1; return channel->write(data, size); } int send(Buffer* buf) { return send(buf->data(), buf->size()); } int send(const std::string& str) { return send(str.data(), str.size()); } int withTLS(hssl_ctx_opt_t* opt = NULL) { tls = true; if (opt) { if (tls_setting == NULL) { HV_ALLOC_SIZEOF(tls_setting); } opt->endpoint = HSSL_CLIENT; *tls_setting = *opt; } return 0; } void setConnectTimeout(int ms) { connect_timeout = ms; } void setReconnect(reconn_setting_t* setting) { if (setting == NULL) { HV_FREE(reconn_setting); return; } if (reconn_setting == NULL) { HV_ALLOC_SIZEOF(reconn_setting); } *reconn_setting = *setting; } bool isReconnect() { return reconn_setting && reconn_setting->cur_retry_cnt > 0; } void setUnpack(unpack_setting_t* setting) { if (setting == NULL) { HV_FREE(unpack_setting); return; } if (unpack_setting == NULL) { HV_ALLOC_SIZEOF(unpack_setting); } *unpack_setting = *setting; } public: TSocketChannelPtr channel; std::string remote_host; int remote_port; sockaddr_u remote_addr; int connect_timeout; bool tls; hssl_ctx_opt_t* tls_setting; reconn_setting_t* reconn_setting; unpack_setting_t* unpack_setting; // Callback std::function onConnection; std::function onMessage; // NOTE: Use Channel::isWriteComplete in onWriteComplete callback to determine whether all data has been written. std::function onWriteComplete; private: EventLoopThread loop_thread; }; typedef TcpClientTmpl TcpClient; } #endif // HV_TCP_CLIENT_HPP_