Core: Add UNIX domain socket support

> [!NOTE]
>
> Later versions of Windows has support for `AF_UNIX`, so it could be
> added.
This commit is contained in:
Stuart Carnie
2025-06-25 07:01:29 +10:00
parent a078895ad2
commit 7227fdd805
39 changed files with 1791 additions and 529 deletions

View File

@@ -121,7 +121,10 @@ void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks,
}
void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, bool p_ignore_error_breaks, const Vector<String> &p_breakpoints, void (*p_allow_focus_steal_fn)()) {
register_uri_handler("tcp://", RemoteDebuggerPeerTCP::create); // TCP is the default protocol. Platforms/modules can add more.
register_uri_handler("tcp://", RemoteDebuggerPeerTCP::create_tcp); // TCP is the default protocol. Platforms/modules can add more.
#ifdef UNIX_ENABLED
register_uri_handler("unix://", RemoteDebuggerPeerTCP::create_unix);
#endif
if (p_uri.is_empty()) {
return;
}
@@ -132,10 +135,10 @@ void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, bo
OS::get_singleton()->initialize_debugging();
} else if (p_uri.contains("://")) {
const String proto = p_uri.substr(0, p_uri.find("://") + 3);
if (!protocols.has(proto)) {
return;
}
RemoteDebuggerPeer *peer = protocols[proto](p_uri);
CreatePeerFunc *create_fn = protocols.getptr(proto);
ERR_FAIL_NULL_MSG(create_fn, vformat("Invalid protocol: %s.", proto));
RemoteDebuggerPeer *peer = (*create_fn)(p_uri);
if (!peer) {
return;
}

View File

@@ -76,18 +76,19 @@ void RemoteDebuggerPeerTCP::close() {
in_buf.clear();
}
RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP(Ref<StreamPeerTCP> p_tcp) {
RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP() {
// This means remote debugger takes 16 MiB just because it exists...
in_buf.resize((8 << 20) + 4); // 8 MiB should be way more than enough (need 4 extra bytes for encoding packet size).
out_buf.resize(8 << 20); // 8 MiB should be way more than enough
tcp_client = p_tcp;
if (tcp_client.is_valid()) { // Attaching to an already connected stream.
connected = true;
running = true;
thread.start(_thread_func, this);
} else {
tcp_client.instantiate();
}
}
RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP(Ref<StreamPeerSocket> p_stream) :
RemoteDebuggerPeerTCP() {
DEV_ASSERT(p_stream.is_valid());
tcp_client = p_stream;
connected = true;
running = true;
thread.start(_thread_func, this);
}
RemoteDebuggerPeerTCP::~RemoteDebuggerPeerTCP() {
@@ -154,22 +155,10 @@ void RemoteDebuggerPeerTCP::_read_in() {
}
}
Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_port) {
IPAddress ip;
if (p_host.is_valid_ip_address()) {
ip = p_host;
} else {
ip = IP::get_singleton()->resolve_hostname(p_host);
}
int port = p_port;
Error RemoteDebuggerPeerTCP::_try_connect(Ref<StreamPeerSocket> tcp_client) {
const int tries = 6;
const int waits[tries] = { 1, 10, 100, 1000, 1000, 1000 };
Error err = tcp_client->connect_to_host(ip, port);
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Remote Debugger: Unable to connect to host '%s:%d'.", p_host, port));
for (int i = 0; i < tries; i++) {
tcp_client->poll();
if (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED) {
@@ -186,9 +175,6 @@ Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_po
ERR_PRINT(vformat("Remote Debugger: Unable to connect. Status: %s.", String::num_int64(tcp_client->get_status())));
return FAILED;
}
connected = true;
running = true;
thread.start(_thread_func, this);
return OK;
}
@@ -222,7 +208,7 @@ void RemoteDebuggerPeerTCP::_poll() {
}
}
RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create(const String &p_uri) {
RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create_tcp(const String &p_uri) {
ERR_FAIL_COND_V(!p_uri.begins_with("tcp://"), nullptr);
String debug_host = p_uri.replace("tcp://", "");
@@ -234,13 +220,30 @@ RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create(const String &p_uri) {
debug_host = debug_host.substr(0, sep_pos);
}
RemoteDebuggerPeerTCP *peer = memnew(RemoteDebuggerPeerTCP);
Error err = peer->connect_to_host(debug_host, debug_port);
if (err != OK) {
memdelete(peer);
return nullptr;
IPAddress ip;
if (debug_host.is_valid_ip_address()) {
ip = debug_host;
} else {
ip = IP::get_singleton()->resolve_hostname(debug_host);
}
return peer;
Ref<StreamPeerTCP> stream;
stream.instantiate();
ERR_FAIL_COND_V_MSG(stream->connect_to_host(ip, debug_port) != OK, nullptr, vformat("Remote Debugger: Unable to connect to host '%s:%d'.", debug_host, debug_port));
ERR_FAIL_COND_V(_try_connect(stream), nullptr);
return memnew(RemoteDebuggerPeerTCP(stream));
}
RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create_unix(const String &p_uri) {
ERR_FAIL_COND_V(!p_uri.begins_with("unix://"), nullptr);
String debug_path = p_uri.replace("unix://", "");
Ref<StreamPeerUDS> stream;
stream.instantiate();
Error err = stream->connect_to_host(debug_path);
ERR_FAIL_COND_V_MSG(err != OK && err != ERR_BUSY, nullptr, vformat("Remote Debugger: Unable to connect to socket path '%s'.", debug_path));
ERR_FAIL_COND_V(_try_connect(stream), nullptr);
return memnew(RemoteDebuggerPeerTCP(stream));
}
RemoteDebuggerPeer::RemoteDebuggerPeer() {

View File

@@ -31,6 +31,7 @@
#pragma once
#include "core/io/stream_peer_tcp.h"
#include "core/io/stream_peer_uds.h"
#include "core/object/ref_counted.h"
#include "core/os/mutex.h"
#include "core/os/thread.h"
@@ -59,7 +60,7 @@ class RemoteDebuggerPeerTCP : public RemoteDebuggerPeer {
GDSOFTCLASS(RemoteDebuggerPeerTCP, RemoteDebuggerPeer);
private:
Ref<StreamPeerTCP> tcp_client;
Ref<StreamPeerSocket> tcp_client;
Mutex mutex;
Thread thread;
List<Array> in_queue;
@@ -78,11 +79,11 @@ private:
void _poll();
void _write_out();
void _read_in();
static Error _try_connect(Ref<StreamPeerSocket> p_stream);
public:
static RemoteDebuggerPeer *create(const String &p_uri);
Error connect_to_host(const String &p_host, uint16_t p_port);
static RemoteDebuggerPeer *create_tcp(const String &p_uri);
static RemoteDebuggerPeer *create_unix(const String &p_uri);
bool is_peer_connected() override;
int get_max_message_size() const override;
@@ -92,6 +93,7 @@ public:
void poll() override;
void close() override;
RemoteDebuggerPeerTCP(Ref<StreamPeerTCP> p_stream = Ref<StreamPeerTCP>());
RemoteDebuggerPeerTCP(Ref<StreamPeerSocket> p_stream);
RemoteDebuggerPeerTCP();
~RemoteDebuggerPeerTCP();
};

View File

@@ -54,21 +54,61 @@ public:
TYPE_UDP,
};
virtual Error open(Type p_type, IP::Type &ip_type) = 0;
enum class Family {
NONE,
INET,
UNIX,
};
class Address {
Family _family = Family::NONE;
CharString _path;
IPAddress _ip;
uint16_t _port = 0;
public:
_FORCE_INLINE_ Family get_family() const { return _family; }
_FORCE_INLINE_ bool is_inet() const { return _family == Family::INET; }
_FORCE_INLINE_ bool is_unix() const { return _family == Family::UNIX; }
_FORCE_INLINE_ bool is_valid() const { return is_inet() || is_unix(); }
_FORCE_INLINE_ const IPAddress &ip() const { return _ip; }
_FORCE_INLINE_ const uint16_t &port() const { return _port; }
_FORCE_INLINE_ const CharString &get_path() const { return _path; }
Address() {}
Address(const IPAddress &p_addr, uint16_t p_port) :
_family(Family::INET) {
_ip = p_addr;
_port = p_port;
}
Address(const String &p_path) :
_family(Family::UNIX), _path(p_path.utf8()) {
}
Address(const CharString &p_path) :
_family(Family::UNIX), _path(p_path) {
}
};
virtual Error open(Family p_family, Type p_type, IP::Type &r_ip_type) = 0;
virtual void close() = 0;
virtual Error bind(IPAddress p_addr, uint16_t p_port) = 0;
virtual Error bind(Address p_addr) = 0;
virtual Error listen(int p_max_pending) = 0;
virtual Error connect_to_host(IPAddress p_addr, uint16_t p_port) = 0;
virtual Error connect_to_host(Address p_addr) = 0;
virtual Error poll(PollType p_type, int timeout) const = 0;
virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) = 0;
virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) = 0;
virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) = 0;
virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) = 0;
virtual Ref<NetSocket> accept(IPAddress &r_ip, uint16_t &r_port) = 0;
virtual Ref<NetSocket> accept(Address &r_addr) = 0;
virtual bool is_open() const = 0;
virtual int get_available_bytes() const = 0;
virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const = 0;
virtual Error get_socket_address(Address *r_addr) const = 0;
virtual Error set_broadcasting_enabled(bool p_enabled) = 0; // Returns OK if the socket option has been set successfully.
virtual void set_blocking_enabled(bool p_enabled) = 0;

View File

