diff --git a/src/common/settings.h b/src/common/settings.h index 7786f0c70..c42581c93 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -661,10 +661,12 @@ struct Values { // Network Setting network_interface{linkage, std::string(), "network_interface", Category::Network}; + Setting lobby_api_url{linkage, "https://api.ynet-fun.xyz", "lobby_api_url", + Category::Network}; // WebService Setting enable_telemetry{linkage, false, "enable_telemetry", Category::WebService}; - Setting web_api_url{linkage, "http://api.ynet-fun.xyz", "web_api_url", + Setting web_api_url{linkage, "https://api.ynet-fun.xyz", "web_api_url", Category::WebService}; Setting citron_username{linkage, std::string(), "citron_username", Category::WebService}; diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index cde538cbd..a6ec2f1d7 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -547,15 +547,32 @@ std::pair BSD::SocketImpl(Domain domain, Type type, Protocol protoco LOG_INFO(Service, "New socket fd={} domain={} type={} protocol={} proxy={}", fd, domain, type, protocol, using_proxy); + // Store socket type information for pooling + descriptor.domain = Translate(domain); + descriptor.type = Translate(type); + descriptor.protocol = Translate(protocol); + descriptor.is_connection_based = IsConnectionBased(type); + + // Try to reuse a socket from the pool if using proxy if (using_proxy) { - descriptor.socket = std::make_shared(room_network); + SocketPoolKey key{descriptor.domain, descriptor.type, descriptor.protocol}; + std::lock_guard lock(socket_pool_mutex); + + auto it = socket_pool.find(key); + if (it != socket_pool.end() && !it->second.empty()) { + descriptor.socket = it->second.back(); + it->second.pop_back(); + LOG_DEBUG(Service, "Reused socket from pool for fd={}", fd); + } else { + descriptor.socket = std::make_shared(room_network); + descriptor.socket->Initialize(descriptor.domain, descriptor.type, descriptor.protocol); + LOG_DEBUG(Service, "Created new ProxySocket for fd={}", fd); + } } else { descriptor.socket = std::make_shared(); + descriptor.socket->Initialize(descriptor.domain, descriptor.type, descriptor.protocol); } - descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(protocol)); - descriptor.is_connection_based = IsConnectionBased(type); - return {fd, Errno::SUCCESS}; } @@ -966,13 +983,34 @@ Errno BSD::CloseImpl(s32 fd) { return Errno::BADF; } - const Errno bsd_errno = Translate(file_descriptors[fd]->socket->Close()); + auto& descriptor = file_descriptors[fd]; + const Errno bsd_errno = Translate(descriptor->socket->Close()); if (bsd_errno != Errno::SUCCESS) { return bsd_errno; } LOG_INFO(Service, "Close socket fd={}", fd); + // Try to return ProxySocket to the pool for reuse + auto proxy_socket = std::dynamic_pointer_cast(descriptor->socket); + auto room_member = room_network.GetRoomMember().lock(); + + if (proxy_socket && room_member && room_member->IsConnected()) { + // Socket is still valid, add to pool + std::lock_guard lock(socket_pool_mutex); + + SocketPoolKey key{descriptor->domain, descriptor->type, descriptor->protocol}; + + // Limit pool size to avoid memory bloat (max 8 sockets per type) + constexpr size_t MAX_POOL_SIZE = 8; + if (socket_pool[key].size() < MAX_POOL_SIZE) { + socket_pool[key].push_back(descriptor->socket); + LOG_DEBUG(Service, "Returned socket fd={} to pool", fd); + } else { + LOG_DEBUG(Service, "Socket pool full, destroying socket fd={}", fd); + } + } + file_descriptors[fd].reset(); return bsd_errno; } diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index c58579644..d65ea820c 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -46,6 +46,9 @@ private: std::shared_ptr socket; s32 flags = 0; bool is_connection_based = false; + Network::Domain domain = Network::Domain::INET; + Network::Type type = Network::Type::DGRAM; + Network::Protocol protocol = Network::Protocol::UDP; }; struct PollWork { @@ -209,6 +212,20 @@ private: // Callback identifier for the OnProxyPacketReceived event. Network::RoomMember::CallbackHandle proxy_packet_received; + /// Socket pool to cache and reuse ProxySocket instances + struct SocketPoolKey { + Network::Domain domain; + Network::Type type; + Network::Protocol protocol; + + bool operator<(const SocketPoolKey& other) const { + return std::tie(domain, type, protocol) < + std::tie(other.domain, other.type, other.protocol); + } + }; + std::map>> socket_pool; + std::mutex socket_pool_mutex; + protected: virtual std::unique_lock LockService() override; }; diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 117d8b768..8af60a886 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -928,7 +929,13 @@ bool Socket::IsOpened() const { } void Socket::HandleProxyPacket(const ProxyPacket& packet) { - LOG_WARNING(Network, "ProxyPacket received, but not in Proxy mode!"); + LOG_WARNING(Network, + "ProxyPacket received on regular socket (not ProxySocket). " + "This may indicate socket type mismatch. " + "Packet from {}:{} to {}:{}, protocol={}, reliable={}", + packet.local_endpoint.ip[0], packet.local_endpoint.portno, + packet.remote_endpoint.ip[0], packet.remote_endpoint.portno, + static_cast(packet.protocol), packet.reliable); } } // namespace Network diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp index 4c176bec4..b43de3a2c 100644 --- a/src/core/internal_network/socket_proxy.cpp +++ b/src/core/internal_network/socket_proxy.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -30,11 +31,15 @@ ProxySocket::~ProxySocket() { void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) { if (protocol != packet.protocol || local_endpoint.portno != packet.remote_endpoint.portno || closed) { + stats.packets_dropped++; + LOG_DEBUG(Network, "Dropped packet: protocol mismatch or closed socket. Stats: sent={}, recv={}, dropped={}", + stats.packets_sent, stats.packets_received, stats.packets_dropped); return; } if (!broadcast && packet.broadcast) { - LOG_INFO(Network, "Received broadcast packet, but not configured for broadcast mode"); + stats.packets_dropped++; + LOG_DEBUG(Network, "Dropped broadcast packet on non-broadcast socket"); return; } @@ -43,6 +48,15 @@ void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) { std::lock_guard guard(packets_mutex); received_packets.push(decompressed); + stats.packets_received++; + stats.bytes_received += decompressed.data.size(); + + // Log statistics periodically (every 100 packets) + if (stats.packets_received % 100 == 0) { + LOG_DEBUG(Network, "ProxySocket stats: sent={} ({} bytes), recv={} ({} bytes), dropped={}", + stats.packets_sent, stats.bytes_sent, + stats.packets_received, stats.bytes_received, stats.packets_dropped); + } } template @@ -189,10 +203,20 @@ std::pair ProxySocket::Send(std::span message, int flags) void ProxySocket::SendPacket(ProxyPacket& packet) { if (auto room_member = room_network.GetRoomMember().lock()) { if (room_member->IsConnected()) { + const size_t original_size = packet.data.size(); packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(), packet.data.size()); room_member->SendProxyPacket(packet); + + stats.packets_sent++; + stats.bytes_sent += original_size; + } else { + LOG_WARNING(Network, "Cannot send packet: not connected to room. Total packets dropped: {}", + ++stats.packets_dropped); } + } else { + LOG_ERROR(Network, "Cannot send packet: room member unavailable"); + stats.packets_dropped++; } } @@ -236,6 +260,14 @@ std::pair ProxySocket::SendTo(u32 flags, std::span message packet.data.clear(); std::copy(message.begin(), message.end(), std::back_inserter(packet.data)); + // Determine if packet should use unreliable delivery for better latency + // Use unreliable delivery for: + // 1. Small, frequent game data packets (< 1200 bytes for typical MTU) + // 2. UDP protocol packets (most game traffic) + // 3. Non-broadcast packets (broadcast should be reliable for coordination) + const bool is_game_data = protocol == Protocol::UDP && message.size() < 1200 && !packet.broadcast; + packet.reliable = !is_game_data; + SendPacket(packet); return {static_cast(message.size()), Errno::SUCCESS}; diff --git a/src/core/internal_network/socket_proxy.h b/src/core/internal_network/socket_proxy.h index 70500cf4a..56efc557b 100644 --- a/src/core/internal_network/socket_proxy.h +++ b/src/core/internal_network/socket_proxy.h @@ -94,6 +94,15 @@ private: std::mutex packets_mutex; RoomNetwork& room_network; + + // Packet statistics for monitoring + struct PacketStatistics { + u64 packets_sent = 0; + u64 packets_received = 0; + u64 packets_dropped = 0; + u64 bytes_sent = 0; + u64 bytes_received = 0; + } stats; }; } // namespace Network diff --git a/src/network/room.cpp b/src/network/room.cpp index 06d1c9509..ac392cb72 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -831,10 +832,14 @@ void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) { bool broadcast; in_packet.Read(broadcast); // Broadcast + bool reliable; + in_packet.Read(reliable); // Reliability flag + Packet out_packet; out_packet.Append(event->packet->data, event->packet->dataLength); + const u32 enet_flags = reliable ? ENET_PACKET_FLAG_RELIABLE : ENET_PACKET_FLAG_UNSEQUENCED; ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(), - ENET_PACKET_FLAG_RELIABLE); + enet_flags); const auto& destination_address = remote_ip; if (broadcast) { // Send the data to everyone except the sender @@ -885,10 +890,14 @@ void Room::RoomImpl::HandleLdnPacket(const ENetEvent* event) { bool broadcast; in_packet.Read(broadcast); // Broadcast + bool reliable; + in_packet.Read(reliable); // Reliability flag + Packet out_packet; out_packet.Append(event->packet->data, event->packet->dataLength); + const u32 enet_flags = reliable ? ENET_PACKET_FLAG_RELIABLE : ENET_PACKET_FLAG_UNSEQUENCED; ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(), - ENET_PACKET_FLAG_RELIABLE); + enet_flags); const auto& destination_address = remote_ip; if (broadcast) { // Send the data to everyone except the sender diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index a6845273c..7519fb2d7 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -44,8 +45,15 @@ public: std::mutex network_mutex; ///< Mutex that controls access to the `client` variable. /// Thread that receives and dispatches network packets std::unique_ptr loop_thread; + + /// Structure to hold a packet and its reliability flag + struct PacketWithReliability { + Packet packet; + bool reliable; + }; + std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable. - std::list send_list; ///< A list that stores all packets to send the async + std::list send_list; ///< A list that stores all packets to send the async template using CallbackSet = std::set>; @@ -73,10 +81,11 @@ public: void StartLoop(); /** - * Sends data to the room. It will be send on channel 0 with flag RELIABLE + * Sends data to the room. It will be send on channel 0 with specified reliability * @param packet The data to send + * @param reliable Whether to use reliable delivery (true) or unreliable/unsequenced (false) */ - void Send(Packet&& packet); + void Send(Packet&& packet, bool reliable = true); /** * Sends a request to the server, asking for permission to join a room with the specified @@ -257,14 +266,17 @@ void RoomMember::RoomMemberImpl::MemberLoop() { break; } } - std::list packets; + std::list packets; { std::lock_guard send_lock(send_list_mutex); packets.swap(send_list); } - for (const auto& packet : packets) { - ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(), - ENET_PACKET_FLAG_RELIABLE); + for (const auto& packet_data : packets) { + const u32 enet_flags = packet_data.reliable ? ENET_PACKET_FLAG_RELIABLE + : ENET_PACKET_FLAG_UNSEQUENCED; + ENetPacket* enetPacket = enet_packet_create(packet_data.packet.GetData(), + packet_data.packet.GetDataSize(), + enet_flags); enet_peer_send(server, 0, enetPacket); } enet_host_flush(client); @@ -276,9 +288,9 @@ void RoomMember::RoomMemberImpl::StartLoop() { loop_thread = std::make_unique(&RoomMember::RoomMemberImpl::MemberLoop, this); } -void RoomMember::RoomMemberImpl::Send(Packet&& packet) { +void RoomMember::RoomMemberImpl::Send(Packet&& packet, bool reliable) { std::lock_guard lock(send_list_mutex); - send_list.push_back(std::move(packet)); + send_list.push_back({std::move(packet), reliable}); } void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname_, @@ -377,6 +389,7 @@ void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) { proxy_packet.protocol = static_cast(protocol_type); packet.Read(proxy_packet.broadcast); + packet.Read(proxy_packet.reliable); packet.Read(proxy_packet.data); Invoke(proxy_packet); @@ -397,6 +410,7 @@ void RoomMember::RoomMemberImpl::HandleLdnPackets(const ENetEvent* event) { packet.Read(ldn_packet.local_ip); packet.Read(ldn_packet.remote_ip); packet.Read(ldn_packet.broadcast); + packet.Read(ldn_packet.reliable); packet.Read(ldn_packet.data); @@ -638,9 +652,10 @@ void RoomMember::SendProxyPacket(const ProxyPacket& proxy_packet) { packet.Write(static_cast(proxy_packet.protocol)); packet.Write(proxy_packet.broadcast); + packet.Write(proxy_packet.reliable); packet.Write(proxy_packet.data); - room_member_impl->Send(std::move(packet)); + room_member_impl->Send(std::move(packet), proxy_packet.reliable); } void RoomMember::SendLdnPacket(const LDNPacket& ldn_packet) { @@ -652,10 +667,11 @@ void RoomMember::SendLdnPacket(const LDNPacket& ldn_packet) { packet.Write(ldn_packet.local_ip); packet.Write(ldn_packet.remote_ip); packet.Write(ldn_packet.broadcast); + packet.Write(ldn_packet.reliable); packet.Write(ldn_packet.data); - room_member_impl->Send(std::move(packet)); + room_member_impl->Send(std::move(packet), ldn_packet.reliable); } void RoomMember::SendChatMessage(const std::string& message) { diff --git a/src/network/room_member.h b/src/network/room_member.h index 37e9ea16a..27036aa66 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -31,6 +32,7 @@ struct LDNPacket { IPv4Address local_ip; IPv4Address remote_ip; bool broadcast; + bool reliable = true; // Control packets use reliable delivery by default std::vector data; }; @@ -40,6 +42,7 @@ struct ProxyPacket { SockAddrIn remote_endpoint; Protocol protocol; bool broadcast; + bool reliable = true; // Use reliable delivery by default for compatibility std::vector data; };