diff --git a/src/api/iptux-core/CoreThread.h b/src/api/iptux-core/CoreThread.h index 886096a2c..94c43ec22 100644 --- a/src/api/iptux-core/CoreThread.h +++ b/src/api/iptux-core/CoreThread.h @@ -1,6 +1,8 @@ #ifndef IPTUX_CORETHREAD_H #define IPTUX_CORETHREAD_H +#include + #include "iptux-core/Models.h" #include #include @@ -25,6 +27,7 @@ enum CoreThreadErr { CORE_THREAD_ERR_UDP_BIND_FAILED, CORE_THREAD_ERR_UDP_THREAD_START_FAILED, CORE_THREAD_ERR_TCP_BIND_FAILED, + CORE_THREAD_ERR_TCP_THREAD_START_FAILED, }; const char* coreThreadErrToStr(enum CoreThreadErr err); @@ -37,10 +40,17 @@ class CoreThread { virtual bool start() noexcept; virtual void stop(); + // For testing only: ignore TCP bind failures + void setIgnoreTcpBindFailed(bool ignore); + // For testing only: get the count of active TCP handler threads + size_t getTcpHandlerThreadCount() const; + CPPalInfo getMe() const; PPalInfo getMe(); + int getTcpSock() const; int getUdpSock() const; + GSocket* getUdpSocket() const; uint16_t port() const; std::shared_ptr getProgramData(); @@ -101,7 +111,7 @@ class CoreThread { PFileInfo GetPrivateFileById(uint32_t id); PFileInfo GetPrivateFileByPacketN(uint32_t packageNum, uint32_t filectime); - void sendFeatureData(PPalInfo pal); + bool sendFeatureData(PPalInfo pal) noexcept; void emitSomeoneExit(const PalKey& palKey); void emitNewPalOnline(PPalInfo palInfo); void emitNewPalOnline(const PalKey& palKey); @@ -196,8 +206,6 @@ class CoreThread { protected: std::shared_ptr programData; std::shared_ptr config; - int tcpSock; - int udpSock; mutable std::mutex mutex; // 锁 private: @@ -209,9 +217,6 @@ class CoreThread { private: bool bind_iptux_port() noexcept; - private: - static void RecvTcpData(CoreThread* pcthrd); - public: struct Impl; diff --git a/src/iptux-core/CoreThread.cpp b/src/iptux-core/CoreThread.cpp index e751a1468..418138509 100644 --- a/src/iptux-core/CoreThread.cpp +++ b/src/iptux-core/CoreThread.cpp @@ -2,22 +2,22 @@ #include "iptux-core/CoreThread.h" #include "Const.h" +#include "gio/gio.h" #include #include #include #include #include #include +#include #include #include #include #include #include -#include -#include +#include -#include "iptux-core/Exception.h" #include "iptux-core/internal/Command.h" #include "iptux-core/internal/RecvFileData.h" #include "iptux-core/internal/SendFile.h" @@ -50,6 +50,8 @@ const char* coreThreadErrToStr(enum CoreThreadErr err) { return _("UDP thread start failed"); case CORE_THREAD_ERR_TCP_BIND_FAILED: return _("TCP bind failed"); + case CORE_THREAD_ERR_TCP_THREAD_START_FAILED: + return _("TCP thread start failed"); default: return _("Unknown error"); } @@ -60,8 +62,7 @@ const char* coreThreadErrToStr(enum CoreThreadErr err) { struct UdpThread; struct UdpThreadOps { bool (*on_new_msg)(UdpThread* udpThread, - in_addr ipv4, - uint16_t port, + GSocketAddress* peer, const char* msg, size_t size); void (*on_init_failed)(UdpThread* udpThread); @@ -76,15 +77,14 @@ enum UdpThreadState { struct UdpThread { // config + GSocket* socket = nullptr; void* data = nullptr; - int fd = -1; const UdpThreadOps* ops = nullptr; // runtime atomic state = UDP_THREAD_STATE_INIT; GMainLoop* loop = nullptr; GThread* thread = nullptr; GMainContext* ctx = nullptr; - GIOChannel* channel = nullptr; guint id = 0; UdpThread() = default; @@ -106,10 +106,6 @@ UdpThread::~UdpThread() { g_main_context_unref(ctx); ctx = nullptr; } - if (channel) { - g_io_channel_unref(channel); - channel = nullptr; - } } gboolean udpThreadCb(GIOChannel*, GIOCondition condition, gpointer data) { @@ -122,47 +118,44 @@ gboolean udpThreadCb(GIOChannel*, GIOCondition condition, gpointer data) { if (condition & G_IO_IN) { char buf[MAX_UDPLEN]; - struct sockaddr_in peer; - socklen_t peer_len = sizeof(peer); - - ssize_t n = recvfrom(udpThread->fd, buf, sizeof(buf) - 1, 0, - (struct sockaddr*)&peer, &peer_len); - - if (n > 0) { - buf[n] = '\0'; - LOG_INFO("Received %zd bytes: %s\n", n, buf); - if (!udpThread->ops->on_new_msg(udpThread, peer.sin_addr, - ntohs(peer.sin_port), buf, n)) { - LOG_WARN("udpThreadCb on_new_msg failed"); - } - } else { - LOG_ERROR("recvfrom failed: [%d] %s", errno, strerror(errno)); + GSocketAddress* peer; + GError* error = nullptr; + + gssize n = g_socket_receive_from(udpThread->socket, &peer, buf, + sizeof(buf) - 1, NULL, /* GCancellable */ + &error); + if (n < 0) { + LOG_ERROR("g_socket_receive_from failed: %s", error->message); + g_error_free(error); + return TRUE; // keep watching + } + + if (n == 0) { + g_object_unref(peer); + return TRUE; // keep watching + } + + buf[n] = '\0'; + LOG_INFO("Received %zd bytes: %s\n", n, buf); + if (!udpThread->ops->on_new_msg(udpThread, peer, buf, n)) { + LOG_WARN("udpThreadCb on_new_msg failed"); } + g_object_unref(peer); } return TRUE; // keep watching } gboolean udpThreadAttachUdp(UdpThread* udpThread) { - udpThread->channel = g_io_channel_unix_new(udpThread->fd); - if (!udpThread->channel) { - LOG_ERROR("Failed to create GIOChannel for UDP socket"); - return FALSE; - } - - g_io_channel_set_encoding(udpThread->channel, NULL, NULL); - g_io_channel_set_buffered(udpThread->channel, FALSE); - - GSource* src = g_io_create_watch( - udpThread->channel, (GIOCondition)(G_IO_IN | G_IO_ERR | G_IO_HUP)); + GSource* src = g_socket_create_source( + udpThread->socket, (GIOCondition)(G_IO_IN | G_IO_ERR | G_IO_HUP), NULL); g_source_set_callback(src, (GSourceFunc)udpThreadCb, udpThread, NULL); udpThread->id = g_source_attach(src, udpThread->ctx); g_source_unref(src); if (udpThread->id == 0) { - LOG_ERROR("g_io_add_watch failed, unable to attach UDP socket to main loop"); - g_io_channel_unref(udpThread->channel); - udpThread->channel = nullptr; + LOG_ERROR( + "g_source_attach failed, unable to attach UDP socket to main loop"); return FALSE; } return TRUE; @@ -249,6 +242,183 @@ gboolean udpThreadStart(UdpThread* udpThread) { return TRUE; } +// ============================================================================ +// TCP Thread Implementation +// ============================================================================ + +struct TcpThread; +struct TcpThreadOps { + void (*on_new_connection)(TcpThread* tcpThread, GSocket* clientSocket); +}; + +enum TcpThreadState { + TCP_THREAD_STATE_INIT = 0, + TCP_THREAD_STATE_RUNNING, + TCP_THREAD_STATE_CLOSING, + TCP_THREAD_STATE_CLOSED +}; + +struct TcpThread { + // config + GSocket* socket = nullptr; + void* data = nullptr; + const TcpThreadOps* ops = nullptr; + // runtime + atomic state = TCP_THREAD_STATE_INIT; + GMainLoop* loop = nullptr; + GThread* thread = nullptr; + GMainContext* ctx = nullptr; + guint id = 0; + + TcpThread() = default; + ~TcpThread(); +}; + +void tcpThreadStop(TcpThread* tcpThread); + +TcpThread::~TcpThread() { + if (state != TCP_THREAD_STATE_CLOSED && state != TCP_THREAD_STATE_INIT) { + tcpThreadStop(this); + } + assert(state == TCP_THREAD_STATE_CLOSED || state == TCP_THREAD_STATE_INIT); + assert(thread == nullptr); + if (loop) { + g_main_loop_unref(loop); + loop = nullptr; + } + if (ctx) { + g_main_context_unref(ctx); + ctx = nullptr; + } +} + +gboolean tcpThreadCb(GSocket* socket, GIOCondition condition, gpointer data) { + TcpThread* tcpThread = static_cast(data); + + if (condition & (G_IO_HUP | G_IO_ERR)) { + LOG_ERROR("TCP socket closed or error in tcpThreadCb (condition=0x%x)", + condition); + return FALSE; // remove source + } + + if (condition & G_IO_IN) { + GError* error = nullptr; + GSocket* clientSocket = g_socket_accept(socket, nullptr, &error); + if (!clientSocket) { + if (error) { + LOG_WARN("g_socket_accept failed: %s", error->message); + g_error_free(error); + } + return TRUE; // keep watching + } + + tcpThread->ops->on_new_connection(tcpThread, clientSocket); + } + + return TRUE; // keep watching +} + +gboolean tcpThreadAttachTcp(TcpThread* tcpThread) { + GError* error = nullptr; + if (!g_socket_listen(tcpThread->socket, &error)) { + LOG_ERROR("g_socket_listen failed: %s", error->message); + g_error_free(error); + return FALSE; + } + + GSource* src = g_socket_create_source( + tcpThread->socket, (GIOCondition)(G_IO_IN | G_IO_ERR | G_IO_HUP), NULL); + g_source_set_callback(src, (GSourceFunc)tcpThreadCb, tcpThread, NULL); + tcpThread->id = g_source_attach(src, tcpThread->ctx); + g_source_unref(src); + + if (tcpThread->id == 0) { + LOG_ERROR( + "g_source_attach failed, unable to attach TCP socket to main loop"); + return FALSE; + } + return TRUE; +} + +gboolean tcpThreadStop0(gpointer data) { + TcpThread* tcpThread = static_cast(data); + + LOG_DEBUG("Quitting TCP thread loop"); + g_main_loop_quit(tcpThread->loop); + return FALSE; +} + +void tcpThreadStop(TcpThread* tcpThread) { + if (tcpThread->state == TCP_THREAD_STATE_CLOSED) { + LOG_WARN("TCP thread already closed"); + return; + } + if (tcpThread->state == TCP_THREAD_STATE_INIT) { + LOG_WARN("TCP thread not started"); + tcpThread->state = TCP_THREAD_STATE_CLOSED; + return; + } + if (tcpThread->state == TCP_THREAD_STATE_RUNNING) { + g_main_context_invoke(tcpThread->ctx, tcpThreadStop0, tcpThread); + } + (void)g_thread_join(tcpThread->thread); + tcpThread->thread = nullptr; + tcpThread->state = TCP_THREAD_STATE_CLOSED; + LOG_INFO("TCP thread stopped"); +} + +static bool tcpThreadRun0(TcpThread* tcpThread) { + if (!g_main_context_acquire(tcpThread->ctx)) { + LOG_ERROR("g_main_context_acquire failed"); + return false; + } + g_main_context_push_thread_default(tcpThread->ctx); + + tcpThread->loop = g_main_loop_new(tcpThread->ctx, FALSE); + if (!tcpThreadAttachTcp(tcpThread)) { + LOG_ERROR("tcpThreadAttachTcp failed"); + return false; + } + return true; +} + +static gpointer tcpThreadRun(gpointer data) { + TcpThread* tcpThread = static_cast(data); + + if (!tcpThreadRun0(tcpThread)) { + LOG_ERROR("Failed to init TCP thread"); + tcpThread->state = TCP_THREAD_STATE_CLOSING; + return NULL; + } + + LOG_INFO("TCP thread start"); + g_main_loop_run(tcpThread->loop); + g_main_context_pop_thread_default(tcpThread->ctx); + g_main_loop_unref(tcpThread->loop); + g_main_context_unref(tcpThread->ctx); + tcpThread->loop = nullptr; + tcpThread->ctx = nullptr; + tcpThread->state = TCP_THREAD_STATE_CLOSING; + return NULL; +} + +gboolean tcpThreadStart(TcpThread* tcpThread) { + if (tcpThread->state != TCP_THREAD_STATE_INIT) { + LOG_WARN("TCP thread already started"); + return FALSE; + } + + tcpThread->ctx = g_main_context_new(); + tcpThread->thread = g_thread_new("tcpThread", tcpThreadRun, tcpThread); + tcpThread->state = TCP_THREAD_STATE_RUNNING; + if (!tcpThread->thread) { + LOG_ERROR("Failed to create TCP thread"); + tcpThread->state = TCP_THREAD_STATE_CLOSED; + return FALSE; + } + return TRUE; +} + namespace { /** * 初始化程序iptux的运行环境. @@ -305,6 +475,7 @@ void init_iptux_environment() { // MARK: CoreThread struct CoreThread::Impl { + CoreThread* owner{nullptr}; // Back-pointer to owner uint16_t port; PPalInfo me; @@ -323,30 +494,78 @@ struct CoreThread::Impl { deque> waitingEvents; std::mutex waitingEventsMutex; - future tcpFuture; future notifyToAllFuture; UdpThread* udpThread{nullptr}; + TcpThread* tcpThread{nullptr}; enum CoreThreadErr lastErr; + GSocket* udpSocket{nullptr}; + GSocket* tcpSocket{nullptr}; + bool ignoreTcpBindFailed{false}; + + // TCP handler threads for incoming connections + std::list tcpHandlerThreads; + std::mutex tcpHandlerThreadsMutex; Impl() = default; ~Impl(); + + std::list::iterator addTcpHandlerThread(GThread* thread); + void removeTcpHandlerThread(std::list::iterator it); + void joinAllTcpHandlerThreads(); }; CoreThread::Impl::~Impl() { + // Join any remaining TCP handler threads + joinAllTcpHandlerThreads(); + if (udpThread) { delete udpThread; udpThread = nullptr; } + if (tcpThread) { + delete tcpThread; + tcpThread = nullptr; + } + if (udpSocket) { + g_object_unref(udpSocket); + udpSocket = nullptr; + } + if (tcpSocket) { + g_object_unref(tcpSocket); + tcpSocket = nullptr; + } +} + +std::list::iterator CoreThread::Impl::addTcpHandlerThread(GThread* thread) { + std::lock_guard lock(tcpHandlerThreadsMutex); + tcpHandlerThreads.push_back(thread); + return std::prev(tcpHandlerThreads.end()); +} + +void CoreThread::Impl::removeTcpHandlerThread(std::list::iterator it) { + std::lock_guard lock(tcpHandlerThreadsMutex); + GThread* thread = *it; + tcpHandlerThreads.erase(it); + g_thread_join(thread); +} + +void CoreThread::Impl::joinAllTcpHandlerThreads() { + std::lock_guard lock(tcpHandlerThreadsMutex); + for (GThread* thread : tcpHandlerThreads) { + if (thread) { + g_thread_join(thread); + } + } + tcpHandlerThreads.clear(); } CoreThread::CoreThread(shared_ptr data) : programData(data), config(data->getConfig()), - tcpSock(-1), - udpSock(-1), started(false), pImpl(std::make_unique()) { + pImpl->owner = this; if (config->GetBool("debug_dont_broadcast")) { pImpl->debugDontBroadcast = true; } @@ -370,14 +589,13 @@ CoreThread::~CoreThread() { } bool udpThreadOpsOnNewMsg(UdpThread* udpThread, - in_addr ipv4, - uint16_t port, + GSocketAddress* peer, const char* msg, size_t size) { CoreThread::Impl* self = static_cast(udpThread->data); try { - self->udp_data_service->process(ipv4, port, msg, size); + self->udp_data_service->process(peer, msg, size); } catch (const std::exception& e) { LOG_ERROR("Exception in UDP message processing: %s", e.what()); return false; @@ -399,6 +617,73 @@ static const UdpThreadOps udpThreadOps = { .on_init_failed = udpThreadOpsOnInitFailed, }; +// Data passed to TCP handler thread +struct TcpHandlerData { + CoreThread* coreThread; + CoreThread::Impl* impl; + GSocket* clientSocket; + std::list::iterator threadIt; // Set after thread creation + bool threadItValid{false}; +}; + +// Cleanup callback invoked on TCP thread's main context +static gboolean tcpHandlerCleanupCb(gpointer data) { + TcpHandlerData* handlerData = static_cast(data); + if (handlerData->threadItValid) { + handlerData->impl->removeTcpHandlerThread(handlerData->threadIt); + } + delete handlerData; + return FALSE; +} + +static gpointer tcpHandlerThreadFunc(gpointer data) { + TcpHandlerData* handlerData = static_cast(data); + try { + TcpData::TcpDataEntry(handlerData->coreThread, handlerData->clientSocket); + } catch (const std::exception& e) { + LOG_ERROR("Exception in TCP handler: %s", e.what()); + } catch (...) { + LOG_ERROR("Unknown exception in TCP handler"); + } + + // Schedule cleanup on TCP thread's main context + if (handlerData->impl->tcpThread && + handlerData->impl->tcpThread->ctx) { + g_main_context_invoke(handlerData->impl->tcpThread->ctx, + tcpHandlerCleanupCb, handlerData); + } else { + // TCP thread already stopped, just delete + delete handlerData; + } + return nullptr; +} + +void tcpThreadOpsOnNewConnection(TcpThread* tcpThread, GSocket* clientSocket) { + CoreThread::Impl* impl = static_cast(tcpThread->data); + + // Create handler data (will be freed by cleanup callback) + TcpHandlerData* handlerData = new TcpHandlerData(); + handlerData->coreThread = impl->owner; + handlerData->impl = impl; + handlerData->clientSocket = clientSocket; + + // Create a GThread to handle the connection + GThread* thread = + g_thread_new("tcp-handler", tcpHandlerThreadFunc, handlerData); + if (thread) { + handlerData->threadIt = impl->addTcpHandlerThread(thread); + handlerData->threadItValid = true; + } else { + LOG_ERROR("Failed to create TCP handler thread"); + delete handlerData; + g_object_unref(clientSocket); + } +} + +static const TcpThreadOps tcpThreadOps = { + .on_new_connection = tcpThreadOpsOnNewConnection, +}; + /** * 程序核心入口,主要任务服务将在此开启. */ @@ -413,7 +698,7 @@ bool CoreThread::start() noexcept { return false; pImpl->udpThread = new UdpThread(); - pImpl->udpThread->fd = udpSock; + pImpl->udpThread->socket = pImpl->udpSocket; pImpl->udpThread->ops = &udpThreadOps; pImpl->udpThread->data = pImpl.get(); if (!udpThreadStart(pImpl->udpThread)) { @@ -422,91 +707,127 @@ bool CoreThread::start() noexcept { return false; } - pImpl->tcpFuture = async([](CoreThread* ct) { RecvTcpData(ct); }, this); + if (pImpl->tcpSocket) { + pImpl->tcpThread = new TcpThread(); + pImpl->tcpThread->socket = pImpl->tcpSocket; + pImpl->tcpThread->ops = &tcpThreadOps; + pImpl->tcpThread->data = pImpl.get(); + if (!tcpThreadStart(pImpl->tcpThread)) { + pImpl->lastErr = CORE_THREAD_ERR_TCP_THREAD_START_FAILED; + LOG_ERROR("Failed to start TCP thread"); + return false; + } + } + pImpl->notifyToAllFuture = async([](CoreThread* ct) { SendNotifyToAll(ct); }, this); return true; } -bool CoreThread::bind_iptux_port() noexcept { - uint16_t port = programData->port(); - struct sockaddr_in addr; - tcpSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - socket_enable_reuse(tcpSock); - udpSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); - socket_enable_reuse(udpSock); - socket_enable_broadcast(udpSock); - if ((tcpSock == -1) || (udpSock == -1)) { - int ec = errno; - const char* errmsg = g_strdup_printf( - _("Fatal Error!! Failed to create new socket!\n%s"), strerror(ec)); - LOG_WARN("%s", errmsg); - pImpl->lastErr = CORE_THREAD_ERR_SOCKET_CREATE_FAILED; - return false; +static GSocket* bind_udp_port(const char* ip, uint16_t port) { + GError* error = nullptr; + GSocket* udpSock = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, + G_SOCKET_PROTOCOL_UDP, &error); + if (error != nullptr) { + LOG_ERROR("g_socket_new failed: %s", error->message); + g_error_free(error); + return nullptr; } + g_socket_set_broadcast(udpSock, TRUE); - memset(&addr, '\0', sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); + GSocketAddress* bind_addr = g_inet_socket_address_new_from_string(ip, port); + if (!bind_addr) { + g_object_unref(udpSock); + LOG_ERROR("create bind address failed: %s:%d", ip, port); + return nullptr; + } - auto bind_ip = config->GetString("bind_ip", "0.0.0.0"); - addr.sin_addr = inAddrFromString(bind_ip); - if (::bind(tcpSock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { - int ec = errno; - close(tcpSock); - close(udpSock); + if (!g_socket_bind(udpSock, bind_addr, TRUE, &error)) { auto errmsg = - stringFormat(_("Fatal Error!! Failed to bind the TCP port(%s:%d)!\n%s"), - bind_ip.c_str(), port, strerror(ec)); + stringFormat(_("Fatal Error!! Failed to bind the UDP port(%s:%d)!\n%s"), + ip, port, error->message); + g_error_free(error); LOG_ERROR("%s", errmsg.c_str()); - pImpl->lastErr = CORE_THREAD_ERR_TCP_BIND_FAILED; - return false; - } else { - LOG_INFO("bind TCP port(%s:%d) success.", bind_ip.c_str(), port); + g_object_unref(bind_addr); + g_object_unref(udpSock); + return nullptr; + } + g_object_unref(bind_addr); + LOG_INFO("bind UDP port(%s:%d) success.", ip, port); + return udpSock; +} + +static GSocket* bind_tcp_port(const char* ip, uint16_t port) { + GError* error = nullptr; + GSocket* tcpSock = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_TCP, &error); + if (error != nullptr) { + LOG_ERROR("g_socket_new failed: %s", error->message); + g_error_free(error); + return nullptr; + } + + // Enable SO_REUSEADDR + if (!g_socket_set_option(tcpSock, SOL_SOCKET, SO_REUSEADDR, 1, &error)) { + LOG_WARN("g_socket_set_option for SO_REUSEADDR failed: %s", error->message); + g_error_free(error); + error = nullptr; + } + + GSocketAddress* bind_addr = g_inet_socket_address_new_from_string(ip, port); + if (!bind_addr) { + g_object_unref(tcpSock); + LOG_ERROR("create bind address failed: %s:%d", ip, port); + return nullptr; } - if (::bind(udpSock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { - int ec = errno; - close(tcpSock); - close(udpSock); + if (!g_socket_bind(tcpSock, bind_addr, TRUE, &error)) { auto errmsg = - stringFormat(_("Fatal Error!! Failed to bind the UDP port(%s:%d)!\n%s"), - bind_ip.c_str(), port, strerror(ec)); + stringFormat(_("Fatal Error!! Failed to bind the TCP port(%s:%d)!\n%s"), + ip, port, error->message); + g_error_free(error); LOG_ERROR("%s", errmsg.c_str()); + g_object_unref(bind_addr); + g_object_unref(tcpSock); + return nullptr; + } + g_object_unref(bind_addr); + LOG_INFO("bind TCP port(%s:%d) success.", ip, port); + return tcpSock; +} + +void CoreThread::setIgnoreTcpBindFailed(bool ignore) { + pImpl->ignoreTcpBindFailed = ignore; +} + +size_t CoreThread::getTcpHandlerThreadCount() const { + std::lock_guard lock(pImpl->tcpHandlerThreadsMutex); + return pImpl->tcpHandlerThreads.size(); +} + +bool CoreThread::bind_iptux_port() noexcept { + auto bind_ip = config->GetString("bind_ip", "0.0.0.0"); + uint16_t port = programData->port(); + + pImpl->udpSocket = bind_udp_port(bind_ip.c_str(), port); + if (!pImpl->udpSocket) { pImpl->lastErr = CORE_THREAD_ERR_UDP_BIND_FAILED; return false; - } else { - LOG_INFO("bind UDP port(%s:%d) success.", bind_ip.c_str(), port); } - return true; -} -/** - * 监听TCP服务端口. - * @param pcthrd 核心类 - */ -void CoreThread::RecvTcpData(CoreThread* pcthrd) { - int subsock; - - listen(pcthrd->tcpSock, 5); - while (pcthrd->started) { - struct pollfd pfd = {pcthrd->tcpSock, POLLIN, 0}; - int ret = poll(&pfd, 1, 10); - if (ret == -1) { - LOG_ERROR("poll tcp socket failed: %s", strerror(errno)); - return; - } - if (ret == 0) { - continue; + pImpl->tcpSocket = bind_tcp_port(bind_ip.c_str(), port); + if (!pImpl->tcpSocket) { + if (pImpl->ignoreTcpBindFailed) { + LOG_WARN("TCP bind failed but ignored due to ignoreTcpBindFailed flag"); + } else { + pImpl->lastErr = CORE_THREAD_ERR_TCP_BIND_FAILED; + g_object_unref(pImpl->udpSocket); + pImpl->udpSocket = nullptr; + return false; } - g_assert(ret == 1); - if ((subsock = accept(pcthrd->tcpSock, NULL, NULL)) == -1) - continue; - thread([](CoreThread* coreThread, - int subsock) { TcpData::TcpDataEntry(coreThread, subsock); }, - pcthrd, subsock) - .detach(); } + + return true; } void CoreThread::stop() { @@ -515,10 +836,14 @@ void CoreThread::stop() { } started = false; ClearSublayer(); + if (pImpl->tcpThread) { + tcpThreadStop(pImpl->tcpThread); + } if (pImpl->udpThread) { udpThreadStop(pImpl->udpThread); } - pImpl->tcpFuture.wait(); + // Join all TCP handler threads after stopping the accept thread + pImpl->joinAllTcpHandlerThreads(); pImpl->notifyToAllFuture.wait(); } @@ -529,16 +854,31 @@ uint16_t CoreThread::port() const { void CoreThread::ClearSublayer() { /** * @note 必须在发送下线信息之后才能关闭套接口. + * Socket closing is handled by the thread destructors and Impl destructor. */ for (auto palInfo : pImpl->pallist) { SendBroadcastExit(palInfo); } - shutdown(tcpSock, SHUT_RDWR); - shutdown(udpSock, SHUT_RDWR); } int CoreThread::getUdpSock() const { - return udpSock; + if (!pImpl->udpSocket) { + return -1; + } + // TODO: should no longer use it + return g_socket_get_fd(pImpl->udpSocket); +} + +GSocket* CoreThread::getUdpSocket() const { + return pImpl->udpSocket; +} + +int CoreThread::getTcpSock() const { + if (!pImpl->tcpSocket) { + return -1; + } + // TODO: should no longer use it + return g_socket_get_fd(pImpl->tcpSocket); } shared_ptr CoreThread::getProgramData() { @@ -552,9 +892,9 @@ shared_ptr CoreThread::getProgramData() { void CoreThread::SendNotifyToAll(CoreThread* pcthrd) { Command cmd(*pcthrd); if (!pcthrd->pImpl->debugDontBroadcast) { - cmd.BroadCast(pcthrd->udpSock, pcthrd->port()); + cmd.BroadCast(pcthrd->getUdpSocket(), pcthrd->port()); } - cmd.DialUp(pcthrd->udpSock, pcthrd->port()); + cmd.DialUp(pcthrd->getUdpSock(), pcthrd->port()); } /** @@ -683,36 +1023,43 @@ void CoreThread::emitEvent(shared_ptr event) { * 向好友发送iptux特有的数据. * @param pal class PalInfo */ -void CoreThread::sendFeatureData(PPalInfo pal) { +bool CoreThread::sendFeatureData(PPalInfo pal) noexcept { Command cmd(*this); char path[MAX_PATHLEN]; const gchar* env; - int sock; if (!programData->sign.empty()) { - cmd.SendMySign(udpSock, pal); + cmd.SendMySign(getUdpSock(), pal); } env = g_get_user_config_dir(); snprintf(path, MAX_PATHLEN, "%s" ICON_PATH "/%s", env, programData->myicon.c_str()); if (access(path, F_OK) == 0) { ifstream ifs(path); - cmd.SendMyIcon(udpSock, pal, ifs); + cmd.SendMyIcon(getUdpSock(), pal, ifs); } snprintf(path, MAX_PATHLEN, "%s" PHOTO_PATH "/photo", env); if (access(path, F_OK) == 0) { - if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + GError* error = nullptr; + GSocket* sock = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_TCP, &error); + if (error != nullptr) { LOG_ERROR(_("Fatal Error!!\nFailed to create new socket!\n%s"), - strerror(errno)); - throw Exception(CREATE_TCP_SOCKET_FAILED); + error->message); + g_error_free(error); + pImpl->lastErr = CORE_THREAD_ERR_SOCKET_CREATE_FAILED; + return false; } - cmd.SendSublayer(sock, pal, IPTUX_PHOTOPICOPT, path); - close(sock); + + bool ret = cmd.SendSublayer(sock, pal, IPTUX_PHOTOPICOPT, path); + g_object_unref(sock); + return ret; } + return true; } void CoreThread::SendMyIcon(PPalInfo pal, istream& iss) { - Command(*this).SendMyIcon(udpSock, pal, iss); + Command(*this).SendMyIcon(getUdpSock(), pal, iss); } void CoreThread::AddBlockIp(in_addr ipv4) { @@ -728,21 +1075,26 @@ bool CoreThread::SendMessage(CPPalInfo palInfo, const string& message) { bool CoreThread::SendMessage(CPPalInfo pal, const ChipData& chipData) { auto ptr = chipData.data.c_str(); + bool ret = true; + switch (chipData.type) { case MessageContentType::STRING: /* 文本类型 */ return SendMessage(pal, chipData.data); - case MESSAGE_CONTENT_TYPE_PICTURE: - /* 图片类型 */ - int sock; - if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + case MESSAGE_CONTENT_TYPE_PICTURE: { + GError* error = nullptr; + GSocket* sock = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_TCP, &error); + if (error != nullptr) { LOG_ERROR(_("Fatal Error!!\nFailed to create new socket!\n%s"), - strerror(errno)); + error->message); + g_error_free(error); return false; } - Command(*this).SendSublayer(sock, pal, IPTUX_MSGPICOPT, ptr); - close(sock); // 关闭网络套接口 - return true; + ret = Command(*this).SendSublayer(sock, pal, IPTUX_MSGPICOPT, ptr); + g_object_unref(sock); + return ret; + } default: g_assert_not_reached(); } @@ -787,7 +1139,7 @@ void CoreThread::UpdateMyInfo() { Lock(); for (auto pal : pImpl->pallist) { if (pal->isOnline()) { - cmd.SendAbsence(udpSock, pal); + cmd.SendAbsence(getUdpSock(), pal); } if (pal->isOnline() and pal->isCompatible()) { thread t1(bind(&CoreThread::sendFeatureData, this, _1), pal); @@ -804,10 +1156,14 @@ void CoreThread::UpdateMyInfo() { */ void CoreThread::SendBroadcastExit(PPalInfo pal) { Command cmd(*this); - cmd.SendExit(udpSock, pal); + cmd.SendExit(getUdpSock(), pal); } int CoreThread::GetOnlineCount() const { + if (this->started == false) { + return 0; + } + int res = 0; for (auto pal : pImpl->pallist) { if (pal->isOnline()) { @@ -822,7 +1178,7 @@ void CoreThread::SendDetectPacket(const string& ipv4) { } void CoreThread::SendDetectPacket(in_addr ipv4) { - Command(*this).SendDetectPacket(udpSock, ipv4, port()); + Command(*this).SendDetectPacket(getUdpSock(), ipv4, port()); } void CoreThread::emitSomeoneExit(const PalKey& palKey) { @@ -839,7 +1195,7 @@ void CoreThread::EmitIconUpdate(const PalKey& palKey) { } void CoreThread::SendExit(PPalInfo palInfo) { - Command(*this).SendExit(udpSock, palInfo); + Command(*this).SendExit(getUdpSock(), palInfo); } const string& CoreThread::GetAccessPublicLimit() const { @@ -943,7 +1299,7 @@ bool CoreThread::SendAskSharedWithPassword(const PalKey& palKey, const std::string& password) { auto epasswd = g_base64_encode((const guchar*)(password.c_str()), password.size()); - Command(*this).SendAskShared(udpSock, palKey, IPTUX_PASSWDOPT, epasswd); + Command(*this).SendAskShared(getUdpSock(), palKey, IPTUX_PASSWDOPT, epasswd); g_free(epasswd); return true; } @@ -951,12 +1307,13 @@ bool CoreThread::SendAskSharedWithPassword(const PalKey& palKey, void CoreThread::SendUnitMessage(const PalKey& palKey, uint32_t opttype, const string& message) { - Command(*this).SendUnitMsg(udpSock, GetPal(palKey), opttype, message.c_str()); + Command(*this).SendUnitMsg(getUdpSock(), GetPal(palKey), opttype, + message.c_str()); } void CoreThread::SendGroupMessage(const PalKey& palKey, const std::string& message) { - Command(*this).SendGroupMsg(udpSock, GetPal(palKey), message.c_str()); + Command(*this).SendGroupMsg(getUdpSock(), GetPal(palKey), message.c_str()); } void CoreThread::BcstFileInfoEntry(const vector& pals, diff --git a/src/iptux-core/CoreThreadTest.cpp b/src/iptux-core/CoreThreadTest.cpp index 2038013a2..a612491b7 100644 --- a/src/iptux-core/CoreThreadTest.cpp +++ b/src/iptux-core/CoreThreadTest.cpp @@ -21,6 +21,7 @@ TEST(CoreThread, Constructor) { auto core = make_shared(config); core->sign = "abc"; CoreThread* thread = new CoreThread(core); + thread->setIgnoreTcpBindFailed(true); EXPECT_TRUE(thread->start()); thread->stop(); delete thread; @@ -273,3 +274,75 @@ TEST(CoreThread, clearFinishedTransTasks) { auto thread = newCoreThreadOnIp("127.0.0.1"); thread->clearFinishedTransTasks(); } + +TEST(CoreThread, TcpHandlerThreadCount) { + auto thread = newCoreThreadOnIp("127.0.0.1"); + EXPECT_EQ(thread->getTcpHandlerThreadCount(), 0u); +} + +TEST(CoreThread, TcpHandlerThreadCleanup) { + using namespace std::chrono_literals; + auto oldLogLevel = Log::getLogLevel(); + Log::setLogLevel(LogLevel::INFO); + + auto config1 = IptuxConfig::newFromString("{}"); + config1->SetString("bind_ip", "127.0.0.1"); + auto config2 = IptuxConfig::newFromString("{}"); + config2->SetString("bind_ip", "127.0.0.2"); + + auto threads = initAndConnnectThreadsFromConfig(config1, config2); + auto thread1 = get<0>(threads); + auto thread2 = get<1>(threads); + + auto pal2InThread1 = thread1->GetPal("127.0.0.2"); + ASSERT_TRUE(pal2InThread1); + + // Send a picture which creates a TCP connection + ChipData chipData(MessageContentType::PICTURE, testDataPath("iptux.png")); + + // Initial thread count should be 0 + EXPECT_EQ(thread2->getTcpHandlerThreadCount(), 0u); + + // Send picture - this creates TCP connection to thread2 + thread1->SendMessage(pal2InThread1, chipData); + + // Wait for TCP handler to complete and clean up + // The handler should be created and then cleaned up after completion + this_thread::sleep_for(500ms); + + // After completion, thread count should be back to 0 + EXPECT_EQ(thread2->getTcpHandlerThreadCount(), 0u); + + Log::setLogLevel(oldLogLevel); + thread1->stop(); + thread2->stop(); +} + +TEST(CoreThread, TcpHandlerThreadStopWithActiveThreads) { + using namespace std::chrono_literals; + auto oldLogLevel = Log::getLogLevel(); + Log::setLogLevel(LogLevel::INFO); + + auto config1 = IptuxConfig::newFromString("{}"); + config1->SetString("bind_ip", "127.0.0.3"); + auto config2 = IptuxConfig::newFromString("{}"); + config2->SetString("bind_ip", "127.0.0.4"); + + auto threads = initAndConnnectThreadsFromConfig(config1, config2); + auto thread1 = get<0>(threads); + auto thread2 = get<1>(threads); + + auto pal2InThread1 = thread1->GetPal("127.0.0.4"); + ASSERT_TRUE(pal2InThread1); + + // Send a picture to create TCP connection + ChipData chipData(MessageContentType::PICTURE, testDataPath("iptux.png")); + thread1->SendMessage(pal2InThread1, chipData); + + // Don't wait for completion - stop immediately + // This tests that stop() properly joins all handler threads + Log::setLogLevel(oldLogLevel); + thread1->stop(); + thread2->stop(); + // If we get here without hanging, the test passes +} diff --git a/src/iptux-core/TestHelper.cpp b/src/iptux-core/TestHelper.cpp index db10b5e0f..e01a89c46 100644 --- a/src/iptux-core/TestHelper.cpp +++ b/src/iptux-core/TestHelper.cpp @@ -34,22 +34,30 @@ shared_ptr newTestIptuxConfigWithFile() { std::shared_ptr newCoreThread() { auto config = newTestIptuxConfig(); - return make_shared(make_shared(config)); + auto thread = make_shared(make_shared(config)); + thread->setIgnoreTcpBindFailed(true); + return thread; } std::shared_ptr newCoreThreadOnIp(const std::string& ip) { auto config = newTestIptuxConfig(); config->SetString("bind_ip", ip); - return make_shared(make_shared(config)); + auto thread = make_shared(make_shared(config)); + thread->setIgnoreTcpBindFailed(true); + return thread; } std::tuple initAndConnnectThreadsFromConfig( PIptuxConfig c1, PIptuxConfig c2) { + int count; + c1->SetBool("debug_dont_broadcast", true); c2->SetBool("debug_dont_broadcast", true); auto thread1 = make_shared(make_shared(c1)); auto thread2 = make_shared(make_shared(c2)); + thread1->setIgnoreTcpBindFailed(true); + thread2->setIgnoreTcpBindFailed(true); if (!thread2->start()) { cerr << "bind to " << c2->GetString("bind_ip") << " failed.\n" << "if you are using mac, please run `sudo ifconfig lo0 alias " @@ -57,17 +65,17 @@ std::tuple initAndConnnectThreadsFromConfig( throw; } thread1->start(); - while (thread2->GetOnlineCount() != 1) { - LOG_INFO("thread2 online count: %d", thread2->GetOnlineCount()); + while ((count = thread2->GetOnlineCount()) != 1) { + LOG_INFO("thread2 online count: %d", count); thread1->SendDetectPacket(c2->GetString("bind_ip")); this_thread::yield(); - this_thread::sleep_for(10ms); + this_thread::sleep_for(100ms); } - while (thread1->GetOnlineCount() != 1) { - LOG_INFO("thread1 online count: %d", thread1->GetOnlineCount()); + while ((count = thread1->GetOnlineCount()) != 1) { + LOG_INFO("thread1 online count: %d", count); thread2->SendDetectPacket(c1->GetString("bind_ip")); this_thread::yield(); - this_thread::sleep_for(10ms); + this_thread::sleep_for(100ms); } return make_tuple(thread1, thread2); } diff --git a/src/iptux-core/internal/Command.cpp b/src/iptux-core/internal/Command.cpp index a4fd25c08..b211ab553 100644 --- a/src/iptux-core/internal/Command.cpp +++ b/src/iptux-core/internal/Command.cpp @@ -86,6 +86,38 @@ static bool commandSendTo(int sockfd, return commandSendTo(sockfd, buf, len, flags, pal->ipv4(), pal->port()); } +static bool commandSendTo(GSocket* sock, + const void* buf, + size_t len, + in_addr ipv4, + uint16_t port) { + if (Log::IsDebugEnabled()) { + LOG_DEBUG("send udp message to %s:%d, size %d\n%s", + inAddrToString(ipv4).c_str(), port, int(len), + stringDump(string((const char*)buf, len)).c_str()); + } else if (Log::IsInfoEnabled()) { + LOG_INFO("send udp message to %s:%d, size %d", inAddrToString(ipv4).c_str(), + port, int(len)); + } + + GInetAddress* addr = g_inet_address_new_from_bytes((const guint8*)&ipv4, + G_SOCKET_FAMILY_IPV4); + GSocketAddress* sockAddr = g_inet_socket_address_new(addr, port); + g_object_unref(addr); + + GError* error = nullptr; + gssize sent = g_socket_send_to(sock, sockAddr, (const char*)buf, len, + nullptr, &error); + g_object_unref(sockAddr); + + if (sent == -1) { + LOG_WARN("g_socket_send_to failed: %s", error->message); + g_error_free(error); + return false; + } + return true; +} + /** * 类构造函数. */ @@ -98,10 +130,10 @@ Command::Command(CoreThread& coreThread) Command::~Command() {} /** - * 向局域网所有计算机广播上线信息. - * @param sock udp socket + * Broadcast online message to all computers in LAN. + * @param sock GSocket udp socket */ -void Command::BroadCast(int sock, uint16_t port) { +void Command::BroadCast(GSocket* sock, uint16_t port) { auto programData = coreThread.getProgramData(); CreateCommand(IPMSG_ABSENCEOPT | IPMSG_BR_ENTRY, programData->nickname.c_str()); @@ -111,7 +143,7 @@ void Command::BroadCast(int sock, uint16_t port) { auto addrs = get_sys_broadcast_addr(sock); for (auto& addr : addrs) { in_addr ipv4 = inAddrFromString(addr); - commandSendTo(sock, buf, size, 0, ipv4, port); + commandSendTo(sock, buf, size, ipv4, port); g_usleep(9999); } } @@ -277,93 +309,96 @@ void Command::SendUnitMsg(int sock, } /** - * 向好友请求文件数据. - * @param sock tcp socket - * @param pal class PalInfo - * @param packetno 好友消息的包编号 - * @param fileid 文件ID标识 - * @param offset 文件偏移量 - * @return 消息发送成功与否 + * Request file data from peer. + * @param sock GSocket tcp socket + * @param palKey peer key + * @param packetno packet number + * @param fileid file ID + * @param offset file offset + * @return true on success */ -bool Command::SendAskData(int sock, - CPPalInfo pal, +bool Command::SendAskData(GSocket* sock, + const PalKey& palKey, uint32_t packetno, uint32_t fileid, int64_t offset) { + auto pal = getAndCheckPalInfo(coreThread, palKey); char attrstr[35]; // 8+1+8+1+16 +1 =35 - struct sockaddr_in addr; const char* iptuxstr = "iptux"; snprintf(attrstr, 35, "%" PRIx32 ":%" PRIx32 ":%" PRIx64, packetno, fileid, offset); - // IPMSG和Feiq的命令字段都是只有IPMSG_GETFILEDATA,使用(IPMSG_FILEATTACHOPT | - // IPMSG_GETFILEDATA) - // 会产生一些潜在的不兼容问题,所以在发往非iptux时只使用IPMSG_GETFILEDATA if (strstr(pal->getVersion().c_str(), iptuxstr)) CreateCommand(IPMSG_FILEATTACHOPT | IPMSG_GETFILEDATA, attrstr); else CreateCommand(IPMSG_GETFILEDATA, attrstr); ConvertEncode(pal->getEncode()); - memset(&addr, '\0', sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(pal->port()); - addr.sin_addr = pal->ipv4(); + in_addr ipv4 = pal->ipv4(); + GInetAddress* addr = + g_inet_address_new_from_bytes((const guint8*)&ipv4, G_SOCKET_FAMILY_IPV4); + GSocketAddress* sockAddr = g_inet_socket_address_new(addr, pal->port()); + g_object_unref(addr); + + GError* error = nullptr; + if (!g_socket_connect(sock, sockAddr, nullptr, &error)) { + LOG_WARN("g_socket_connect failed: %s", error->message); + g_error_free(error); + g_object_unref(sockAddr); + return false; + } + g_object_unref(sockAddr); - if (((connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) && - (errno != EINTR)) || - (xsend(sock, buf, size) == -1)) + gssize sent = g_socket_send(sock, buf, size, nullptr, &error); + if (sent == -1) { + LOG_WARN("g_socket_send failed: %s", error->message); + g_error_free(error); return false; + } return true; } -bool Command::SendAskData(int sock, - const PalKey& palKey, - uint32_t packetno, - uint32_t fileid, - int64_t offset) { - auto palInfo = getAndCheckPalInfo(coreThread, palKey); - return SendAskData(sock, palInfo, packetno, fileid, offset); -} - /** - * 向好友请求目录文件. - * @param sock tcp socket - * @param pal class PalInfo - * @param packetno 好友消息的包编号 - * @param fileid 文件ID标识 - * @return 消息发送成功与否 + * Request directory files from peer. + * @param sock GSocket tcp socket + * @param palKey peer key + * @param packetno packet number + * @param fileid file ID + * @return true on success */ -bool Command::SendAskFiles(int sock, +bool Command::SendAskFiles(GSocket* sock, const PalKey& palKey, uint32_t packetno, uint32_t fileid) { - auto palInfo = getAndCheckPalInfo(coreThread, palKey); - return SendAskFiles(sock, palInfo, packetno, fileid); -} - -bool Command::SendAskFiles(int sock, - CPPalInfo pal, - uint32_t packetno, - uint32_t fileid) { + auto pal = getAndCheckPalInfo(coreThread, palKey); char attrstr[20]; // 8+1+8+1+1 +1 =20 - struct sockaddr_in addr; - snprintf(attrstr, 20, "%" PRIx32 ":%" PRIx32 ":0", packetno, - fileid); // 兼容LanQQ软件 + snprintf(attrstr, 20, "%" PRIx32 ":%" PRIx32 ":0", packetno, fileid); CreateCommand(IPMSG_FILEATTACHOPT | IPMSG_GETDIRFILES, attrstr); ConvertEncode(pal->getEncode()); - memset(&addr, '\0', sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(pal->port()); - addr.sin_addr = pal->ipv4(); + in_addr ipv4 = pal->ipv4(); + GInetAddress* addr = + g_inet_address_new_from_bytes((const guint8*)&ipv4, G_SOCKET_FAMILY_IPV4); + GSocketAddress* sockAddr = g_inet_socket_address_new(addr, pal->port()); + g_object_unref(addr); + + GError* error = nullptr; + if (!g_socket_connect(sock, sockAddr, nullptr, &error)) { + LOG_WARN("g_socket_connect failed: %s", error->message); + g_error_free(error); + g_object_unref(sockAddr); + return false; + } + g_object_unref(sockAddr); - if (((connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) && - (errno != EINTR)) || - (xsend(sock, buf, size) == -1)) + gssize sent = g_socket_send(sock, buf, size, nullptr, &error); + if (sent == -1) { + LOG_WARN("g_socket_send failed: %s", error->message); + g_error_free(error); return false; + } return true; } @@ -440,73 +475,99 @@ void Command::SendMySign(int sock, CPPalInfo pal) { } /** - * 发送底层数据(即发送为最终用户所不能察觉的文件数据). - * @param sock tcp socket + * 回馈错误消息. * @param pal class PalInfo - * @param opttype 命令额外选项 - * @param path 文件路径 + * @param btype 消息归属类型 + * @param error 错误串 */ -void Command::SendSublayer(int sock, +void Command::FeedbackError(CPPalInfo pal, + GroupBelongType btype, + const char* error) { + MsgPara para(coreThread.GetPal(pal->GetKey())); + para.stype = MessageSourceType::ERROR; + para.btype = btype; + + ChipData chip(MESSAGE_CONTENT_TYPE_STRING, error); + para.dtlist.push_back(std::move(chip)); + /* 交给某人处理吧 */ + coreThread.InsertMessage(std::move(para)); +} + +/** + * Send sublayer data (file data invisible to end users). + * @param sock GSocket tcp socket + * @param pal class PalInfo + * @param opttype command option type + * @param path file path + */ +bool Command::SendSublayer(GSocket* sock, CPPalInfo pal, uint32_t opttype, const char* path) { LOG_DEBUG("send tcp message to %s, op %d, file %s", pal->GetKey().ToString().c_str(), int(opttype), path); - struct sockaddr_in addr; + GError* error = nullptr; int fd; + bool ret; CreateCommand(opttype | IPTUX_SENDSUBLAYER, NULL); ConvertEncode(pal->getEncode()); - memset(&addr, '\0', sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(pal->port()); - addr.sin_addr = pal->ipv4(); + in_addr ipv4 = pal->ipv4(); + GInetAddress* addr = + g_inet_address_new_from_bytes((const guint8*)&ipv4, G_SOCKET_FAMILY_IPV4); + GSocketAddress* sockAddr = g_inet_socket_address_new(addr, pal->port()); + g_object_unref(addr); - if (((connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) && - (errno != EINTR)) || - (xsend(sock, buf, size) == -1) || ((fd = open(path, O_RDONLY)) == -1)) { - LOG_WARN("send tcp message failed"); - return; + if (!g_socket_connect(sock, sockAddr, nullptr, &error)) { + LOG_WARN("g_socket_connect failed: %s", error->message); + g_error_free(error); + g_object_unref(sockAddr); + return false; } + g_object_unref(sockAddr); - SendSublayerData(sock, fd); - close(fd); -} + gssize sent = g_socket_send(sock, buf, size, nullptr, &error); + if (sent == -1) { + LOG_WARN("g_socket_send failed: %s", error->message); + g_error_free(error); + return false; + } -/** - * 回馈错误消息. - * @param pal class PalInfo - * @param btype 消息归属类型 - * @param error 错误串 - */ -void Command::FeedbackError(CPPalInfo pal, - GroupBelongType btype, - const char* error) { - MsgPara para(coreThread.GetPal(pal->GetKey())); - para.stype = MessageSourceType::ERROR; - para.btype = btype; + if ((fd = open(path, O_RDONLY)) == -1) { + LOG_WARN("open file failed: %s", path); + return false; + } - ChipData chip(MESSAGE_CONTENT_TYPE_STRING, error); - para.dtlist.push_back(std::move(chip)); - /* 交给某人处理吧 */ - coreThread.InsertMessage(std::move(para)); + ret = SendSublayerData(sock, fd); + close(fd); + return ret; } /** - * 将文件描述符数据写入网络套接口. - * @param sock tcp socket + * Write file descriptor data to network socket. + * @param sock GSocket tcp socket * @param fd file descriptor */ -void Command::SendSublayerData(int sock, int fd) { +bool Command::SendSublayerData(GSocket* sock, int fd) { ssize_t len; + bool ret = true; + GError* error = nullptr; do { if ((len = xread(fd, buf, MAX_UDPLEN)) <= 0) break; - if ((len = xsend(sock, buf, len)) <= 0) + gssize sent = g_socket_send(sock, buf, len, nullptr, &error); + if (sent <= 0) { + if (error) { + LOG_WARN("g_socket_send failed: %s", error->message); + g_error_free(error); + } + ret = false; break; + } } while (1); + return ret; } /** diff --git a/src/iptux-core/internal/Command.h b/src/iptux-core/internal/Command.h index 2af3482d6..ff074e22b 100644 --- a/src/iptux-core/internal/Command.h +++ b/src/iptux-core/internal/Command.h @@ -12,6 +12,7 @@ #ifndef IPTUX_COMMAND_H #define IPTUX_COMMAND_H +#include #include #include @@ -32,7 +33,7 @@ class Command { /// Const Pointer to PalInfo using CPPalInfo = std::shared_ptr; - void BroadCast(int sock, uint16_t port); + void BroadCast(GSocket* sock, uint16_t port); void DialUp(int sock, uint16_t port); void SendAnsentry(int sock, CPPalInfo pal); void SendExit(int sock, CPPalInfo pal); @@ -44,21 +45,12 @@ class Command { void SendGroupMsg(int sock, CPPalInfo pal, const char* msg); void SendUnitMsg(int sock, CPPalInfo pal, uint32_t opttype, const char* msg); - bool SendAskData(int sock, - CPPalInfo pal, - uint32_t packetno, - uint32_t fileid, - int64_t offset); - bool SendAskData(int sock, + bool SendAskData(GSocket* sock, const PalKey& pal, uint32_t packetno, uint32_t fileid, int64_t offset); - bool SendAskFiles(int sock, - CPPalInfo pal, - uint32_t packetno, - uint32_t fileid); - bool SendAskFiles(int sock, + bool SendAskFiles(GSocket* sock, const PalKey& pal, uint32_t packetno, uint32_t fileid); @@ -80,7 +72,7 @@ class Command { const char* extra); void SendMyIcon(int sock, CPPalInfo pal, std::istream& iss); void SendMySign(int sock, CPPalInfo pal); - void SendSublayer(int sock, + bool SendSublayer(GSocket* sock, CPPalInfo pal, uint32_t opttype, const char* path); @@ -90,7 +82,7 @@ class Command { private: void FeedbackError(CPPalInfo pal, GroupBelongType btype, const char* error); - void SendSublayerData(int sock, int fd); + bool SendSublayerData(GSocket* sock, int fd); void ConvertEncode(const std::string& encode); void CreateCommand(uint32_t command, const char* attach); void CreateIpmsgExtra(const char* extra, const char* encode); diff --git a/src/iptux-core/internal/RecvFileData.cpp b/src/iptux-core/internal/RecvFileData.cpp index ead8995bf..741edc34f 100644 --- a/src/iptux-core/internal/RecvFileData.cpp +++ b/src/iptux-core/internal/RecvFileData.cpp @@ -127,37 +127,39 @@ void RecvFileData::CreateUIPara() { } /** - * 接收常规文件. + * Receive regular file. */ void RecvFileData::RecvRegularFile() { AnalogFS afs; Command cmd(*coreThread); int64_t finishsize; - int sock, fd; + int fd; struct utimbuf timebuf; - /* 创建文件传输套接口 */ - if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + GError* error = nullptr; + GSocket* sock = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_TCP, &error); + if (error != nullptr) { LOG_ERROR(_("Fatal Error!!\nFailed to create new socket!\n%s"), - strerror(errno)); + error->message); + g_error_free(error); throw Exception(CREATE_TCP_SOCKET_FAILED); } - /* 请求文件数据 */ + if (!cmd.SendAskData(sock, file->fileown->GetKey(), file->packetn, file->fileid, 0)) { - close(sock); - terminate = true; // 标记处理过程失败 + g_object_unref(sock); + terminate = true; return; } - /* 打开文件 */ + if ((fd = afs.open(file->filepath, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, 00644)) == -1) { - close(sock); + g_object_unref(sock); terminate = true; return; } - /* 接收文件数据 */ gettimeofday(&filetime, NULL); finishsize = RecvData(sock, fd, file->filesize, 0); close(fd); @@ -166,9 +168,7 @@ void RecvFileData::RecvRegularFile() { timebuf.modtime = int(file->filectime); utime(file->filepath, &timebuf); } - // sumsize += finishsize; - /* 考察处理结果 */ if (finishsize < file->filesize) { terminate = true; LOG_ERROR(_("Failed to receive the file \"%s\" from %s! expect length %jd, " @@ -179,12 +179,12 @@ void RecvFileData::RecvRegularFile() { LOG_INFO(_("Receive the file \"%s\" from %s successfully!"), file->filepath, file->fileown->getName().c_str()); } - /* 关闭文件传输套接口 */ - close(sock); + + g_object_unref(sock); } /** - * 接收目录文件. + * Receive directory files. */ void RecvFileData::RecvDirFiles() { AnalogFS afs; @@ -192,23 +192,26 @@ void RecvFileData::RecvDirFiles() { gchar *dirname, *pathname, *filename, *filectime, *filemtime; int64_t filesize, finishsize; uint32_t headsize, fileattr; - int sock, fd; + int fd; ssize_t size; size_t len; bool result; struct utimbuf timebuf; - /* 创建文件传输套接口 */ - if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + GError* error = nullptr; + GSocket* sock = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_TCP, &error); + if (error != nullptr) { LOG_ERROR(_("Fatal Error!!\nFailed to create new socket!\n%s"), - strerror(errno)); + error->message); + g_error_free(error); throw Exception(CREATE_TCP_SOCKET_FAILED); } - /* 请求目录文件 */ + if (!cmd.SendAskFiles(sock, file->fileown->GetKey(), file->packetn, file->fileid)) { - close(sock); - terminate = true; // 标记处理过程失败 + g_object_unref(sock); + terminate = true; return; } /* 转到文件存档目录 */ @@ -322,8 +325,8 @@ void RecvFileData::RecvDirFiles() { LOG_INFO(_("Receive the directory \"%s\" from %s successfully!"), file->filepath, file->fileown->getName().c_str()); } - /* 关闭文件传输套接口 */ - close(sock); + + g_object_unref(sock); } /** @@ -382,7 +385,65 @@ int64_t RecvFileData::RecvData(int sock, } /** - * 更新UI参考数据到任务结束. + * Receive file data from network socket. + * @param sock GSocket tcp socket + * @param fd file descriptor + * @param filesize total file size + * @param offset starting offset + * @return received data size + */ +int64_t RecvFileData::RecvData(GSocket* sock, + int fd, + int64_t filesize, + int64_t offset) { + int64_t tmpsize, finishsize; + struct timeval val1, val2; + float difftime; + uint32_t rate; + gssize size; + + if (offset == filesize) + return filesize; + + tmpsize = finishsize = offset; + gettimeofday(&val1, NULL); + do { + size = MAX_SOCKLEN < filesize - finishsize ? MAX_SOCKLEN + : filesize - finishsize; + GError* error = nullptr; + size = g_socket_receive(sock, buf, size, nullptr, &error); + if (size == -1) { + if (error) { + LOG_WARN("g_socket_receive failed: %s", error->message); + g_error_free(error); + } + return finishsize; + } + if (size > 0 && xwrite(fd, buf, size) == -1) + return finishsize; + finishsize += size; + sumsize += size; + file->finishedsize = sumsize; + + gettimeofday(&val2, NULL); + difftime = difftimeval(val2, val1); + if (difftime >= 1) { + rate = (uint32_t)((finishsize - tmpsize) / difftime); + para.setFinishedLength(finishsize) + .setCost(numeric_to_time((uint32_t)(difftimeval(val2, filetime)))) + .setRemain( + numeric_to_time((uint32_t)((filesize - finishsize) / rate))) + .setRate(numeric_to_rate(rate)); + val1 = val2; + tmpsize = finishsize; + } + } while (!terminate && size && finishsize < filesize); + + return finishsize; +} + +/** + * Update UI parameters when task is finished. */ void RecvFileData::UpdateUIParaToOver() { struct timeval time; diff --git a/src/iptux-core/internal/RecvFileData.h b/src/iptux-core/internal/RecvFileData.h index eaf3f634d..0655d8fbb 100644 --- a/src/iptux-core/internal/RecvFileData.h +++ b/src/iptux-core/internal/RecvFileData.h @@ -12,6 +12,8 @@ #ifndef IPTUX_RECVFILEDATA_H #define IPTUX_RECVFILEDATA_H +#include + #include "iptux-core/CoreThread.h" #include "iptux-core/Models.h" #include "iptux-core/internal/TransAbstract.h" @@ -34,6 +36,7 @@ class RecvFileData : public TransAbstract { void RecvDirFiles(); int64_t RecvData(int sock, int fd, int64_t filesize, int64_t offset); + int64_t RecvData(GSocket* sock, int fd, int64_t filesize, int64_t offset); void UpdateUIParaToOver(); CoreThread* coreThread; diff --git a/src/iptux-core/internal/TcpData.cpp b/src/iptux-core/internal/TcpData.cpp index 84b3e9a04..2a6456dff 100644 --- a/src/iptux-core/internal/TcpData.cpp +++ b/src/iptux-core/internal/TcpData.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include "iptux-core/internal/CommandMode.h" @@ -29,24 +28,28 @@ namespace iptux { /** * 类构造函数. */ -TcpData::TcpData() : sock(-1), size(0) {} +TcpData::TcpData() : socket(nullptr), sock(-1), size(0) {} /** * 类析构函数. */ TcpData::~TcpData() { - close(sock); + if (socket) { + g_object_unref(socket); + socket = nullptr; + } } /** * TCP连接处理入口. - * @param sock tcp socket + * @param socket GSocket for the connection (takes ownership) */ -void TcpData::TcpDataEntry(CoreThread* coreThread, int sock) { +void TcpData::TcpDataEntry(CoreThread* coreThread, GSocket* socket) { TcpData tdata; tdata.coreThread = coreThread; - tdata.sock = sock; + tdata.socket = socket; + tdata.sock = g_socket_get_fd(socket); tdata.DispatchTcpData(); } @@ -54,27 +57,38 @@ void TcpData::TcpDataEntry(CoreThread* coreThread, int sock) { * 分派TCP数据处理方案. */ void TcpData::DispatchTcpData() { - struct sockaddr_in addr; - socklen_t socklen; - socklen = sizeof(addr); - getpeername(sock, (struct sockaddr*)&addr, &socklen); - LOG_DEBUG("received tcp message from %s:%d", - inAddrToString(addr.sin_addr).c_str(), int(addr.sin_port)); + GError* error = nullptr; + GSocketAddress* remoteAddr = g_socket_get_remote_address(socket, &error); + if (!remoteAddr) { + LOG_WARN("Failed to get remote address: %s", + error ? error->message : "unknown"); + if (error) + g_error_free(error); + return; + } + + GInetSocketAddress* inetAddr = G_INET_SOCKET_ADDRESS(remoteAddr); + GInetAddress* gaddr = g_inet_socket_address_get_address(inetAddr); + guint16 port = g_inet_socket_address_get_port(inetAddr); + char* addrStr = g_inet_address_to_string(gaddr); + LOG_DEBUG("received tcp message from %s:%d", addrStr, int(port)); + g_object_unref(remoteAddr); uint32_t commandno; ssize_t len; /* 读取消息前缀 */ if ((len = read_ipmsg_prefix(sock, buf, MAX_SOCKLEN)) <= 0) { + g_free(addrStr); return; } /* 分派消息 */ size = len; // 设置缓冲区数据的有效长度 commandno = iptux_get_dec_number(buf, ':', 4); // 获取命令字 - LOG_INFO("recv TCP request from %s, command NO.: [0x%x] %s", - inAddrToString(addr.sin_addr).c_str(), commandno, - CommandMode(GET_MODE(commandno)).toString().c_str()); + LOG_INFO("recv TCP request from %s, command NO.: [0x%x] %s", addrStr, + commandno, CommandMode(GET_MODE(commandno)).toString().c_str()); + g_free(addrStr); switch (GET_MODE(commandno)) { case IPMSG_GETFILEDATA: RequestData(FileAttr::REGULAR); @@ -124,15 +138,28 @@ void TcpData::RequestData(FileAttr fileattr) { void TcpData::RecvSublayer(uint32_t cmdopt) { static uint32_t count = 0; char path[MAX_PATHLEN]; - struct sockaddr_in addr; - socklen_t len; PPalInfo pal; int fd; /* 检查好友是否存在 */ - len = sizeof(addr); - getpeername(sock, (struct sockaddr*)&addr, &len); - if (!(pal = coreThread->GetPal(addr.sin_addr))) { + GError* error = nullptr; + GSocketAddress* remoteAddr = g_socket_get_remote_address(socket, &error); + if (!remoteAddr) { + LOG_WARN("Failed to get remote address: %s", + error ? error->message : "unknown"); + if (error) + g_error_free(error); + return; + } + + GInetSocketAddress* inetAddr = G_INET_SOCKET_ADDRESS(remoteAddr); + GInetAddress* gaddr = g_inet_socket_address_get_address(inetAddr); + char* addrStr = g_inet_address_to_string(gaddr); + in_addr ipv4 = inAddrFromString(addrStr); + g_free(addrStr); + g_object_unref(remoteAddr); + + if (!(pal = coreThread->GetPal(ipv4))) { return; } diff --git a/src/iptux-core/internal/TcpData.h b/src/iptux-core/internal/TcpData.h index 0d20c6805..96b144adf 100644 --- a/src/iptux-core/internal/TcpData.h +++ b/src/iptux-core/internal/TcpData.h @@ -12,6 +12,8 @@ #ifndef IPTUX_TCPDATA_H #define IPTUX_TCPDATA_H +#include + #include "iptux-core/CoreThread.h" #include "iptux-core/Models.h" #include "iptux-core/internal/ipmsg.h" @@ -23,7 +25,7 @@ class TcpData { TcpData(); ~TcpData(); - static void TcpDataEntry(CoreThread* coreThread, int sock); + static void TcpDataEntry(CoreThread* coreThread, GSocket* socket); private: void DispatchTcpData(); @@ -36,7 +38,8 @@ class TcpData { void RecvMsgPic(PalInfo* pal, const char* path); CoreThread* coreThread; - int sock; //数据交流套接口 + GSocket* socket; // GIO socket for the connection + int sock; // file descriptor (from socket, for legacy code) size_t size; //缓冲区已使用长度 char buf[MAX_SOCKLEN]; //缓冲区 }; diff --git a/src/iptux-core/internal/UdpDataService.cpp b/src/iptux-core/internal/UdpDataService.cpp index 50548a4ea..8720adcf6 100644 --- a/src/iptux-core/internal/UdpDataService.cpp +++ b/src/iptux-core/internal/UdpDataService.cpp @@ -1,5 +1,6 @@ #include "UdpDataService.h" +#include "gio/gio.h" #include "iptux-utils/output.h" #include "iptux-utils/utils.h" @@ -16,6 +17,20 @@ unique_ptr UdpDataService::process(in_addr ipv4, return process(ipv4, port, buf, size, true); } +unique_ptr UdpDataService::process(GSocketAddress* peer, + const char buf[], + size_t size) { + GInetSocketAddress* isa = G_INET_SOCKET_ADDRESS(peer); + guint16 port = g_inet_socket_address_get_port(isa); + GInetAddress* ia = g_inet_socket_address_get_address(isa); + char* ip = g_inet_address_to_string(ia); + + // TODO: too many conversions, optimize it + in_addr ipv4 = inAddrFromString(ip); + g_free(ip); + return process(ipv4, port, buf, size, true); +} + unique_ptr UdpDataService::process(in_addr ipv4, int port, const char buf[], diff --git a/src/iptux-core/internal/UdpDataService.h b/src/iptux-core/internal/UdpDataService.h index ccc199466..2dfdde3fb 100644 --- a/src/iptux-core/internal/UdpDataService.h +++ b/src/iptux-core/internal/UdpDataService.h @@ -1,6 +1,7 @@ #ifndef IPTUX_UDP_DATA_SERVICE_H #define IPTUX_UDP_DATA_SERVICE_H +#include "gio/gio.h" #include "iptux-core/CoreThread.h" #include "iptux-core/internal/UdpData.h" @@ -10,6 +11,10 @@ class UdpDataService { public: explicit UdpDataService(CoreThread& coreThread); + std::unique_ptr process(GSocketAddress* peer, + const char buf[], + size_t size); + std::unique_ptr process(in_addr ipv4, int port, const char buf[], diff --git a/src/iptux-core/internal/support.cpp b/src/iptux-core/internal/support.cpp index 18ae1ad1a..015962821 100644 --- a/src/iptux-core/internal/support.cpp +++ b/src/iptux-core/internal/support.cpp @@ -26,21 +26,6 @@ using namespace std; namespace iptux { -/** - * 让套接口支持广播. - * @param sock socket - */ -void socket_enable_broadcast(int sock) { - socklen_t len; - int optval; - - optval = 1; - len = sizeof(optval); - if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &optval, len) != 0) { - LOG_WARN("setsockopt for SO_BROADCAST failed: %s", strerror(errno)); - } -} - /** * 让套接口监听端口可重用. * @param sock socket @@ -63,12 +48,11 @@ void socket_enable_reuse(int sock) { } /** - * 获取系统主机的广播地址. + * Get system broadcast addresses. * @param sock socket - * @return 广播地址链表 - * @note 链表数据不是指针而是实际的IP + * @return list of broadcast addresses */ -vector get_sys_broadcast_addr(int sock) { +static vector get_sys_broadcast_addr_by_fd(int sock) { const uint8_t amount = 5; //支持5个IP地址 uint8_t count, sum; struct ifconf ifc; @@ -105,4 +89,8 @@ vector get_sys_broadcast_addr(int sock) { return res; } +vector get_sys_broadcast_addr(GSocket* sock) { + return get_sys_broadcast_addr_by_fd(g_socket_get_fd(sock)); +} + } // namespace iptux diff --git a/src/iptux-core/internal/support.h b/src/iptux-core/internal/support.h index 22f660260..c8a7d9c15 100644 --- a/src/iptux-core/internal/support.h +++ b/src/iptux-core/internal/support.h @@ -12,14 +12,14 @@ #ifndef IPTUX_SUPPORT_H #define IPTUX_SUPPORT_H +#include #include #include namespace iptux { -void socket_enable_broadcast(int sock); void socket_enable_reuse(int sock); -std::vector get_sys_broadcast_addr(int sock); +std::vector get_sys_broadcast_addr(GSocket* sock); } // namespace iptux diff --git a/src/iptux-core/internal/supportTest.cpp b/src/iptux-core/internal/supportTest.cpp index 276be36c3..1c5c5eeb6 100644 --- a/src/iptux-core/internal/supportTest.cpp +++ b/src/iptux-core/internal/supportTest.cpp @@ -1,15 +1,16 @@ #include "gtest/gtest.h" #include "support.h" -#include -#include using namespace iptux; using namespace std; TEST(Support, get_sys_broadcast_addr) { - int udpSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + GError* error = nullptr; + GSocket* udpSock = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, + G_SOCKET_PROTOCOL_UDP, &error); + ASSERT_EQ(error, nullptr); auto ips = get_sys_broadcast_addr(udpSock); ASSERT_GE(int(ips.size()), 2); - shutdown(udpSock, SHUT_RDWR); + g_object_unref(udpSock); } diff --git a/src/iptux-core/meson.build b/src/iptux-core/meson.build index 785b8651f..77f5225a5 100644 --- a/src/iptux-core/meson.build +++ b/src/iptux-core/meson.build @@ -1,4 +1,5 @@ glib_dep = dependency('glib-2.0', version: '>=2.35') +gio_dep = dependency('gio-2.0', version: '>=2.35') jsoncpp_dep = dependency('jsoncpp', version: '>=1.0') sigc_dep = dependency('sigc++-2.0') @@ -34,14 +35,14 @@ thread_dep = dependency('threads') if get_option('static-link') libiptux_core = static_library('iptux-core', core_sources, - dependencies: [glib_dep, jsoncpp_dep, sigc_dep, thread_dep], + dependencies: [glib_dep, gio_dep, jsoncpp_dep, sigc_dep, thread_dep], link_with: [libiptux_utils], include_directories: inc, ) else libiptux_core = shared_library('iptux-core', core_sources, - dependencies: [glib_dep, jsoncpp_dep, sigc_dep, thread_dep], + dependencies: [glib_dep, gio_dep, jsoncpp_dep, sigc_dep, thread_dep], link_with: [libiptux_utils], include_directories: inc, install: true, @@ -53,7 +54,7 @@ else description: 'Communicate and Share File in LAN', name: 'iptux-core', version: meson.project_version(), - requires: [jsoncpp_dep, glib_dep, sigc_dep, thread_dep], + requires: [jsoncpp_dep, glib_dep, gio_dep, sigc_dep, thread_dep], libraries: [libiptux_core], ) endif @@ -64,7 +65,7 @@ core_test_helper_sources = files([ ]) libiptux_core_test_helper = static_library('iptux-test-helper', core_test_helper_sources, - dependencies: [glib_dep, jsoncpp_dep, sigc_dep], + dependencies: [glib_dep, gio_dep, jsoncpp_dep, sigc_dep], link_with: [libiptux_core, libiptux_utils_test_helper], include_directories: inc ) @@ -84,7 +85,7 @@ core_test_sources = files([ ]) libiptux_core_test = executable('libiptux_core_test', core_test_sources, - dependencies: [glib_dep, jsoncpp_dep, sigc_dep, thread_dep], + dependencies: [glib_dep, gio_dep, jsoncpp_dep, sigc_dep, thread_dep], link_with: [libiptux_core, libgtest, libiptux_core_test_helper], include_directories: [inc, gtest_inc] ) diff --git a/src/iptux-utils/meson.build b/src/iptux-utils/meson.build index 7ac4c6bd2..12df8c270 100644 --- a/src/iptux-utils/meson.build +++ b/src/iptux-utils/meson.build @@ -1,4 +1,5 @@ glib_dep = dependency('glib-2.0', version: '>=2.35') +gio_dep = dependency('gio-2.0') thread_dep = dependency('threads') sources = files([ @@ -11,7 +12,7 @@ inc = include_directories('..', '../api') libiptux_utils = static_library('iptux-utils', sources, - dependencies: [glib_dep], + dependencies: [glib_dep, gio_dep], include_directories: inc, ) diff --git a/src/iptux-utils/output.h b/src/iptux-utils/output.h index bd8d33acf..b26f980af 100644 --- a/src/iptux-utils/output.h +++ b/src/iptux-utils/output.h @@ -26,6 +26,7 @@ namespace iptux { DoLog(__FILE__, __LINE__, __func__, G_LOG_LEVEL_CRITICAL, __VA_ARGS__) #define LOG_ERROR(...) \ DoLog(__FILE__, __LINE__, __func__, G_LOG_LEVEL_ERROR, __VA_ARGS__) +#define LOG_TRACE() LOG_DEBUG("called") /* 警告信息输出 */ #ifndef WARNING diff --git a/src/iptux-utils/utils.cpp b/src/iptux-utils/utils.cpp index 0e1dd7373..4bc112c7f 100644 --- a/src/iptux-utils/utils.cpp +++ b/src/iptux-utils/utils.cpp @@ -802,30 +802,31 @@ ssize_t read_ipmsg_dirfiles(int fd, void* buf, size_t count, size_t offset) { } /** - * 读取ipmsg文件头信息. - * 本函数的退出条件为: \n - * 1.缓冲区内必须要有数据; \n - * 2.文件头长度必须能够被获得; \n - * 3.文件头长度必须小于或等于缓冲区内已有数据长度; \n - * 4.读取数据出错(晕,这还值得怀疑吗?). \n - * @param fd 文件描述符 - * @param buf 缓冲区 - * @param count 缓冲区长度 - * @param offset 缓冲区无效数据偏移量 - * @return 成功读取的信息长度 + * Read file info from GSocket. + * @param sock GSocket + * @param buf buffer + * @param count buffer size + * @param offset existing data offset + * @return total data length read, or -1 on error */ -ssize_t read_ipmsg_fileinfo(int fd, void* buf, size_t count, size_t offset) { - ssize_t size; +ssize_t read_ipmsg_fileinfo(GSocket* sock, + void* buf, + size_t count, + size_t offset) { + gssize size; uint32_t headsize; - if (offset < count) // 注意不要写到缓冲区外了 + if (offset < count) ((char*)buf)[offset] = '\0'; while (!offset || !strchr((char*)buf, ':') || sscanf((char*)buf, "%" SCNx32, &headsize) != 1 || headsize > offset) { - mark: - if ((size = read(fd, (char*)buf + offset, count - offset)) == -1) { - if (errno == EINTR) - goto mark; + GError* error = nullptr; + size = g_socket_receive(sock, (char*)buf + offset, count - offset, nullptr, + &error); + if (size == -1) { + if (error) { + g_error_free(error); + } return -1; } else if (size == 0) return -1; diff --git a/src/iptux-utils/utils.h b/src/iptux-utils/utils.h index 9482df5a4..e6de7b9df 100644 --- a/src/iptux-utils/utils.h +++ b/src/iptux-utils/utils.h @@ -12,6 +12,7 @@ #ifndef IPTUX_UTILS_H #define IPTUX_UTILS_H +#include #include #include #include @@ -116,7 +117,10 @@ ssize_t xread(int fd, void* buf, size_t count); ssize_t read_ipmsg_prefix(int fd, void* buf, size_t count); ssize_t read_ipmsg_filedata(int fd, void* buf, size_t count, size_t offset); ssize_t read_ipmsg_dirfiles(int fd, void* buf, size_t count, size_t offset); -ssize_t read_ipmsg_fileinfo(int fd, void* buf, size_t count, size_t offset); +ssize_t read_ipmsg_fileinfo(GSocket* sock, + void* buf, + size_t count, + size_t offset); /** * @brief wrapper for g_utf8_make_valid diff --git a/src/iptux/Application.cpp b/src/iptux/Application.cpp index 3268f073f..5f7c14e50 100644 --- a/src/iptux/Application.cpp +++ b/src/iptux/Application.cpp @@ -4,7 +4,6 @@ #include #include -#include "iptux-core/Exception.h" #include "iptux-utils/output.h" #include "iptux-utils/utils.h" #include "iptux/AboutDialog.h" @@ -48,7 +47,7 @@ void onWhatsNew() { iptux_open_url("https://github.com/iptux-src/iptux/blob/master/NEWS"); } -void iptux_init(LogSystem* logSystem) { +void iptux_init(LogSystemPtr logSystem) { signal(SIGPIPE, SIG_IGN); logSystem->systemLog("%s", _("Loading the process successfully!")); } @@ -63,6 +62,12 @@ void init_theme(Application* app) { } } // namespace +static void appDestroy(gpointer user_data) { + LOG_TRACE(); + Application* app = (Application*)user_data; + delete app; +} + Application::Application(shared_ptr config) : config(config), data(nullptr), window(nullptr), shareFile(nullptr) { auto application_id = @@ -73,9 +78,10 @@ Application::Application(shared_ptr config) logSystem = nullptr; app = gtk_application_new(application_id.c_str(), G_APPLICATION_FLAGS_NONE); + g_object_set_data_full(G_OBJECT(app), "application", this, appDestroy); g_signal_connect_swapped(app, "startup", G_CALLBACK(onStartup), this); g_signal_connect_swapped(app, "activate", G_CALLBACK(onActivate), this); - + this->data = make_shared(this->config); #if SYSTEM_DARWIN notificationService = new TerminalNotifierNoticationService(); #else @@ -91,15 +97,10 @@ Application::Application(shared_ptr config) } Application::~Application() { - g_object_unref(app); if (menuBuilder) { g_object_unref(menuBuilder); } transModelDelete(transModel); - if (logSystem) { - delete logSystem; - } - delete window; delete notificationService; if (this->process_events_source_id) g_source_remove(this->process_events_source_id); @@ -118,13 +119,12 @@ void Application::activate() { } void Application::onStartup(Application& self) { - self.data = make_shared(self.config); + self.logSystem = std::make_shared(self.data); self.cthrd = make_shared(&self, self.data); init_theme(&self); iptux_register_resource(); self.menuBuilder = gtk_builder_new_from_resource(IPTUX_RESOURCE "gtk/menus.ui"); - self.logSystem = new LogSystem(self.data); if (self.enable_app_indicator_) { self.app_indicator = make_shared(&self); } @@ -201,14 +201,21 @@ void Application::onActivate(Application& self) { if (!self.cthrd->start()) { enum CoreThreadErr err = self.cthrd->getLastErr(); - pop_warning(self.window->getWindow(), - _("Start core thread failed: [%d] %s"), err, + if (self.test_mode) { + LOG_ERROR("Start core thread failed: [%d] %s", err, coreThreadErrToStr(err)); - exit(1); + } else { + pop_warning(self.window->getWindow(), + _("Start core thread failed: [%d] %s"), err, + coreThreadErrToStr(err)); + } + g_application_quit(G_APPLICATION(self.app)); + return; } iptux_init(self.logSystem); self.process_events_source_id = g_idle_add(G_SOURCE_FUNC(Application::ProcessEvents), &self); + self.activated = true; } void Application::onQuit(void*, void*, Application& self) { diff --git a/src/iptux/Application.h b/src/iptux/Application.h index f4bf59d28..e295ade5f 100644 --- a/src/iptux/Application.h +++ b/src/iptux/Application.h @@ -32,7 +32,7 @@ class Application { TransModel* getTransModel() { return transModel; } MainWindow* getMainWindow() { return window; } GtkBuilder* getMenuBuilder() { return menuBuilder; } - LogSystem* getLogSystem() { return logSystem; } + LogSystemPtr getLogSystem() { return logSystem; } std::shared_ptr getProgramData() { return data; } std::shared_ptr getCoreThread() { return cthrd; } bool use_header_bar() { return use_header_bar_; } @@ -43,6 +43,9 @@ class Application { GtkWidget* getPreferenceDialog() { return preference_dialog_; } void setPreferenceDialog(GtkWidget* dialog) { preference_dialog_ = dialog; } + void setTestMode(bool test) { test_mode = test; } + bool isActivated() const { return activated; } + private: std::shared_ptr config; std::shared_ptr data; @@ -58,12 +61,14 @@ class Application { MainWindow* window = 0; ShareFile* shareFile = 0; TransWindow* transWindow = 0; - LogSystem* logSystem = 0; + LogSystemPtr logSystem; NotificationService* notificationService = 0; GMenuModel* menu_ = 0; GtkWidget* preference_dialog_ = 0; bool use_header_bar_ = false; bool started{false}; + bool test_mode{false}; + bool activated{false}; guint process_events_source_id{0}; public: diff --git a/src/iptux/LogSystem.h b/src/iptux/LogSystem.h index 888faac4d..230000101 100644 --- a/src/iptux/LogSystem.h +++ b/src/iptux/LogSystem.h @@ -42,6 +42,8 @@ class LogSystem { void InitSublayer(); }; +using LogSystemPtr = std::shared_ptr; + } // namespace iptux #endif diff --git a/src/iptux/LogSystemTest.cpp b/src/iptux/LogSystemTest.cpp index c4ddbcbd5..33d03cfef 100644 --- a/src/iptux/LogSystemTest.cpp +++ b/src/iptux/LogSystemTest.cpp @@ -9,6 +9,5 @@ using namespace iptux; TEST(LogSystem, Constructor) { auto config = newTestIptuxConfig(); auto core = make_shared(config); - LogSystem* logSystem = new LogSystem(core); - delete logSystem; + auto logSystem = make_shared(core); } diff --git a/src/iptux/MainWindow.cpp b/src/iptux/MainWindow.cpp index f4f36fc43..08c68fa51 100644 --- a/src/iptux/MainWindow.cpp +++ b/src/iptux/MainWindow.cpp @@ -103,6 +103,11 @@ void MainWindow::Show() { gtk_window_present(GTK_WINDOW(window)); } +static void mainWindowDestroy(gpointer data) { + MainWindow* self = (MainWindow*)data; + delete self; +} + /** * 创建程序主窗口入口. */ @@ -113,6 +118,8 @@ void MainWindow::CreateWindow() { /* 创建主窗口 */ window = CreateMainWindow(); + g_object_set_data_full(G_OBJECT(window), "main-window", (gpointer)this, + mainWindowDestroy); CreateActions(); CreateTitle(); gtk_container_add(GTK_CONTAINER(window), CreateAllArea()); diff --git a/src/iptux/TestHelper.cpp b/src/iptux/TestHelper.cpp index 4e6f195f0..edc2059f6 100644 --- a/src/iptux/TestHelper.cpp +++ b/src/iptux/TestHelper.cpp @@ -1,8 +1,10 @@ #include "config.h" #include "Application.h" +#include "UiCoreThread.h" #include "gtest/gtest.h" #include "iptux-core/TestHelper.h" +#include "iptux-utils/output.h" #include "iptux/TestHelper.h" namespace iptux { @@ -11,19 +13,23 @@ Application* CreateApplication() { gtk_init(nullptr, nullptr); auto config = newTestIptuxConfig(); Application* app = new Application(config); + app->setTestMode(true); app->set_enable_app_indicator(false); app->startup(); // g_application_register(G_APPLICATION(app->getApp()), nullptr, nullptr); // auto i = g_application_get_is_registered(G_APPLICATION(app->getApp())); // EXPECT_TRUE(i); - app->set_enable_app_indicator(false); - app->startup(); + app->getCoreThread()->setIgnoreTcpBindFailed(true); app->activate(); + if (app->isActivated() == false) { + LOG_ERROR("Application activate failed"); + DestroyApplication(app); + return nullptr; + } return app; } void DestroyApplication(Application* app) { g_application_quit(G_APPLICATION(app->getApp())); - delete app; } } // namespace iptux diff --git a/src/iptux/UiCoreThread.h b/src/iptux/UiCoreThread.h index 9409d7454..9d8f0ddd9 100644 --- a/src/iptux/UiCoreThread.h +++ b/src/iptux/UiCoreThread.h @@ -28,8 +28,6 @@ namespace iptux { -class LogSystem; - /** * @note 请保证插入或更新某成员时,底层优先于UI;删除某成员时,UI优先于底层, * 否则你会把所有事情都搞砸. \n @@ -61,7 +59,7 @@ class UiCoreThread : public CoreThread { void PushItemToEnclosureList(FileInfo* file); void PopItemFromEnclosureList(FileInfo* file); - LogSystem* getLogSystem() { return logSystem; } + LogSystemPtr getLogSystem() { return logSystem; } GtkTextTagTable* tag_table() { return tag_table_; } @@ -88,7 +86,7 @@ class UiCoreThread : public CoreThread { private: std::shared_ptr programData; - LogSystem* logSystem; + LogSystemPtr logSystem; std::queue messages; GSList *groupInfos, *sgmlist, *grplist, *brdlist; // 群组链表(成员不能被删除) diff --git a/src/iptux/UiCoreThreadTest.cpp b/src/iptux/UiCoreThreadTest.cpp index 3016407d8..be53a9259 100644 --- a/src/iptux/UiCoreThreadTest.cpp +++ b/src/iptux/UiCoreThreadTest.cpp @@ -9,9 +9,13 @@ using namespace iptux; TEST(UiCoreThread, Constructor) { Application* app = CreateApplication(); + if (!app) { + GTEST_SKIP() << "Application creation failed"; + } auto core = make_shared(app->getConfig()); core->sign = "abc"; UiCoreThread* thread = new UiCoreThread(app, core); + thread->setIgnoreTcpBindFailed(true); thread->start(); thread->stop(); delete thread; diff --git a/src/iptux/UiModels.cpp b/src/iptux/UiModels.cpp index 3b308bca4..ec243be59 100644 --- a/src/iptux/UiModels.cpp +++ b/src/iptux/UiModels.cpp @@ -403,7 +403,7 @@ string GroupInfo::user_name() const { return ""; } -GroupInfo::GroupInfo(PPalInfo pal, CPPalInfo me, LogSystem* logSystem) +GroupInfo::GroupInfo(PPalInfo pal, CPPalInfo me, LogSystemPtr logSystem) : grpid(0), buffer(NULL), dialogBase(NULL), @@ -420,7 +420,7 @@ GroupInfo::GroupInfo(iptux::GroupBelongType t, const vector& pals, CPPalInfo me, const string& name, - LogSystem* logSystem) + LogSystemPtr logSystem) : grpid(0), buffer(NULL), dialogBase(NULL), diff --git a/src/iptux/UiModels.h b/src/iptux/UiModels.h index c2fd7ab5f..d79939608 100644 --- a/src/iptux/UiModels.h +++ b/src/iptux/UiModels.h @@ -58,12 +58,12 @@ const char* GtkSortTypeToStr(GtkSortType t); class GroupInfo { public: - GroupInfo(PPalInfo pal, CPPalInfo me, LogSystem* logSystem); + GroupInfo(PPalInfo pal, CPPalInfo me, LogSystemPtr logSystem); GroupInfo(GroupBelongType type, const std::vector& pals, CPPalInfo me, const std::string& name, - LogSystem* logSystem); + LogSystemPtr logSystem); ~GroupInfo(); const std::vector& getMembers() const { return members; } @@ -132,7 +132,7 @@ class GroupInfo { CPPalInfo me; std::vector members; GroupBelongType type; ///< 群组类型 - LogSystem* logSystem; + LogSystemPtr logSystem; int allMsgCount = 0; /* all received message count */ int readMsgCount = 0; /* already read message count */ diff --git a/src/main/iptux.cpp b/src/main/iptux.cpp index 07138209e..729af01d7 100644 --- a/src/main/iptux.cpp +++ b/src/main/iptux.cpp @@ -137,6 +137,8 @@ static void dealLog(const IptuxConfig& config) { } int main(int argc, char** argv) { + int ret; + installCrashHandler(); setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, __LOCALE_PATH); @@ -162,6 +164,10 @@ int main(int argc, char** argv) { if (bindIp) { config->SetString("bind_ip", bindIp); } - Application app(config); - return app.run(argc, argv); + + Application* app = new Application(config); + GtkApplication* gtkApp = GTK_APPLICATION(app->getApp()); + ret = g_application_run(G_APPLICATION(gtkApp), argc, argv); + g_object_unref(gtkApp); + return ret; }