@@ -52,7 +52,7 @@ Error PacketPeerUDP::join_multicast_group(IPAddress p_multi_address, const Strin
if (!_sock->is_open()) {
IP::Type ip_type = p_multi_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
Error err = _sock->open(NetSocket::TYPE_UDP, ip_type);
Error err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type);
ERR_FAIL_COND_V(err != OK, err);
_sock->set_blocking_enabled(false);
_sock->set_broadcasting_enabled(broadcast);
@@ -141,7 +141,7 @@ Error PacketPeerUDP::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
if (!_sock->is_open()) {
IP::Type ip_type = peer_addr.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
err = _sock->open(NetSocket::TYPE_UDP, ip_type);
err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type);
ERR_FAIL_COND_V(err != OK, err);
_sock->set_blocking_enabled(false);
_sock->set_broadcasting_enabled(broadcast);
@@ -186,7 +186,7 @@ Error PacketPeerUDP::bind(int p_port, const IPAddress &p_bind_address, int p_rec
ip_type = p_bind_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
}
err = _sock->open(NetSocket::TYPE_UDP, ip_type);
err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type);
if (err != OK) {
return ERR_CANT_CREATE;
@@ -194,7 +194,8 @@ Error PacketPeerUDP::bind(int p_port, const IPAddress &p_bind_address, int p_rec
_sock->set_blocking_enabled(false);
_sock->set_broadcasting_enabled(broadcast);
err = _sock->bind(p_bind_address, p_port);
NetSocket::Address addr(p_bind_address, p_port);
err = _sock->bind(addr);
if (err != OK) {
_sock->close();
@@ -231,12 +232,13 @@ Error PacketPeerUDP::connect_to_host(const IPAddress &p_host, int p_port) {
if (!_sock->is_open()) {
IP::Type ip_type = p_host.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
err = _sock->open(NetSocket::TYPE_UDP, ip_type);
err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type);
ERR_FAIL_COND_V(err != OK, ERR_CANT_OPEN);
_sock->set_blocking_enabled(false);
}
err = _sock->connect_to_host(p_host, p_port);
NetSocket::Address addr(p_host, p_port);
err = _sock->connect_to_host(addr);
// I see no reason why we should get ERR_BUSY (wouldblock/eagain) here.
// This is UDP, so connect is only used to tell the OS to which socket
@@ -345,9 +347,9 @@ int PacketPeerUDP::get_packet_port() const {
}
int PacketPeerUDP::get_local_port() const {
uint16_t local_port;
_sock->get_socket_address(nullptr, &local_port);
return local_port;
NetSocket::Address addr;
_sock->get_socket_address(&addr);
return addr.port();
}
void PacketPeerUDP::set_dest_address(const IPAddress &p_address, int p_port) {

90
core/io/socket_server.cpp Normal file
View File

@@ -0,0 +1,90 @@
/**************************************************************************/
/* socket_server.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "socket_server.h"
void SocketServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_connection_available"), &SocketServer::is_connection_available);
ClassDB::bind_method(D_METHOD("is_listening"), &SocketServer::is_listening);
ClassDB::bind_method(D_METHOD("stop"), &SocketServer::stop);
ClassDB::bind_method(D_METHOD("take_socket_connection"), &SocketServer::take_socket_connection);
}
Error SocketServer::_listen(const NetSocket::Address &p_addr) {
DEV_ASSERT(_sock.is_valid());
DEV_ASSERT(_sock->is_open());
_sock->set_blocking_enabled(false);
Error err = _sock->bind(p_addr);
if (err != OK) {
_sock->close();
return ERR_ALREADY_IN_USE;
}
err = _sock->listen(MAX_PENDING_CONNECTIONS);
if (err != OK) {
_sock->close();
return FAILED;
}
return OK;
}
bool SocketServer::is_listening() const {
ERR_FAIL_COND_V(_sock.is_null(), false);
return _sock->is_open();
}
bool SocketServer::is_connection_available() const {
ERR_FAIL_COND_V(_sock.is_null(), false);
if (!_sock->is_open()) {
return false;
}
Error err = _sock->poll(NetSocket::POLL_TYPE_IN, 0);
return (err == OK);
}
void SocketServer::stop() {
if (_sock.is_valid()) {
_sock->close();
}
}
SocketServer::SocketServer() :
_sock(NetSocket::create()) {
}
SocketServer::~SocketServer() {
stop();
}

77
core/io/socket_server.h Normal file
View File

@@ -0,0 +1,77 @@
/**************************************************************************/
/* socket_server.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/net_socket.h"
#include "core/io/stream_peer_socket.h"
class SocketServer : public RefCounted {
GDCLASS(SocketServer, RefCounted);
protected:
enum {
MAX_PENDING_CONNECTIONS = 8,
};
Ref<NetSocket> _sock;
static void _bind_methods();
Error _listen(const NetSocket::Address &p_addr);
template <typename T>
Ref<T> _take_connection() {
Ref<T> conn;
if (!is_connection_available()) {
return conn;
}
Ref<NetSocket> ns;
NetSocket::Address addr;
ns = _sock->accept(addr);
if (ns.is_null()) {
return conn;
}
conn.instantiate();
conn->accept_socket(ns, addr);
return conn;
}
public:
bool is_listening() const;
bool is_connection_available() const;
virtual Ref<StreamPeerSocket> take_socket_connection() = 0;
void stop(); // Stop listening
SocketServer();
~SocketServer();
};

View File

@@ -0,0 +1,52 @@
/**************************************************************************/
/* stream_peer_socket.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
namespace compat::StreamPeerTCP {
enum class Status {
STATUS_NONE = StreamPeerSocket::STATUS_NONE,
STATUS_CONNECTING = StreamPeerSocket::STATUS_CONNECTING,
STATUS_CONNECTED = StreamPeerSocket::STATUS_CONNECTED,
STATUS_ERROR = StreamPeerSocket::STATUS_ERROR,
};
}
VARIANT_ENUM_CAST(compat::StreamPeerTCP::Status);
compat::StreamPeerTCP::Status StreamPeerSocket::_get_status_compat_107954() const {
return (compat::StreamPeerTCP::Status)get_status();
}
void StreamPeerSocket::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("get_status"), &StreamPeerSocket::_get_status_compat_107954);
}
#endif

View File

@@ -0,0 +1,236 @@
/**************************************************************************/
/* stream_peer_socket.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "stream_peer_socket.h"
#include "stream_peer_socket.compat.inc"
Error StreamPeerSocket::poll() {
if (status == STATUS_CONNECTED) {
Error err;
err = _sock->poll(NetSocket::POLL_TYPE_IN, 0);
if (err == OK) {
// FIN received
if (_sock->get_available_bytes() == 0) {
disconnect_from_host();
return OK;
}
}
// Also poll write
err = _sock->poll(NetSocket::POLL_TYPE_IN_OUT, 0);
if (err != OK && err != ERR_BUSY) {
// Got an error
disconnect_from_host();
status = STATUS_ERROR;
return err;
}
return OK;
} else if (status != STATUS_CONNECTING) {
return OK;
}
Error err = _sock->connect_to_host(peer_address);
if (err == OK) {
status = STATUS_CONNECTED;
return OK;
} else if (err == ERR_BUSY) {
// Check for connect timeout
if (OS::get_singleton()->get_ticks_msec() > timeout) {
disconnect_from_host();
status = STATUS_ERROR;
return ERR_CONNECTION_ERROR;
}
// Still trying to connect
return OK;
}
disconnect_from_host();
status = STATUS_ERROR;
return ERR_CONNECTION_ERROR;
}
Error StreamPeerSocket::write(const uint8_t *p_data, int p_bytes, int &r_sent, bool p_block) {
ERR_FAIL_COND_V(_sock.is_null(), ERR_UNAVAILABLE);
if (status != STATUS_CONNECTED) {
return FAILED;
}
Error err;
int data_to_send = p_bytes;
const uint8_t *offset = p_data;
int total_sent = 0;
while (data_to_send) {
int sent_amount = 0;
err = _sock->send(offset, data_to_send, sent_amount);
if (err != OK) {
if (err != ERR_BUSY) {
disconnect_from_host();
return FAILED;
}
if (!p_block) {
r_sent = total_sent;
return OK;
}
// Block and wait for the socket to accept more data
err = _sock->poll(NetSocket::POLL_TYPE_OUT, -1);
if (err != OK) {
disconnect_from_host();
return FAILED;
}
} else {
data_to_send -= sent_amount;
offset += sent_amount;
total_sent += sent_amount;
}
}
r_sent = total_sent;
return OK;
}
Error StreamPeerSocket::read(uint8_t *p_buffer, int p_bytes, int &r_received, bool p_block) {
if (status != STATUS_CONNECTED) {
return FAILED;
}
Error err;
int to_read = p_bytes;
int total_read = 0;
r_received = 0;
while (to_read) {
int read = 0;
err = _sock->recv(p_buffer + total_read, to_read, read);
if (err != OK) {
if (err != ERR_BUSY) {
disconnect_from_host();
return FAILED;
}
if (!p_block) {
r_received = total_read;
return OK;
}
err = _sock->poll(NetSocket::POLL_TYPE_IN, -1);
if (err != OK) {
disconnect_from_host();
return FAILED;
}
} else if (read == 0) {
disconnect_from_host();
r_received = total_read;
return ERR_FILE_EOF;
} else {
to_read -= read;
total_read += read;
if (!p_block) {
r_received = total_read;
return OK;
}
}
}
r_received = total_read;
return OK;
}
StreamPeerSocket::Status StreamPeerSocket::get_status() const {
return status;
}
void StreamPeerSocket::disconnect_from_host() {
if (_sock.is_valid() && _sock->is_open()) {
_sock->close();
}
timeout = 0;
status = STATUS_NONE;
peer_address = NetSocket::Address();
}
Error StreamPeerSocket::wait(NetSocket::PollType p_type, int p_timeout) {
ERR_FAIL_COND_V(_sock.is_null() || !_sock->is_open(), ERR_UNAVAILABLE);
return _sock->poll(p_type, p_timeout);
}
Error StreamPeerSocket::put_data(const uint8_t *p_data, int p_bytes) {
int total;
return write(p_data, p_bytes, total, true);
}
Error StreamPeerSocket::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) {
return write(p_data, p_bytes, r_sent, false);
}
Error StreamPeerSocket::get_data(uint8_t *p_buffer, int p_bytes) {
int total;
return read(p_buffer, p_bytes, total, true);
}
Error StreamPeerSocket::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
return read(p_buffer, p_bytes, r_received, false);
}
int StreamPeerSocket::get_available_bytes() const {
ERR_FAIL_COND_V(_sock.is_null(), -1);
return _sock->get_available_bytes();
}
void StreamPeerSocket::_bind_methods() {
ClassDB::bind_method(D_METHOD("poll"), &StreamPeerSocket::poll);
ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerSocket::get_status);
ClassDB::bind_method(D_METHOD("disconnect_from_host"), &StreamPeerSocket::disconnect_from_host);
BIND_ENUM_CONSTANT(STATUS_NONE);
BIND_ENUM_CONSTANT(STATUS_CONNECTING);
BIND_ENUM_CONSTANT(STATUS_CONNECTED);
BIND_ENUM_CONSTANT(STATUS_ERROR);
}
StreamPeerSocket::StreamPeerSocket() :
_sock(NetSocket::create()) {
}
StreamPeerSocket::~StreamPeerSocket() {
disconnect_from_host();
}

View File

@@ -0,0 +1,93 @@
/**************************************************************************/
/* stream_peer_socket.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/net_socket.h"
#include "core/io/stream_peer.h"
#ifndef DISABLE_DEPRECATED
namespace compat::StreamPeerTCP {
enum class Status;
} //namespace compat::StreamPeerTCP
#endif
class StreamPeerSocket : public StreamPeer {
GDCLASS(StreamPeerSocket, StreamPeer);
public:
enum Status {
STATUS_NONE,
STATUS_CONNECTING,
STATUS_CONNECTED,
STATUS_ERROR,
};
protected:
#ifndef DISABLE_DEPRECATED
compat::StreamPeerTCP::Status _get_status_compat_107954() const;
static void _bind_compatibility_methods();
#endif
Ref<NetSocket> _sock;
uint64_t timeout = 0;
Status status = STATUS_NONE;
NetSocket::Address peer_address;
Error write(const uint8_t *p_data, int p_bytes, int &r_sent, bool p_block);
Error read(uint8_t *p_buffer, int p_bytes, int &r_received, bool p_block);
static void _bind_methods();
public:
virtual void accept_socket(Ref<NetSocket> p_sock, const NetSocket::Address &p_addr) = 0;
void disconnect_from_host();
int get_available_bytes() const override;
Status get_status() const;
// Poll socket updating its state.
Error poll();
// Wait or check for writable, readable.
Error wait(NetSocket::PollType p_type, int p_timeout = 0);
// Read/Write from StreamPeer
Error put_data(const uint8_t *p_data, int p_bytes) override;
Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) override;
Error get_data(uint8_t *p_buffer, int p_bytes) override;
Error get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) override;
StreamPeerSocket();
virtual ~StreamPeerSocket();
};
VARIANT_ENUM_CAST(StreamPeerSocket::Status);

View File

@@ -32,60 +32,14 @@
#include "core/config/project_settings.h"
Error StreamPeerTCP::poll() {
if (status == STATUS_CONNECTED) {
Error err;
err = _sock->poll(NetSocket::POLL_TYPE_IN, 0);
if (err == OK) {
// FIN received
if (_sock->get_available_bytes() == 0) {
disconnect_from_host();
return OK;
}
}
// Also poll write
err = _sock->poll(NetSocket::POLL_TYPE_IN_OUT, 0);
if (err != OK && err != ERR_BUSY) {
// Got an error
disconnect_from_host();
status = STATUS_ERROR;
return err;
}
return OK;
} else if (status != STATUS_CONNECTING) {
return OK;
}
Error err = _sock->connect_to_host(peer_host, peer_port);
if (err == OK) {
status = STATUS_CONNECTED;
return OK;
} else if (err == ERR_BUSY) {
// Check for connect timeout
if (OS::get_singleton()->get_ticks_msec() > timeout) {
disconnect_from_host();
status = STATUS_ERROR;
return ERR_CONNECTION_ERROR;
}
// Still trying to connect
return OK;
}
disconnect_from_host();
status = STATUS_ERROR;
return ERR_CONNECTION_ERROR;
}
void StreamPeerTCP::accept_socket(Ref<NetSocket> p_sock, IPAddress p_host, uint16_t p_port) {
void StreamPeerTCP::accept_socket(Ref<NetSocket> p_sock, const NetSocket::Address &p_addr) {
_sock = p_sock;
_sock->set_blocking_enabled(false);
timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/tcp/connect_timeout_seconds")) * 1000);
status = STATUS_CONNECTED;
peer_host = p_host;
peer_port = p_port;
peer_address = p_addr;
}
Error StreamPeerTCP::bind(int p_port, const IPAddress &p_host) {
@@ -97,12 +51,13 @@ Error StreamPeerTCP::bind(int p_port, const IPAddress &p_host) {
if (p_host.is_wildcard()) {
ip_type = IP::TYPE_ANY;
}
Error err = _sock->open(NetSocket::TYPE_TCP, ip_type);
Error err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_TCP, ip_type);
if (err != OK) {
return err;
}
_sock->set_blocking_enabled(false);
return _sock->bind(p_host, p_port);
NetSocket::Address addr(p_host, p_port);
return _sock->bind(addr);
}
Error StreamPeerTCP::connect_to_host(const IPAddress &p_host, int p_port) {
@@ -113,7 +68,7 @@ Error StreamPeerTCP::connect_to_host(const IPAddress &p_host, int p_port) {
if (!_sock->is_open()) {
IP::Type ip_type = p_host.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
Error err = _sock->open(NetSocket::TYPE_TCP, ip_type);
Error err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_TCP, ip_type);
if (err != OK) {
return err;
}
@@ -121,7 +76,9 @@ Error StreamPeerTCP::connect_to_host(const IPAddress &p_host, int p_port) {
}
timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/tcp/connect_timeout_seconds")) * 1000);
Error err = _sock->connect_to_host(p_host, p_port);
NetSocket::Address addr(p_host, p_port);
Error err = _sock->connect_to_host(addr);
if (err == OK) {
status = STATUS_CONNECTED;
@@ -133,106 +90,7 @@ Error StreamPeerTCP::connect_to_host(const IPAddress &p_host, int p_port) {
return FAILED;
}
peer_host = p_host;
peer_port = p_port;
return OK;
}
Error StreamPeerTCP::write(const uint8_t *p_data, int p_bytes, int &r_sent, bool p_block) {
ERR_FAIL_COND_V(_sock.is_null(), ERR_UNAVAILABLE);
if (status != STATUS_CONNECTED) {
return FAILED;
}
Error err;
int data_to_send = p_bytes;
const uint8_t *offset = p_data;
int total_sent = 0;
while (data_to_send) {
int sent_amount = 0;
err = _sock->send(offset, data_to_send, sent_amount);
if (err != OK) {
if (err != ERR_BUSY) {
disconnect_from_host();
return FAILED;
}
if (!p_block) {
r_sent = total_sent;
return OK;
}
// Block and wait for the socket to accept more data
err = _sock->poll(NetSocket::POLL_TYPE_OUT, -1);
if (err != OK) {
disconnect_from_host();
return FAILED;
}
} else {
data_to_send -= sent_amount;
offset += sent_amount;
total_sent += sent_amount;
}
}
r_sent = total_sent;
return OK;
}
Error StreamPeerTCP::read(uint8_t *p_buffer, int p_bytes, int &r_received, bool p_block) {
if (status != STATUS_CONNECTED) {
return FAILED;
}
Error err;
int to_read = p_bytes;
int total_read = 0;
r_received = 0;
while (to_read) {
int read = 0;
err = _sock->recv(p_buffer + total_read, to_read, read);
if (err != OK) {
if (err != ERR_BUSY) {
disconnect_from_host();
return FAILED;
}
if (!p_block) {
r_received = total_read;
return OK;
}
err = _sock->poll(NetSocket::POLL_TYPE_IN, -1);
if (err != OK) {
disconnect_from_host();
return FAILED;
}
} else if (read == 0) {
disconnect_from_host();
r_received = total_read;
return ERR_FILE_EOF;
} else {
to_read -= read;
total_read += read;
if (!p_block) {
r_received = total_read;
return OK;
}
}
}
r_received = total_read;
peer_address = addr;
return OK;
}
@@ -242,61 +100,18 @@ void StreamPeerTCP::set_no_delay(bool p_enabled) {
_sock->set_tcp_no_delay_enabled(p_enabled);
}
StreamPeerTCP::Status StreamPeerTCP::get_status() const {
return status;
}
void StreamPeerTCP::disconnect_from_host() {
if (_sock.is_valid() && _sock->is_open()) {
_sock->close();
}
timeout = 0;
status = STATUS_NONE;
peer_host = IPAddress();
peer_port = 0;
}
Error StreamPeerTCP::wait(NetSocket::PollType p_type, int p_timeout) {
ERR_FAIL_COND_V(_sock.is_null() || !_sock->is_open(), ERR_UNAVAILABLE);
return _sock->poll(p_type, p_timeout);
}
Error StreamPeerTCP::put_data(const uint8_t *p_data, int p_bytes) {
int total;
return write(p_data, p_bytes, total, true);
}
Error StreamPeerTCP::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) {
return write(p_data, p_bytes, r_sent, false);
}
Error StreamPeerTCP::get_data(uint8_t *p_buffer, int p_bytes) {
int total;
return read(p_buffer, p_bytes, total, true);
}
Error StreamPeerTCP::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
return read(p_buffer, p_bytes, r_received, false);
}
int StreamPeerTCP::get_available_bytes() const {
ERR_FAIL_COND_V(_sock.is_null(), -1);
return _sock->get_available_bytes();
}
IPAddress StreamPeerTCP::get_connected_host() const {
return peer_host;
return peer_address.ip();
}
int StreamPeerTCP::get_connected_port() const {
return peer_port;
return peer_address.port();
}
int StreamPeerTCP::get_local_port() const {
uint16_t local_port;
_sock->get_socket_address(nullptr, &local_port);
return local_port;
NetSocket::Address addr;
_sock->get_socket_address(&addr);
return addr.port();
}
Error StreamPeerTCP::_connect(const String &p_address, int p_port) {
@@ -316,24 +131,8 @@ Error StreamPeerTCP::_connect(const String &p_address, int p_port) {
void StreamPeerTCP::_bind_methods() {
ClassDB::bind_method(D_METHOD("bind", "port", "host"), &StreamPeerTCP::bind, DEFVAL("*"));
ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port"), &StreamPeerTCP::_connect);
ClassDB::bind_method(D_METHOD("poll"), &StreamPeerTCP::poll);
ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerTCP::get_status);
ClassDB::bind_method(D_METHOD("get_connected_host"), &StreamPeerTCP::get_connected_host);
ClassDB::bind_method(D_METHOD("get_connected_port"), &StreamPeerTCP::get_connected_port);
ClassDB::bind_method(D_METHOD("get_local_port"), &StreamPeerTCP::get_local_port);
ClassDB::bind_method(D_METHOD("disconnect_from_host"), &StreamPeerTCP::disconnect_from_host);
ClassDB::bind_method(D_METHOD("set_no_delay", "enabled"), &StreamPeerTCP::set_no_delay);
BIND_ENUM_CONSTANT(STATUS_NONE);
BIND_ENUM_CONSTANT(STATUS_CONNECTING);
BIND_ENUM_CONSTANT(STATUS_CONNECTED);
BIND_ENUM_CONSTANT(STATUS_ERROR);
}
StreamPeerTCP::StreamPeerTCP() :
_sock(Ref<NetSocket>(NetSocket::create())) {
}
StreamPeerTCP::~StreamPeerTCP() {
disconnect_from_host();
}

View File

@@ -32,62 +32,24 @@
#include "core/io/ip.h"
#include "core/io/ip_address.h"
#include "core/io/net_socket.h"
#include "core/io/stream_peer.h"
#include "core/io/stream_peer_socket.h"
class StreamPeerTCP : public StreamPeer {
GDCLASS(StreamPeerTCP, StreamPeer);
public:
enum Status {
STATUS_NONE,
STATUS_CONNECTING,
STATUS_CONNECTED,
STATUS_ERROR,
};
class StreamPeerTCP : public StreamPeerSocket {
GDCLASS(StreamPeerTCP, StreamPeerSocket);
protected:
Ref<NetSocket> _sock;
uint64_t timeout = 0;
Status status = STATUS_NONE;
IPAddress peer_host;
uint16_t peer_port = 0;
Error _connect(const String &p_address, int p_port);
Error write(const uint8_t *p_data, int p_bytes, int &r_sent, bool p_block);
Error read(uint8_t *p_buffer, int p_bytes, int &r_received, bool p_block);
static void _bind_methods();
public:
void accept_socket(Ref<NetSocket> p_sock, IPAddress p_host, uint16_t p_port);
void accept_socket(Ref<NetSocket> p_sock, const NetSocket::Address &p_addr) override;
Error bind(int p_port, const IPAddress &p_host);
Error connect_to_host(const IPAddress &p_host, int p_port);
IPAddress get_connected_host() const;
int get_connected_port() const;
int get_local_port() const;
void disconnect_from_host();
int get_available_bytes() const override;
Status get_status() const;
void set_no_delay(bool p_enabled);
// Poll socket updating its state.
Error poll();
// Wait or check for writable, readable.
Error wait(NetSocket::PollType p_type, int p_timeout = 0);
// Read/Write from StreamPeer
Error put_data(const uint8_t *p_data, int p_bytes) override;
Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) override;
Error get_data(uint8_t *p_buffer, int p_bytes) override;
Error get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) override;
StreamPeerTCP();
~StreamPeerTCP();
};
VARIANT_ENUM_CAST(StreamPeerTCP::Status);

View File

@@ -0,0 +1,99 @@
/**************************************************************************/
/* stream_peer_uds.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "stream_peer_uds.h"
#include "core/config/project_settings.h"
void StreamPeerUDS::_bind_methods() {
ClassDB::bind_method(D_METHOD("bind", "path"), &StreamPeerUDS::bind);
ClassDB::bind_method(D_METHOD("connect_to_host", "path"), &StreamPeerUDS::connect_to_host);
ClassDB::bind_method(D_METHOD("get_connected_path"), &StreamPeerUDS::get_connected_path);
}
void StreamPeerUDS::accept_socket(Ref<NetSocket> p_sock, const NetSocket::Address &p_addr) {
_sock = p_sock;
_sock->set_blocking_enabled(false);
timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/unix/connect_timeout_seconds")) * 1000);
status = STATUS_CONNECTED;
}
Error StreamPeerUDS::bind(const String &p_path) {
ERR_FAIL_COND_V(_sock.is_null(), ERR_UNAVAILABLE);
ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE);
IP::Type ip_type = IP::TYPE_NONE;
Error err = _sock->open(NetSocket::Family::UNIX, NetSocket::TYPE_NONE, ip_type);
if (err != OK) {
return err;
}
_sock->set_blocking_enabled(false);
NetSocket::Address addr(p_path);
return _sock->bind(addr);
}
Error StreamPeerUDS::connect_to_host(const String &p_path) {
ERR_FAIL_COND_V(_sock.is_null(), ERR_UNAVAILABLE);
ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE);
ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);
if (!_sock->is_open()) {
IP::Type ip_type = IP::TYPE_NONE;
Error err = _sock->open(NetSocket::Family::UNIX, NetSocket::TYPE_NONE, ip_type);
if (err != OK) {
return err;
}
_sock->set_blocking_enabled(false);
}
timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/unix/connect_timeout_seconds")) * 1000);
NetSocket::Address addr(p_path);
Error err = _sock->connect_to_host(addr);
if (err == OK) {
status = STATUS_CONNECTED;
} else if (err == ERR_BUSY) {
status = STATUS_CONNECTING;
} else {
ERR_PRINT("Connection to remote host failed!");
disconnect_from_host();
return FAILED;
}
peer_address = addr;
peer_path = p_path;
return OK;
}
const String StreamPeerUDS::get_connected_path() const {
return String(peer_address.get_path().get_data());
}

48
core/io/stream_peer_uds.h Normal file
View File

@@ -0,0 +1,48 @@
/**************************************************************************/
/* stream_peer_uds.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/stream_peer_socket.h"
class StreamPeerUDS : public StreamPeerSocket {
GDCLASS(StreamPeerUDS, StreamPeerSocket);
protected:
String peer_path;
static void _bind_methods();
public:
void accept_socket(Ref<NetSocket> p_sock, const NetSocket::Address &p_addr) override;
Error bind(const String &p_path);
Error connect_to_host(const String &p_path);
const String get_connected_path() const;
};

View File

@@ -32,11 +32,8 @@
void TCPServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("listen", "port", "bind_address"), &TCPServer::listen, DEFVAL("*"));
ClassDB::bind_method(D_METHOD("is_connection_available"), &TCPServer::is_connection_available);
ClassDB::bind_method(D_METHOD("is_listening"), &TCPServer::is_listening);
ClassDB::bind_method(D_METHOD("get_local_port"), &TCPServer::get_local_port);
ClassDB::bind_method(D_METHOD("take_connection"), &TCPServer::take_connection);
ClassDB::bind_method(D_METHOD("stop"), &TCPServer::stop);
}
Error TCPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) {
@@ -52,81 +49,21 @@ Error TCPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) {
ip_type = p_bind_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
}
err = _sock->open(NetSocket::TYPE_TCP, ip_type);
err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_TCP, ip_type);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
_sock->set_blocking_enabled(false);
_sock->set_reuse_address_enabled(true);
err = _sock->bind(p_bind_address, p_port);
if (err != OK) {
_sock->close();
return ERR_ALREADY_IN_USE;
}
err = _sock->listen(MAX_PENDING_CONNECTIONS);
if (err != OK) {
_sock->close();
return FAILED;
}
return OK;
return _listen(NetSocket::Address(p_bind_address, p_port));
}
int TCPServer::get_local_port() const {
uint16_t local_port;
_sock->get_socket_address(nullptr, &local_port);
return local_port;
}
bool TCPServer::is_listening() const {
ERR_FAIL_COND_V(_sock.is_null(), false);
return _sock->is_open();
}
bool TCPServer::is_connection_available() const {
ERR_FAIL_COND_V(_sock.is_null(), false);
if (!_sock->is_open()) {
return false;
}
Error err = _sock->poll(NetSocket::POLL_TYPE_IN, 0);
return (err == OK);
NetSocket::Address addr;
_sock->get_socket_address(&addr);
return addr.port();
}
Ref<StreamPeerTCP> TCPServer::take_connection() {
Ref<StreamPeerTCP> conn;
if (!is_connection_available()) {
return conn;
}
Ref<NetSocket> ns;
IPAddress ip;
uint16_t port = 0;
ns = _sock->accept(ip, port);
if (ns.is_null()) {
return conn;
}
conn.instantiate();
conn->accept_socket(ns, ip, port);
return conn;
}
void TCPServer::stop() {
if (_sock.is_valid()) {
_sock->close();
}
}
TCPServer::TCPServer() :
_sock(Ref<NetSocket>(NetSocket::create())) {
}
TCPServer::~TCPServer() {
stop();
return _take_connection<StreamPeerTCP>();
}

View File

@@ -31,30 +31,18 @@
#pragma once
#include "core/io/ip.h"
#include "core/io/net_socket.h"
#include "core/io/stream_peer.h"
#include "core/io/socket_server.h"
#include "core/io/stream_peer_tcp.h"
class TCPServer : public RefCounted {
GDCLASS(TCPServer, RefCounted);
class TCPServer : public SocketServer {
GDCLASS(TCPServer, SocketServer);
protected:
enum {
MAX_PENDING_CONNECTIONS = 8
};
Ref<NetSocket> _sock;
static void _bind_methods();
public:
Error listen(uint16_t p_port, const IPAddress &p_bind_address = IPAddress("*"));
int get_local_port() const;
bool is_listening() const;
bool is_connection_available() const;
Ref<StreamPeerTCP> take_connection();
void stop(); // Stop listening
TCPServer();
~TCPServer();
Ref<StreamPeerSocket> take_socket_connection() override { return take_connection(); }
};

View File

@@ -99,7 +99,7 @@ Error UDPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) {
ip_type = p_bind_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
}
err = _sock->open(NetSocket::TYPE_UDP, ip_type);
err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type);
if (err != OK) {
return ERR_CANT_CREATE;
@@ -107,7 +107,8 @@ Error UDPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) {
_sock->set_blocking_enabled(false);
_sock->set_reuse_address_enabled(true);
err = _sock->bind(p_bind_address, p_port);
NetSocket::Address addr(p_bind_address, p_port);
err = _sock->bind(addr);
if (err != OK) {
stop();
@@ -117,9 +118,9 @@ Error UDPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) {
}
int UDPServer::get_local_port() const {
uint16_t local_port;
_sock->get_socket_address(nullptr, &local_port);
return local_port;
NetSocket::Address addr;
_sock->get_socket_address(&addr);
return addr.port();
}
bool UDPServer::is_listening() const {

52
core/io/uds_server.cpp Normal file
View File

@@ -0,0 +1,52 @@
/**************************************************************************/
/* uds_server.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "uds_server.h"
void UDSServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("listen", "path"), &UDSServer::listen);
ClassDB::bind_method(D_METHOD("take_connection"), &UDSServer::take_connection);
}
Error UDSServer::listen(const String &p_path) {
ERR_FAIL_COND_V(_sock.is_null(), ERR_UNAVAILABLE);
ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE);
ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);
IP::Type ip_type = IP::TYPE_NONE;
Error err = _sock->open(NetSocket::Family::UNIX, NetSocket::TYPE_NONE, ip_type);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
return _listen(p_path);
}
Ref<StreamPeerUDS> UDSServer::take_connection() {
return _take_connection<StreamPeerUDS>();
}

46
core/io/uds_server.h Normal file
View File

@@ -0,0 +1,46 @@
/**************************************************************************/
/* uds_server.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/socket_server.h"
#include "core/io/stream_peer_uds.h"
class UDSServer : public SocketServer {
GDCLASS(UDSServer, SocketServer);
protected:
static void _bind_methods();
public:
Error listen(const String &p_path);
Ref<StreamPeerUDS> take_connection();
Ref<StreamPeerSocket> take_socket_connection() override { return take_connection(); }
};

View File

@@ -63,6 +63,7 @@
#include "core/io/tcp_server.h"
#include "core/io/translation_loader_po.h"
#include "core/io/udp_server.h"
#include "core/io/uds_server.h"
#include "core/io/xml_parser.h"
#include "core/math/a_star.h"
#include "core/math/a_star_grid_2d.h"
@@ -198,12 +199,18 @@ void register_core_types() {
GDREGISTER_ABSTRACT_CLASS(IP);
GDREGISTER_ABSTRACT_CLASS(StreamPeer);
GDREGISTER_ABSTRACT_CLASS(StreamPeerSocket);
GDREGISTER_ABSTRACT_CLASS(SocketServer);
GDREGISTER_CLASS(StreamPeerExtension);
GDREGISTER_CLASS(StreamPeerBuffer);
GDREGISTER_CLASS(StreamPeerGZIP);
GDREGISTER_CLASS(StreamPeerTCP);
GDREGISTER_CLASS(TCPServer);
// IPC using UNIX domain sockets.
GDREGISTER_CLASS(StreamPeerUDS);
GDREGISTER_CLASS(UDSServer);
GDREGISTER_ABSTRACT_CLASS(PacketPeer);
GDREGISTER_CLASS(PacketPeerExtension);
GDREGISTER_CLASS(PacketPeerStream);
@@ -322,6 +329,7 @@ void register_core_types() {
void register_core_settings() {
// Since in register core types, globals may not be present.
GLOBAL_DEF(PropertyInfo(Variant::INT, "network/limits/tcp/connect_timeout_seconds", PROPERTY_HINT_RANGE, "1,1800,1"), (30));
GLOBAL_DEF(PropertyInfo(Variant::INT, "network/limits/unix/connect_timeout_seconds", PROPERTY_HINT_RANGE, "1,1800,1"), (30));
GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "network/limits/packet_peer_stream/max_buffer_po2", PROPERTY_HINT_RANGE, "8,64,1,or_greater"), (16));
GLOBAL_DEF(PropertyInfo(Variant::STRING, "network/tls/certificate_bundle_override", PROPERTY_HINT_FILE, "*.crt"), "");

View File

@@ -2377,6 +2377,9 @@
<member name="network/limits/tcp/connect_timeout_seconds" type="int" setter="" getter="" default="30">
Timeout (in seconds) for connection attempts using TCP.
</member>
<member name="network/limits/unix/connect_timeout_seconds" type="int" setter="" getter="" default="30">
Timeout (in seconds) for connection attempts using UNIX domain socket.
</member>
<member name="network/limits/webrtc/max_channel_in_buffer_kb" type="int" setter="" getter="" default="64">
Maximum size (in kiB) for the [WebRTCDataChannel] input buffer.
</member>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="SocketServer" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
An abstract class for servers based on sockets.
</brief_description>
<description>
A socket server.
</description>
<tutorials>
</tutorials>
<methods>
<method name="is_connection_available" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if a connection is available for taking.
</description>
</method>
<method name="is_listening" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the server is currently listening for connections.
</description>
</method>
<method name="stop">
<return type="void" />
<description>
Stops listening.
</description>
</method>
<method name="take_socket_connection">
<return type="StreamPeerSocket" />
<description>
If a connection is available, returns a StreamPeerSocket with the connection.
</description>
</method>
</methods>
</class>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="StreamPeerSocket" inherits="StreamPeer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Abstract base class for interacting with socket streams.
</brief_description>
<description>
StreamPeerSocket is an abstract base class that defines common behavior for socket-based streams.
</description>
<tutorials>
</tutorials>
<methods>
<method name="disconnect_from_host">
<return type="void" />
<description>
Disconnects from host.
</description>
</method>
<method name="get_status" qualifiers="const">
<return type="int" enum="StreamPeerSocket.Status" />
<description>
Returns the status of the connection.
</description>
</method>
<method name="poll">
<return type="int" enum="Error" />
<description>
Polls the socket, updating its state. See [method get_status].
</description>
</method>
</methods>
<constants>
<constant name="STATUS_NONE" value="0" enum="Status">
The initial status of the [StreamPeerSocket]. This is also the status after disconnecting.
</constant>
<constant name="STATUS_CONNECTING" value="1" enum="Status">
A status representing a [StreamPeerSocket] that is connecting to a host.
</constant>
<constant name="STATUS_CONNECTED" value="2" enum="Status">
A status representing a [StreamPeerSocket] that is connected to a host.
</constant>
<constant name="STATUS_ERROR" value="3" enum="Status">
A status representing a [StreamPeerSocket] in error state.
</constant>
</constants>
</class>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="StreamPeerTCP" inherits="StreamPeer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<class name="StreamPeerTCP" inherits="StreamPeerSocket" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A stream peer that handles TCP connections.
</brief_description>
@@ -27,12 +27,6 @@
Connects to the specified [code]host:port[/code] pair. A hostname will be resolved if valid. Returns [constant OK] on success.
</description>
</method>
<method name="disconnect_from_host">
<return type="void" />
<description>
Disconnects from host.
</description>
</method>
<method name="get_connected_host" qualifiers="const">
<return type="String" />
<description>
@@ -51,18 +45,6 @@
Returns the local port to which this peer is bound.
</description>
</method>
<method name="get_status" qualifiers="const">
<return type="int" enum="StreamPeerTCP.Status" />
<description>
Returns the status of the connection.
</description>
</method>
<method name="poll">
<return type="int" enum="Error" />
<description>
Poll the socket, updating its state. See [method get_status].
</description>
</method>
<method name="set_no_delay">
<return type="void" />
<param index="0" name="enabled" type="bool" />
@@ -72,18 +54,4 @@
</description>
</method>
</methods>
<constants>
<constant name="STATUS_NONE" value="0" enum="Status">
The initial status of the [StreamPeerTCP]. This is also the status after disconnecting.
</constant>
<constant name="STATUS_CONNECTING" value="1" enum="Status">
A status representing a [StreamPeerTCP] that is connecting to a host.
</constant>
<constant name="STATUS_CONNECTED" value="2" enum="Status">
A status representing a [StreamPeerTCP] that is connected to a host.
</constant>
<constant name="STATUS_ERROR" value="3" enum="Status">
A status representing a [StreamPeerTCP] in error state.
</constant>
</constants>
</class>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="StreamPeerUDS" inherits="StreamPeerSocket" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A stream peer that handles UNIX Domain Socket (UDS) connections.
</brief_description>
<description>
A stream peer that handles UNIX Domain Socket (UDS) connections. This object can be used to connect to UDS servers, or also is returned by a UDS server. Unix Domain Sockets provide inter-process communication on the same machine using the filesystem namespace.
[b]Note:[/b] UNIX Domain Sockets are only available on UNIX-like systems (Linux, macOS, etc.) and are not supported on Windows.
</description>
<tutorials>
</tutorials>
<methods>
<method name="bind">
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
<description>
Opens the UDS socket, and binds it to the specified socket path.
This method is generally not needed, and only used to force the subsequent call to [method connect_to_host] to use the specified [param path] as the source address.
</description>
</method>
<method name="connect_to_host">
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
<description>
Connects to the specified UNIX Domain Socket path. Returns [constant OK] on success.
</description>
</method>
<method name="get_connected_path" qualifiers="const">
<return type="String" />
<description>
Returns the socket path of this peer.
</description>
</method>
</methods>
</class>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="TCPServer" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<class name="TCPServer" inherits="SocketServer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A TCP server.
</brief_description>
@@ -16,18 +16,6 @@
Returns the local port this server is listening to.
</description>
</method>
<method name="is_connection_available" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if a connection is available for taking.
</description>
</method>
<method name="is_listening" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the server is currently listening for connections.
</description>
</method>
<method name="listen">
<return type="int" enum="Error" />
<param index="0" name="port" type="int" />
@@ -39,12 +27,6 @@
If [param bind_address] is set to any valid address (e.g. [code]"192.168.1.101"[/code], [code]"::1"[/code], etc.), the server will only listen on the interface with that address (or fail if no interface with the given address exists).
</description>
</method>
<method name="stop">
<return type="void" />
<description>
Stops listening.
</description>
</method>
<method name="take_connection">
<return type="StreamPeerTCP" />
<description>

28
doc/classes/UDSServer.xml Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="UDSServer" inherits="SocketServer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A Unix Domain Socket (UDS) server.
</brief_description>
<description>
A Unix Domain Socket (UDS) server. Listens to connections on a socket path and returns a [StreamPeerUDS] when it gets an incoming connection. Unix Domain Sockets provide inter-process communication on the same machine using the filesystem namespace.
[b]Note:[/b] Unix Domain Sockets are only available on Unix-like systems (Linux, macOS, etc.) and are not supported on Windows.
</description>
<tutorials>
</tutorials>
<methods>
<method name="listen">
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
<description>
Listens on the socket at [param path]. The socket file will be created at the specified path.
[b]Note:[/b] The socket file must not already exist at the specified path. You may need to remove any existing socket file before calling this method.
</description>
</method>
<method name="take_connection">
<return type="StreamPeerUDS" />
<description>
If a connection is available, returns a StreamPeerUDS with the connection.
</description>
</method>
</methods>
</class>

View File

@@ -41,6 +41,7 @@
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>
@@ -94,6 +95,20 @@ size_t NetSocketUnix::_set_addr_storage(struct sockaddr_storage *p_addr, const I
}
}
socklen_t NetSocketUnix::_unix_set_sockaddr(struct sockaddr_un *p_addr, const CharString &p_path) {
memset(p_addr, 0, sizeof(struct sockaddr_un));
p_addr->sun_family = AF_UNIX;
// Path must not exceed maximum path length for Unix domain socket
size_t path_len = p_path.length();
ERR_FAIL_COND_V(path_len >= sizeof(p_addr->sun_path) - 1, 0);
// Regular file system socket
memcpy(p_addr->sun_path, p_path.get_data(), path_len);
p_addr->sun_path[path_len] = '\0';
return sizeof(struct sockaddr_un);
}
void NetSocketUnix::_set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ip, uint16_t *r_port) {
if (p_addr->ss_family == AF_INET) {
struct sockaddr_in *addr4 = (struct sockaddr_in *)p_addr;
@@ -172,8 +187,14 @@ bool NetSocketUnix::_can_use_ip(const IPAddress &p_ip, const bool p_for_bind) co
return !(_ip_type != IP::TYPE_ANY && !p_ip.is_wildcard() && _ip_type != type);
}
bool NetSocketUnix::_can_use_path(const CharString &p_path) const {
// Path must not exceed maximum path length for Unix domain socket
return !p_path.is_empty() && (size_t)p_path.length() < sizeof(((sockaddr_un *)0)->sun_path);
}
_FORCE_INLINE_ Error NetSocketUnix::_change_multicast_group(IPAddress p_ip, String p_if_name, bool p_add) {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V(_family != Family::INET, ERR_UNAVAILABLE);
ERR_FAIL_COND_V(!_can_use_ip(p_ip, false), ERR_INVALID_PARAMETER);
// Need to force level and af_family to IP(v4) when using dual stacking and provided multicast group is IPv4.
@@ -240,36 +261,36 @@ void NetSocketUnix::_set_close_exec_enabled(bool p_enabled) {
fcntl(_sock, F_SETFD, opts | FD_CLOEXEC);
}
Error NetSocketUnix::open(Type p_sock_type, IP::Type &ip_type) {
ERR_FAIL_COND_V(is_open(), ERR_ALREADY_IN_USE);
ERR_FAIL_COND_V(ip_type > IP::TYPE_ANY || ip_type < IP::TYPE_NONE, ERR_INVALID_PARAMETER);
Error NetSocketUnix::_inet_open(Type p_sock_type, IP::Type &r_ip_type) {
ERR_FAIL_COND_V(r_ip_type > IP::TYPE_ANY || r_ip_type < IP::TYPE_NONE, ERR_INVALID_PARAMETER);
#if defined(__OpenBSD__)
// OpenBSD does not support dual stacking, fallback to IPv4 only.
if (ip_type == IP::TYPE_ANY) {
ip_type = IP::TYPE_IPV4;
if (r_ip_type == IP::TYPE_ANY) {
r_ip_type = IP::TYPE_IPV4;
}
#endif
int family = ip_type == IP::TYPE_IPV4 ? AF_INET : AF_INET6;
int family = r_ip_type == IP::TYPE_IPV4 ? AF_INET : AF_INET6;
int protocol = p_sock_type == TYPE_TCP ? IPPROTO_TCP : IPPROTO_UDP;
int type = p_sock_type == TYPE_TCP ? SOCK_STREAM : SOCK_DGRAM;
_sock = socket(family, type, protocol);
if (_sock == -1 && ip_type == IP::TYPE_ANY) {
if (_sock == -1 && r_ip_type == IP::TYPE_ANY) {
// Careful here, changing the referenced parameter so the caller knows that we are using an IPv4 socket
// in place of a dual stack one, and further calls to _set_sock_addr will work as expected.
ip_type = IP::TYPE_IPV4;
r_ip_type = IP::TYPE_IPV4;
family = AF_INET;
_sock = socket(family, type, protocol);
}
ERR_FAIL_COND_V(_sock == -1, FAILED);
_ip_type = ip_type;
_ip_type = r_ip_type;
_family = Family::INET;
if (family == AF_INET6) {
// Select IPv4 over IPv6 mapping.
set_ipv6_only_enabled(ip_type != IP::TYPE_ANY);
set_ipv6_only_enabled(r_ip_type != IP::TYPE_ANY);
}
if (protocol == IPPROTO_UDP) {
@@ -293,18 +314,59 @@ Error NetSocketUnix::open(Type p_sock_type, IP::Type &ip_type) {
return OK;
}
Error NetSocketUnix::_unix_open() {
_sock = socket(AF_UNIX, SOCK_STREAM, 0);
ERR_FAIL_COND_V(_sock == -1, FAILED);
_family = Family::UNIX;
_set_close_exec_enabled(true);
#if defined(SO_NOSIGPIPE)
// Disable SIGPIPE (should only be relevant to stream sockets, but seems to affect UDP too on iOS).
int par = 1;
if (setsockopt(_sock, SOL_SOCKET, SO_NOSIGPIPE, &par, sizeof(int)) != 0) {
print_verbose("Unable to turn off SIGPIPE on socket.");
}
#endif
return OK;
}
Error NetSocketUnix::open(NetSocket::Family p_family, NetSocket::Type p_sock_type, IP::Type &r_ip_type) {
ERR_FAIL_COND_V(is_open(), ERR_ALREADY_IN_USE);
switch (p_family) {
case Family::INET:
return _inet_open(p_sock_type, r_ip_type);
case Family::UNIX:
return _unix_open();
case Family::NONE:
default:
return ERR_INVALID_PARAMETER;
}
}
void NetSocketUnix::close() {
if (_sock != -1) {
::close(_sock);
if (_family == Family::UNIX) {
if (_unlink_on_close) {
::unlink(_unix_path.get_data());
_unlink_on_close = false;
_unix_path = CharString();
}
}
}
_sock = -1;
_family = Family::NONE;
_ip_type = IP::TYPE_NONE;
_is_stream = false;
}
Error NetSocketUnix::bind(IPAddress p_addr, uint16_t p_port) {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
Error NetSocketUnix::_inet_bind(IPAddress p_addr, uint16_t p_port) {
ERR_FAIL_COND_V(!_can_use_ip(p_addr, true), ERR_INVALID_PARAMETER);
sockaddr_storage addr;
@@ -320,6 +382,69 @@ Error NetSocketUnix::bind(IPAddress p_addr, uint16_t p_port) {
return OK;
}
Error NetSocketUnix::_unix_bind(const CharString &p_path) {
ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);
struct sockaddr_un addr;
socklen_t addr_size = _unix_set_sockaddr(&addr, p_path);
ERR_FAIL_COND_V(addr_size == 0, ERR_INVALID_PARAMETER);
// If the socket file exists, attempt to remove it.
if (access(p_path.get_data(), F_OK) == 0) {
// Check if it's a socket
struct stat st;
if (stat(p_path.get_data(), &st) == 0) {
if (S_ISSOCK(st.st_mode)) {
// It is a socket, try to remove it.
if (unlink(p_path.get_data()) != 0) {
// Failed to remove existing socket file.
return FAILED;
}
} else {
// It's not a socket, don't remove it.
return ERR_ALREADY_EXISTS;
}
}
}
_unlink_on_close = true;
if (::bind(_sock, (struct sockaddr *)&addr, addr_size) != 0) {
NetError err = _get_socket_error();
print_verbose("Failed to bind socket. Error: " + itos(err) + ".");
close();
switch (err) {
case ERR_NET_UNAUTHORIZED:
return ERR_UNAUTHORIZED;
default:
return ERR_UNAVAILABLE;
}
}
return OK;
}
Error NetSocketUnix::bind(NetSocket::Address p_addr) {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V(_family != p_addr.get_family(), ERR_INVALID_PARAMETER);
switch (p_addr.get_family()) {
case Family::INET: {
Error res = _inet_bind(p_addr.ip(), p_addr.port());
ERR_FAIL_COND_V(res != OK, res);
} break;
case Family::UNIX: {
_unix_path = p_addr.get_path();
Error res = _unix_bind(_unix_path);
ERR_FAIL_COND_V(res != OK, res);
} break;
case Family::NONE:
default:
return ERR_INVALID_PARAMETER;
}
return OK;
}
Error NetSocketUnix::listen(int p_max_pending) {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
@@ -333,8 +458,7 @@ Error NetSocketUnix::listen(int p_max_pending) {
return OK;
}
Error NetSocketUnix::connect_to_host(IPAddress p_host, uint16_t p_port) {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
Error NetSocketUnix::_inet_connect_to_host(IPAddress p_host, uint16_t p_port) {
ERR_FAIL_COND_V(!_can_use_ip(p_host, false), ERR_INVALID_PARAMETER);
struct sockaddr_storage addr;
@@ -361,6 +485,49 @@ Error NetSocketUnix::connect_to_host(IPAddress p_host, uint16_t p_port) {
return OK;
}
Error NetSocketUnix::_unix_connect_to_host(const CharString &p_path) {
ERR_FAIL_COND_V(!_can_use_path(p_path), ERR_INVALID_PARAMETER);
struct sockaddr_un addr;
socklen_t addr_size = _unix_set_sockaddr(&addr, p_path);
ERR_FAIL_COND_V(addr_size == 0, ERR_INVALID_PARAMETER);
if (::connect(_sock, (struct sockaddr *)&addr, addr_size) != 0) {
NetError err = _get_socket_error();
switch (err) {
case ERR_NET_ADDRESS_INVALID_OR_UNAVAILABLE:
return ERR_INVALID_PARAMETER;
// Still waiting to connect, try again in a while.
case ERR_NET_WOULD_BLOCK:
case ERR_NET_IN_PROGRESS:
return ERR_BUSY;
case ERR_NET_UNAUTHORIZED:
return ERR_UNAUTHORIZED;
default:
print_verbose("Connection to host failed.");
close();
return FAILED;
}
}
return OK;
}
Error NetSocketUnix::connect_to_host(NetSocket::Address p_addr) {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V(_family != p_addr.get_family(), ERR_INVALID_PARAMETER);
switch (p_addr.get_family()) {
case Family::INET:
return _inet_connect_to_host(p_addr.ip(), p_addr.port());
case Family::UNIX:
return _unix_connect_to_host(p_addr.get_path());
case Family::NONE:
default:
return ERR_INVALID_PARAMETER;
}
}
Error NetSocketUnix::poll(PollType p_type, int p_timeout) const {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
@@ -418,6 +585,7 @@ Error NetSocketUnix::recv(uint8_t *p_buffer, int p_len, int &r_read) {
Error NetSocketUnix::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek) {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V(_family != Family::INET, ERR_UNAVAILABLE);
struct sockaddr_storage from;
socklen_t len = sizeof(struct sockaddr_storage);
@@ -459,7 +627,7 @@ Error NetSocketUnix::send(const uint8_t *p_buffer, int p_len, int &r_sent) {
int flags = 0;
#ifdef MSG_NOSIGNAL
if (_is_stream) {
if (_is_stream || _family == Family::UNIX) {
flags = MSG_NOSIGNAL;
}
#endif
@@ -482,6 +650,7 @@ Error NetSocketUnix::send(const uint8_t *p_buffer, int p_len, int &r_sent) {
Error NetSocketUnix::sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V(_family != Family::INET, ERR_UNAVAILABLE);
struct sockaddr_storage addr;
size_t addr_size = _set_addr_storage(&addr, p_ip, p_port, _ip_type);
@@ -580,9 +749,7 @@ int NetSocketUnix::get_available_bytes() const {
return len;
}
Error NetSocketUnix::get_socket_address(IPAddress *r_ip, uint16_t *r_port) const {
ERR_FAIL_COND_V(!is_open(), FAILED);
Error NetSocketUnix::_inet_get_socket_address(IPAddress *r_ip, uint16_t *r_port) const {
struct sockaddr_storage saddr;
socklen_t len = sizeof(saddr);
if (getsockname(_sock, (struct sockaddr *)&saddr, &len) != 0) {
@@ -594,17 +761,40 @@ Error NetSocketUnix::get_socket_address(IPAddress *r_ip, uint16_t *r_port) const
return OK;
}
Ref<NetSocket> NetSocketUnix::accept(IPAddress &r_ip, uint16_t &r_port) {
Ref<NetSocket> out;
ERR_FAIL_COND_V(!is_open(), out);
Error NetSocketUnix::get_socket_address(NetSocket::Address *r_addr) const {
ERR_FAIL_COND_V(!is_open(), FAILED);
switch (_family) {
case Family::INET: {
IPAddress ip;
uint16_t port = 0;
Error res = _inet_get_socket_address(&ip, &port);
ERR_FAIL_COND_V(res != OK, res);
if (r_addr) {
Address addr(ip, port);
*r_addr = addr;
}
} break;
case Family::UNIX: {
if (r_addr) {
*r_addr = Address(_unix_path);
}
} break;
case Family::NONE:
default:
return FAILED;
}
return OK;
}
Ref<NetSocket> NetSocketUnix::_inet_accept(IPAddress &r_ip, uint16_t &r_port) {
struct sockaddr_storage their_addr;
socklen_t size = sizeof(their_addr);
int fd = ::accept(_sock, (struct sockaddr *)&their_addr, &size);
if (fd == -1) {
_get_socket_error();
print_verbose("Error when accepting socket connection.");
return out;
return Ref<NetSocket>();
}
_set_ip_port(&their_addr, &r_ip, &r_port);
@@ -615,6 +805,48 @@ Ref<NetSocket> NetSocketUnix::accept(IPAddress &r_ip, uint16_t &r_port) {
return Ref<NetSocket>(ns);
}
Ref<NetSocket> NetSocketUnix::_unix_accept() {
struct sockaddr_un addr;
socklen_t addr_len = sizeof(addr);
int fd = ::accept(_sock, (struct sockaddr *)&addr, &addr_len);
if (fd == -1) {
_get_socket_error();
print_verbose("Error when accepting socket connection.");
return Ref<NetSocket>();
}
NetSocketUnix *ret = memnew(NetSocketUnix);
ret->_sock = fd;
ret->_family = _family;
ret->_unix_path = _unix_path;
ret->set_blocking_enabled(false);
return Ref<NetSocket>(ret);
}
Ref<NetSocket> NetSocketUnix::accept(NetSocket::Address &r_addr) {
Ref<NetSocket> out;
ERR_FAIL_COND_V(!is_open(), out);
switch (_family) {
case Family::INET: {
IPAddress ip;
uint16_t port;
out = _inet_accept(ip, port);
if (out.is_valid()) {
r_addr = Address(ip, port);
}
} break;
case Family::UNIX: {
out = _unix_accept();
} break;
case Family::NONE:
default:
break;
}
return out;
}
Error NetSocketUnix::join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) {
return _change_multicast_group(p_multi_address, p_if_name, true);
}

View File

@@ -35,14 +35,19 @@
#include "core/io/net_socket.h"
#include <sys/socket.h>
#include <sys/un.h>
class NetSocketUnix : public NetSocket {
GDSOFTCLASS(NetSocketUnix, NetSocket);
private:
int _sock = -1;
Family _family = Family::NONE;
IP::Type _ip_type = IP::TYPE_NONE;
bool _is_stream = false;
CharString _unix_path;
// If this is Family::UNIX,
bool _unlink_on_close = false;
enum NetError {
ERR_NET_WOULD_BLOCK,
@@ -63,6 +68,19 @@ protected:
static NetSocket *_create_func();
bool _can_use_ip(const IPAddress &p_ip, const bool p_for_bind) const;
bool _can_use_path(const CharString &p_path) const;
Error _inet_open(Type p_sock_type, IP::Type &r_ip_type);
Error _inet_bind(IPAddress p_addr, uint16_t p_port);
Error _inet_connect_to_host(IPAddress p_addr, uint16_t p_port);
Error _inet_get_socket_address(IPAddress *r_ip, uint16_t *r_port) const;
Ref<NetSocket> _inet_accept(IPAddress &r_ip, uint16_t &r_port);
static socklen_t _unix_set_sockaddr(struct sockaddr_un *p_addr, const CharString &p_path);
Error _unix_open();
Error _unix_bind(const CharString &p_path);
Error _unix_connect_to_host(const CharString &p_path);
Ref<NetSocket> _unix_accept();
public:
static void make_default();
@@ -70,21 +88,21 @@ public:
static void _set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ip, uint16_t *r_port);
static size_t _set_addr_storage(struct sockaddr_storage *p_addr, const IPAddress &p_ip, uint16_t p_port, IP::Type p_ip_type);
virtual Error open(Type p_sock_type, IP::Type &ip_type) override;
virtual Error open(Family p_family, Type p_sock_type, IP::Type &r_ip_type) override;
virtual void close() override;
virtual Error bind(IPAddress p_addr, uint16_t p_port) override;
virtual Error bind(Address p_addr) override;
virtual Error listen(int p_max_pending) override;
virtual Error connect_to_host(IPAddress p_host, uint16_t p_port) override;
virtual Error connect_to_host(Address p_addr) override;
virtual Error poll(PollType p_type, int timeout) const override;
virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) override;
virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) override;
virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) override;
virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) override;
virtual Ref<NetSocket> accept(IPAddress &r_ip, uint16_t &r_port) override;
virtual Ref<NetSocket> accept(Address &r_addr) override;
virtual bool is_open() const override;
virtual int get_available_bytes() const override;
virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const override;
virtual Error get_socket_address(Address *r_addr) const override;
virtual Error set_broadcasting_enabled(bool p_enabled) override;
virtual void set_blocking_enabled(bool p_enabled) override;

View File

@@ -217,7 +217,8 @@ void NetSocketWinSock::_set_socket(SOCKET p_sock, IP::Type p_ip_type, bool p_is_
_is_stream = p_is_stream;
}
Error NetSocketWinSock::open(Type p_sock_type, IP::Type &ip_type) {
Error NetSocketWinSock::open(Family p_family, Type p_sock_type, IP::Type &ip_type) {
ERR_FAIL_COND_V(p_family != Family::INET, ERR_UNAVAILABLE);
ERR_FAIL_COND_V(is_open(), ERR_ALREADY_IN_USE);
ERR_FAIL_COND_V(ip_type > IP::TYPE_ANY || ip_type < IP::TYPE_NONE, ERR_INVALID_PARAMETER);
@@ -275,12 +276,13 @@ void NetSocketWinSock::close() {
_is_stream = false;
}
Error NetSocketWinSock::bind(IPAddress p_addr, uint16_t p_port) {
Error NetSocketWinSock::bind(Address p_addr) {
ERR_FAIL_COND_V(!p_addr.is_inet(), ERR_UNAVAILABLE);
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!_can_use_ip(p_addr, true), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(!_can_use_ip(p_addr.ip(), true), ERR_INVALID_PARAMETER);
sockaddr_storage addr;
size_t addr_size = _set_addr_storage(&addr, p_addr, p_port, _ip_type);
size_t addr_size = _set_addr_storage(&addr, p_addr.ip(), p_addr.port(), _ip_type);
if (::bind(_sock, (struct sockaddr *)&addr, addr_size) != 0) {
NetError err = _get_socket_error();
@@ -305,12 +307,13 @@ Error NetSocketWinSock::listen(int p_max_pending) {
return OK;
}
Error NetSocketWinSock::connect_to_host(IPAddress p_host, uint16_t p_port) {
Error NetSocketWinSock::connect_to_host(Address p_addr) {
ERR_FAIL_COND_V(!p_addr.is_inet(), ERR_UNAVAILABLE);
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!_can_use_ip(p_host, false), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(!_can_use_ip(p_addr.ip(), false), ERR_INVALID_PARAMETER);
struct sockaddr_storage addr;
size_t addr_size = _set_addr_storage(&addr, p_host, p_port, _ip_type);
size_t addr_size = _set_addr_storage(&addr, p_addr.ip(), p_addr.port(), _ip_type);
if (::WSAConnect(_sock, (struct sockaddr *)&addr, addr_size, nullptr, nullptr, nullptr, nullptr) != 0) {
NetError err = _get_socket_error();
@@ -567,7 +570,7 @@ int NetSocketWinSock::get_available_bytes() const {
return len;
}
Error NetSocketWinSock::get_socket_address(IPAddress *r_ip, uint16_t *r_port) const {
Error NetSocketWinSock::get_socket_address(Address *r_addr) const {
ERR_FAIL_COND_V(!is_open(), FAILED);
struct sockaddr_storage saddr;
@@ -577,11 +580,16 @@ Error NetSocketWinSock::get_socket_address(IPAddress *r_ip, uint16_t *r_port) co
print_verbose("Error when reading local socket address.");
return FAILED;
}
_set_ip_port(&saddr, r_ip, r_port);
IPAddress ip;
uint16_t port = 0;
_set_ip_port(&saddr, &ip, &port);
if (r_addr) {
*r_addr = Address(ip, port);
}
return OK;
}
Ref<NetSocket> NetSocketWinSock::accept(IPAddress &r_ip, uint16_t &r_port) {
Ref<NetSocket> NetSocketWinSock::accept(Address &r_addr) {
Ref<NetSocket> out;
ERR_FAIL_COND_V(!is_open(), out);
@@ -594,7 +602,10 @@ Ref<NetSocket> NetSocketWinSock::accept(IPAddress &r_ip, uint16_t &r_port) {
return out;
}
_set_ip_port(&their_addr, &r_ip, &r_port);
IPAddress ip;
uint16_t port = 0;
_set_ip_port(&their_addr, &ip, &port);
r_addr = Address(ip, port);
NetSocketWinSock *ns = memnew(NetSocketWinSock);
ns->_set_socket(fd, _ip_type, _is_stream);

View File

@@ -70,21 +70,21 @@ public:
static void _set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ip, uint16_t *r_port);
static size_t _set_addr_storage(struct sockaddr_storage *p_addr, const IPAddress &p_ip, uint16_t p_port, IP::Type p_ip_type);
virtual Error open(Type p_sock_type, IP::Type &ip_type) override;
virtual Error open(Family p_family, Type p_sock_type, IP::Type &ip_type) override;
virtual void close() override;
virtual Error bind(IPAddress p_addr, uint16_t p_port) override;
virtual Error bind(Address p_addr) override;
virtual Error listen(int p_max_pending) override;
virtual Error connect_to_host(IPAddress p_host, uint16_t p_port) override;
virtual Error connect_to_host(Address p_addr) override;
virtual Error poll(PollType p_type, int timeout) const override;
virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) override;
virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) override;
virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) override;
virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) override;
virtual Ref<NetSocket> accept(IPAddress &r_ip, uint16_t &r_port) override;
virtual Ref<NetSocket> accept(Address &r_addr) override;
virtual bool is_open() const override;
virtual int get_available_bytes() const override;
virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const override;
virtual Error get_socket_address(Address *r_addr) const override;
virtual Error set_broadcasting_enabled(bool p_enabled) override;
virtual void set_blocking_enabled(bool p_enabled) override;

View File

@@ -243,8 +243,7 @@ ScriptEditorDebugger *EditorDebuggerNode::get_default_debugger() const {
}
String EditorDebuggerNode::get_server_uri() const {
ERR_FAIL_COND_V(server.is_null(), "");
return server->get_uri();
return server.is_valid() ? server->get_uri() : "";
}
void EditorDebuggerNode::set_keep_open(bool p_keep_open) {

View File

@@ -31,30 +31,36 @@
#include "editor_debugger_server.h"
#include "core/io/tcp_server.h"
#include "core/io/uds_server.h"
#include "core/os/thread.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/settings/editor_settings.h"
class EditorDebuggerServerTCP : public EditorDebuggerServer {
GDSOFTCLASS(EditorDebuggerServerTCP, EditorDebuggerServer);
template <typename T>
class EditorDebuggerServerSocket : public EditorDebuggerServer {
GDSOFTCLASS(EditorDebuggerServerSocket, EditorDebuggerServer);
private:
Ref<TCPServer> server;
protected:
Ref<T> server;
String endpoint;
public:
static EditorDebuggerServer *create(const String &p_protocol);
virtual void poll() override {}
virtual String get_uri() const override;
virtual Error start(const String &p_uri) override;
virtual void stop() override;
virtual bool is_active() const override;
virtual bool is_connection_available() const override;
virtual Ref<RemoteDebuggerPeer> take_connection() override;
EditorDebuggerServerTCP();
EditorDebuggerServerSocket();
};
class EditorDebuggerServerTCP : public EditorDebuggerServerSocket<TCPServer> {
public:
static EditorDebuggerServer *create(const String &p_protocol);
virtual Error start(const String &p_uri) override;
};
EditorDebuggerServer *EditorDebuggerServerTCP::create(const String &p_protocol) {
@@ -62,11 +68,13 @@ EditorDebuggerServer *EditorDebuggerServerTCP::create(const String &p_protocol)
return memnew(EditorDebuggerServerTCP);
}
EditorDebuggerServerTCP::EditorDebuggerServerTCP() {
template <typename T>
EditorDebuggerServerSocket<T>::EditorDebuggerServerSocket() {
server.instantiate();
}
String EditorDebuggerServerTCP::get_uri() const {
template <typename T>
String EditorDebuggerServerSocket<T>::get_uri() const {
return endpoint;
}
@@ -104,29 +112,61 @@ Error EditorDebuggerServerTCP::start(const String &p_uri) {
return OK;
}
void EditorDebuggerServerTCP::stop() {
template <typename T>
void EditorDebuggerServerSocket<T>::stop() {
server->stop();
}
bool EditorDebuggerServerTCP::is_active() const {
template <typename T>
bool EditorDebuggerServerSocket<T>::is_active() const {
return server->is_listening();
}
bool EditorDebuggerServerTCP::is_connection_available() const {
template <typename T>
bool EditorDebuggerServerSocket<T>::is_connection_available() const {
return server->is_listening() && server->is_connection_available();
}
Ref<RemoteDebuggerPeer> EditorDebuggerServerTCP::take_connection() {
ERR_FAIL_COND_V(!is_connection_available(), Ref<RemoteDebuggerPeer>());
return memnew(RemoteDebuggerPeerTCP(server->take_connection()));
template <typename T>
Ref<RemoteDebuggerPeer> EditorDebuggerServerSocket<T>::take_connection() {
const Ref<RemoteDebuggerPeer> out;
ERR_FAIL_COND_V(!is_connection_available(), out);
Ref<StreamPeerSocket> stream = server->take_socket_connection();
ERR_FAIL_COND_V(stream.is_null(), out);
return memnew(RemoteDebuggerPeerTCP(stream));
}
class EditorDebuggerServerUDS : public EditorDebuggerServerSocket<UDSServer> {
public:
static EditorDebuggerServer *create(const String &p_protocol);
virtual Error start(const String &p_uri) override;
};
EditorDebuggerServer *EditorDebuggerServerUDS::create(const String &p_protocol) {
ERR_FAIL_COND_V(p_protocol != "unix://", nullptr);
return memnew(EditorDebuggerServerUDS);
}
Error EditorDebuggerServerUDS::start(const String &p_uri) {
String bind_path = p_uri.is_empty() ? String("/tmp/godot_debugger.sock") : p_uri.replace("unix://", "");
const Error err = server->listen(bind_path);
if (err != OK) {
EditorNode::get_log()->add_message(vformat("Cannot listen at path %s, remote debugging unavailable.", bind_path), EditorLog::MSG_TYPE_ERROR);
return err;
}
endpoint = "unix://" + bind_path;
return OK;
}
/// EditorDebuggerServer
HashMap<StringName, EditorDebuggerServer::CreateServerFunc> EditorDebuggerServer::protocols;
EditorDebuggerServer *EditorDebuggerServer::create(const String &p_protocol) {
ERR_FAIL_COND_V(!protocols.has(p_protocol), nullptr);
return protocols[p_protocol](p_protocol);
CreateServerFunc *create_fn = protocols.getptr(p_protocol);
ERR_FAIL_NULL_V(create_fn, nullptr);
return (*create_fn)(p_protocol);
}
void EditorDebuggerServer::register_protocol_handler(const String &p_protocol, CreateServerFunc p_func) {
@@ -136,6 +176,9 @@ void EditorDebuggerServer::register_protocol_handler(const String &p_protocol, C
void EditorDebuggerServer::initialize() {
register_protocol_handler("tcp://", EditorDebuggerServerTCP::create);
#if defined(UNIX_ENABLED)
register_protocol_handler("unix://", EditorDebuggerServerUDS::create);
#endif
}
void EditorDebuggerServer::deinitialize() {

View File

@@ -314,8 +314,12 @@ void EditorRunBar::_run_scene(const String &p_scene_path, const Vector<String> &
if (!EditorNode::get_singleton()->call_build()) {
return;
}
EditorDebuggerNode::get_singleton()->start();
// Use the existing URI, in case it is overridden by the CLI.
String uri = EditorDebuggerNode::get_singleton()->get_server_uri();
if (uri.is_empty()) {
uri = "tcp://";
}
EditorDebuggerNode::get_singleton()->start(uri);
Error error = editor_run.run(run_filename, write_movie_file, p_run_args);
if (error != OK) {
EditorDebuggerNode::get_singleton()->stop();

View File

@@ -8,6 +8,18 @@ Add new entries at the end of the file.
## Changes between 4.5-stable and 4.6-stable
GH-107954
---------
Validate extension JSON: API was removed: classes/TCPServer/methods/is_connection_available
Validate extension JSON: API was removed: classes/TCPServer/methods/is_listening
Validate extension JSON: API was removed: classes/TCPServer/methods/stop
Validate extension JSON: API was removed: classes/StreamPeerTCP/methods/disconnect_from_host
Validate extension JSON: API was removed: classes/StreamPeerTCP/methods/get_status
Validate extension JSON: API was removed: classes/StreamPeerTCP/methods/poll
These were moved to the parent classes, and are still available.
GH-110250
---------
Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Control/methods/grab_focus': arguments

View File

@@ -43,21 +43,21 @@ protected:
public:
static void make_default();
virtual Error open(Type p_sock_type, IP::Type &ip_type) override { return ERR_UNAVAILABLE; }
virtual Error open(Family p_family, Type p_sock_type, IP::Type &ip_type) override { return ERR_UNAVAILABLE; }
virtual void close() override {}
virtual Error bind(IPAddress p_addr, uint16_t p_port) override { return ERR_UNAVAILABLE; }
virtual Error bind(Address p_addr) override { return ERR_UNAVAILABLE; }
virtual Error listen(int p_max_pending) override { return ERR_UNAVAILABLE; }
virtual Error connect_to_host(IPAddress p_host, uint16_t p_port) override { return ERR_UNAVAILABLE; }
virtual Error connect_to_host(Address p_addr) override { return ERR_UNAVAILABLE; }
virtual Error poll(PollType p_type, int timeout) const override { return ERR_UNAVAILABLE; }
virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) override { return ERR_UNAVAILABLE; }
virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) override { return ERR_UNAVAILABLE; }
virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) override { return ERR_UNAVAILABLE; }
virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) override { return ERR_UNAVAILABLE; }
virtual Ref<NetSocket> accept(IPAddress &r_ip, uint16_t &r_port) override { return Ref<NetSocket>(); }
virtual Ref<NetSocket> accept(Address &r_addr) override { return Ref<NetSocket>(); }
virtual bool is_open() const override { return false; }
virtual int get_available_bytes() const override { return -1; }
virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const override { return ERR_UNAVAILABLE; }
virtual Error get_socket_address(Address *r_addr) const override { return ERR_UNAVAILABLE; }
virtual Error set_broadcasting_enabled(bool p_enabled) override { return ERR_UNAVAILABLE; }
virtual void set_blocking_enabled(bool p_enabled) override {}

View File

@@ -0,0 +1,302 @@
/**************************************************************************/
/* test_uds_server.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/file_access.h"
#include "core/io/stream_peer_uds.h"
#include "core/io/uds_server.h"
#include "tests/test_macros.h"
#include <functional>
namespace TestUDSServer {
#ifdef UNIX_ENABLED
const String SOCKET_PATH = "/tmp/godot_test_uds_socket";
const uint32_t SLEEP_DURATION = 1000;
const uint64_t MAX_WAIT_USEC = 2000000;
void wait_for_condition(std::function<bool()> f_test) {
const uint64_t time = OS::get_singleton()->get_ticks_usec();
while (!f_test() && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) {
OS::get_singleton()->delay_usec(SLEEP_DURATION);
}
}
void cleanup_socket_file() {
// Remove socket file if it exists
if (FileAccess::exists(SOCKET_PATH)) {
DirAccess::remove_absolute(SOCKET_PATH);
}
}
Ref<UDSServer> create_server(const String &p_path) {
cleanup_socket_file();
Ref<UDSServer> server;
server.instantiate();
REQUIRE_EQ(server->listen(p_path), Error::OK);
REQUIRE(server->is_listening());
CHECK_FALSE(server->is_connection_available());
return server;
}
Ref<StreamPeerUDS> create_client(const String &p_path) {
Ref<StreamPeerUDS> client;
client.instantiate();
Error err = client->connect_to_host(p_path);
REQUIRE_EQ(err, Error::OK);
// UDS connections may be immediately connected or in connecting state
StreamPeerUDS::Status status = client->get_status();
REQUIRE((status == StreamPeerUDS::STATUS_CONNECTED || status == StreamPeerUDS::STATUS_CONNECTING));
if (status == StreamPeerUDS::STATUS_CONNECTED) {
CHECK_EQ(client->get_connected_path(), p_path);
}
return client;
}
Ref<StreamPeerUDS> accept_connection(Ref<UDSServer> &p_server) {
wait_for_condition([&]() {
return p_server->is_connection_available();
});
REQUIRE(p_server->is_connection_available());
Ref<StreamPeerUDS> client_from_server = p_server->take_connection();
REQUIRE(client_from_server.is_valid());
CHECK_EQ(client_from_server->get_status(), StreamPeerUDS::STATUS_CONNECTED);
return client_from_server;
}
TEST_CASE("[UDSServer] Instantiation") {
Ref<UDSServer> server;
server.instantiate();
REQUIRE(server.is_valid());
CHECK_FALSE(server->is_listening());
}
TEST_CASE("[UDSServer] Accept a connection and receive/send data") {
Ref<UDSServer> server = create_server(SOCKET_PATH);
Ref<StreamPeerUDS> client = create_client(SOCKET_PATH);
Ref<StreamPeerUDS> client_from_server = accept_connection(server);
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerUDS::STATUS_CONNECTED;
});
CHECK_EQ(client->get_status(), StreamPeerUDS::STATUS_CONNECTED);
// Sending data from client to server.
const String hello_world = "Hello World!";
client->put_string(hello_world);
CHECK_EQ(client_from_server->get_string(), hello_world);
// Sending data from server to client.
const float pi = 3.1415;
client_from_server->put_float(pi);
CHECK_EQ(client->get_float(), pi);
client->disconnect_from_host();
server->stop();
CHECK_FALSE(server->is_listening());
cleanup_socket_file();
}
TEST_CASE("[UDSServer] Handle multiple clients at the same time") {
Ref<UDSServer> server = create_server(SOCKET_PATH);
Vector<Ref<StreamPeerUDS>> clients;
for (int i = 0; i < 5; i++) {
clients.push_back(create_client(SOCKET_PATH));
}
Vector<Ref<StreamPeerUDS>> clients_from_server;
for (int i = 0; i < clients.size(); i++) {
clients_from_server.push_back(accept_connection(server));
}
wait_for_condition([&]() {
bool should_exit = true;
for (Ref<StreamPeerUDS> &c : clients) {
if (c->poll() != Error::OK) {
return true;
}
StreamPeerUDS::Status status = c->get_status();
if (status != StreamPeerUDS::STATUS_CONNECTED && status != StreamPeerUDS::STATUS_CONNECTING) {
return true;
}
if (status != StreamPeerUDS::STATUS_CONNECTED) {
should_exit = false;
}
}
return should_exit;
});
for (Ref<StreamPeerUDS> &c : clients) {
REQUIRE_EQ(c->get_status(), StreamPeerUDS::STATUS_CONNECTED);
}
// Sending data from each client to server.
for (int i = 0; i < clients.size(); i++) {
String hello_client = "Hello " + itos(i);
clients[i]->put_string(hello_client);
CHECK_EQ(clients_from_server[i]->get_string(), hello_client);
}
for (Ref<StreamPeerUDS> &c : clients) {
c->disconnect_from_host();
}
server->stop();
cleanup_socket_file();
}
TEST_CASE("[UDSServer] When stopped shouldn't accept new connections") {
Ref<UDSServer> server = create_server(SOCKET_PATH);
Ref<StreamPeerUDS> client = create_client(SOCKET_PATH);
Ref<StreamPeerUDS> client_from_server = accept_connection(server);
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerUDS::STATUS_CONNECTED;
});
CHECK_EQ(client->get_status(), StreamPeerUDS::STATUS_CONNECTED);
// Sending data from client to server.
const String hello_world = "Hello World!";
client->put_string(hello_world);
CHECK_EQ(client_from_server->get_string(), hello_world);
client->disconnect_from_host();
server->stop();
CHECK_FALSE(server->is_listening());
// Clean up the socket file after server stops
cleanup_socket_file();
// Try to connect to non-existent socket
Ref<StreamPeerUDS> new_client;
new_client.instantiate();
Error err = new_client->connect_to_host(SOCKET_PATH);
// Connection should fail since socket doesn't exist
CHECK_NE(err, Error::OK);
CHECK_FALSE(server->is_connection_available());
cleanup_socket_file();
}
TEST_CASE("[UDSServer] Should disconnect client") {
Ref<UDSServer> server = create_server(SOCKET_PATH);
Ref<StreamPeerUDS> client = create_client(SOCKET_PATH);
Ref<StreamPeerUDS> client_from_server = accept_connection(server);
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerUDS::STATUS_CONNECTED;
});
CHECK_EQ(client->get_status(), StreamPeerUDS::STATUS_CONNECTED);
// Sending data from client to server.
const String hello_world = "Hello World!";
client->put_string(hello_world);
CHECK_EQ(client_from_server->get_string(), hello_world);
client_from_server->disconnect_from_host();
server->stop();
CHECK_FALSE(server->is_listening());
// Wait for disconnection
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerUDS::STATUS_NONE;
});
// Wait for disconnection
wait_for_condition([&]() {
return client_from_server->poll() != Error::OK || client_from_server->get_status() == StreamPeerUDS::STATUS_NONE;
});
CHECK_EQ(client->get_status(), StreamPeerUDS::STATUS_NONE);
CHECK_EQ(client_from_server->get_status(), StreamPeerUDS::STATUS_NONE);
ERR_PRINT_OFF;
CHECK_EQ(client->get_string(), String());
CHECK_EQ(client_from_server->get_string(), String());
ERR_PRINT_ON;
cleanup_socket_file();
}
TEST_CASE("[UDSServer] Test with different socket paths") {
// Test with a different socket path
const String alt_socket_path = "/tmp/godot_test_uds_socket_alt";
// Clean up before test
if (FileAccess::exists(alt_socket_path)) {
DirAccess::remove_absolute(alt_socket_path);
}
Ref<UDSServer> server = create_server(alt_socket_path);
Ref<StreamPeerUDS> client = create_client(alt_socket_path);
Ref<StreamPeerUDS> client_from_server = accept_connection(server);
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerUDS::STATUS_CONNECTED;
});
CHECK_EQ(client->get_status(), StreamPeerUDS::STATUS_CONNECTED);
// Test data exchange
const int test_number = 42;
client->put_32(test_number);
CHECK_EQ(client_from_server->get_32(), test_number);
client->disconnect_from_host();
server->stop();
// Clean up
if (FileAccess::exists(alt_socket_path)) {
DirAccess::remove_absolute(alt_socket_path);
}
}
#endif
} // namespace TestUDSServer

View File

@@ -62,6 +62,7 @@
#include "tests/core/io/test_stream_peer_gzip.h"
#include "tests/core/io/test_tcp_server.h"
#include "tests/core/io/test_udp_server.h"
#include "tests/core/io/test_uds_server.h"
#include "tests/core/io/test_xml_parser.h"
#include "tests/core/math/test_aabb.h"
#include "tests/core/math/test_astar.h"

View File

@@ -74,7 +74,7 @@ public:
ENetUDP() {
sock = Ref<NetSocket>(NetSocket::create());
IP::Type ip_type = IP::TYPE_ANY;
sock->open(NetSocket::TYPE_UDP, ip_type);
sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type);
}
~ENetUDP() {
@@ -88,11 +88,15 @@ public:
Error bind(IPAddress p_ip, uint16_t p_port) {
local_address = p_ip;
bound = true;
return sock->bind(p_ip, p_port);
NetSocket::Address addr(p_ip, p_port);
return sock->bind(addr);
}
Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) {
Error err = sock->get_socket_address(r_ip, r_port);
NetSocket::Address addr;
Error err = sock->get_socket_address(&addr);
*r_ip = addr.ip();
*r_port = addr.port();
if (bound) {
*r_ip = local_address;
}