diff --git a/client/clinet.cpp b/client/clinet.cpp index e10cb90ed4..7995969713 100644 --- a/client/clinet.cpp +++ b/client/clinet.cpp @@ -16,6 +16,7 @@ #endif // Qt +#include #include #include #include @@ -132,37 +133,48 @@ static int try_to_connect(const QUrl &url, char *errbuf, int errbufsize) (void) fc_strlcpy( errbuf, _("Canceled previous connection, trying new connection."), errbufsize); - client.conn.sock->abort(); + if (auto tcp = qobject_cast(client.conn.sock)) { + tcp->abort(); + } else if (auto local = + qobject_cast(client.conn.sock)) { + local->abort(); + } connect_to = url_copy; } else { (void) fc_strlcpy(errbuf, _("Connection in progress."), errbufsize); return -1; } } + + // Reset + if (client.conn.sock) { + client.conn.sock->disconnect(client.conn.sock); + client.conn.sock->deleteLater(); + } client.conn.used = true; // Now there will be a connection :) - client.conn.sock->disconnect(client.conn.sock); // Connect if (!client.conn.sock) { - client.conn.sock = new QTcpSocket; + auto sock = new QTcpSocket; + client.conn.sock = sock; + QObject::connect( + sock, + QOverload::of(&QAbstractSocket::error), + [] { + if (client.conn.sock != nullptr) { + log_debug("%s", qUtf8Printable(client.conn.sock->errorString())); + real_output_window_append(client.conn.sock->errorString(), NULL, + -1); + } + client.conn.used = false; + }); + QObject::connect(sock, &QTcpSocket::connected, + [userName = url.userName()] { + make_connection(client.conn.sock, userName); + }); + sock->connectToHost(url.host(), url.port()); } - QObject::connect( - client.conn.sock, - QOverload::of(&QAbstractSocket::error), - [] { - if (client.conn.sock != nullptr) { - log_debug("%s", qUtf8Printable(client.conn.sock->errorString())); - real_output_window_append(client.conn.sock->errorString(), NULL, - -1); - } - client.conn.used = false; - }); - QObject::connect(client.conn.sock, &QTcpSocket::connected, - [userName = url.userName()] { - make_connection(client.conn.sock, userName); - }); - client.conn.sock->connectToHost(url.host(), url.port()); return 0; } } // namespace @@ -192,7 +204,7 @@ int connect_to_server(const QUrl &url, char *errbuf, int errbufsize) /** Called after a connection is completed (e.g., in try_to_connect). */ -void make_connection(QTcpSocket *sock, const QString &username) +void make_connection(QIODevice *sock, const QString &username) { struct packet_server_join_req req; @@ -260,7 +272,7 @@ void disconnect_from_server() */ static int read_from_connection(struct connection *pc, bool block) { - QTcpSocket *socket = pc->sock; + auto socket = pc->sock; bool have_data_for_server = (pc->used && pc->send_buffer && 0 < pc->send_buffer->ndata); @@ -276,7 +288,7 @@ static int read_from_connection(struct connection *pc, bool block) if (block) { // Wait (and block the main event loop) until we get some data - socket->waitForReadyRead(); + socket->waitForReadyRead(-1); } // Consume everything @@ -301,7 +313,7 @@ static int read_from_connection(struct connection *pc, bool block) This function is called when the client received a new input from the server. */ -void input_from_server(QTcpSocket *sock) +void input_from_server(QIODevice *sock) { int nb; diff --git a/client/clinet.h b/client/clinet.h index be71225eb9..d2e85e51fe 100644 --- a/client/clinet.h +++ b/client/clinet.h @@ -11,15 +11,15 @@ #pragma once // Forward declarations -class QTcpSocket; +class QIODevice; class QString; class QUrl; int connect_to_server(const QUrl &url, char *errbuf, int errbufsize); -void make_connection(QTcpSocket *sock, const QString &username); +void make_connection(QIODevice *sock, const QString &username); -void input_from_server(QTcpSocket *sock); +void input_from_server(QIODevice *sock); void disconnect_from_server(); void try_to_autoconnect(const QUrl &url); diff --git a/client/gui-qt/fc_client.cpp b/client/gui-qt/fc_client.cpp index 6037a6024e..9a2dd88a44 100644 --- a/client/gui-qt/fc_client.cpp +++ b/client/gui-qt/fc_client.cpp @@ -277,7 +277,7 @@ enum client_pages fc_client::current_page() { return page; } /** Add notifier for server input */ -void fc_client::add_server_source(QTcpSocket *sock) +void fc_client::add_server_source(QIODevice *sock) { connect(sock, &QIODevice::readyRead, this, &fc_client::server_input); @@ -300,7 +300,7 @@ void fc_client::closeEvent(QCloseEvent *event) */ void fc_client::server_input() { - if (auto *socket = dynamic_cast(sender())) { + if (auto *socket = dynamic_cast(sender())) { input_from_server(socket); } } diff --git a/client/gui-qt/fc_client.h b/client/gui-qt/fc_client.h index 5745d788ae..201ae0b2f9 100644 --- a/client/gui-qt/fc_client.h +++ b/client/gui-qt/fc_client.h @@ -112,7 +112,7 @@ class fc_client : public QMainWindow { ~fc_client() override; QWidget *pages[static_cast(PAGE_GAME) + 2]; void fc_main(QApplication *); - void add_server_source(QTcpSocket *socket); + void add_server_source(QIODevice *socket); enum client_pages current_page(); diff --git a/client/gui-qt/gui_main.cpp b/client/gui-qt/gui_main.cpp index a73c281895..9f93e10d61 100644 --- a/client/gui-qt/gui_main.cpp +++ b/client/gui-qt/gui_main.cpp @@ -177,7 +177,7 @@ void qtg_sound_bell() This function is called after the client succesfully has connected to the server. */ -void qtg_add_net_input(QTcpSocket *sock) { king()->add_server_source(sock); } +void qtg_add_net_input(QIODevice *sock) { king()->add_server_source(sock); } /** Stop waiting for any server network data. See add_net_input(). diff --git a/client/gui-qt/qtg_cxxside.h b/client/gui-qt/qtg_cxxside.h index ad10c91379..ce183607c0 100644 --- a/client/gui-qt/qtg_cxxside.h +++ b/client/gui-qt/qtg_cxxside.h @@ -84,7 +84,7 @@ void qtg_canvas_put_text(QPixmap *pcanvas, int canvas_x, int canvas_y, void qtg_set_rulesets(int num_rulesets, QStringList rulesets); void qtg_options_extra_init(); -void qtg_add_net_input(QTcpSocket *sock); +void qtg_add_net_input(QIODevice *sock); void qtg_remove_net_input(); void qtg_real_conn_list_dialog_update(void *unused); void qtg_close_connection_dialog(); diff --git a/client/gui_interface.cpp b/client/gui_interface.cpp index e41268a166..44de1116ef 100644 --- a/client/gui_interface.cpp +++ b/client/gui_interface.cpp @@ -291,7 +291,7 @@ void options_extra_init(void) { funcs.options_extra_init(); } /** Call add_net_input callback */ -void add_net_input(QTcpSocket *sock) { funcs.add_net_input(sock); } +void add_net_input(QIODevice *sock) { funcs.add_net_input(sock); } /** Call remove_net_input callback diff --git a/client/gui_interface.h b/client/gui_interface.h index 8684cdd4c0..26850c23b3 100644 --- a/client/gui_interface.h +++ b/client/gui_interface.h @@ -92,7 +92,7 @@ struct gui_funcs { void (*set_rulesets)(int num_rulesets, QStringList rulesets); void (*options_extra_init)(); - void (*add_net_input)(QTcpSocket *sock); + void (*add_net_input)(QIODevice *sock); void (*remove_net_input)(); void (*real_conn_list_dialog_update)(void *unused); void (*close_connection_dialog)(); diff --git a/client/include/gui_main_g.h b/client/include/gui_main_g.h index ba86bd593c..4bf6ab45c8 100644 --- a/client/include/gui_main_g.h +++ b/client/include/gui_main_g.h @@ -11,7 +11,7 @@ #pragma once // Forward declarations -class QTcpSocket; +class QIODevice; // utility #include "support.h" // bool type @@ -28,7 +28,7 @@ GUI_FUNC_PROTO(void, options_extra_init, void) GUI_FUNC_PROTO(void, real_conn_list_dialog_update, void *) GUI_FUNC_PROTO(void, sound_bell, void) -GUI_FUNC_PROTO(void, add_net_input, QTcpSocket *) +GUI_FUNC_PROTO(void, add_net_input, QIODevice *) GUI_FUNC_PROTO(void, remove_net_input, void) GUI_FUNC_PROTO(void, set_unit_icon, int idx, struct unit *punit) diff --git a/common/networking/connection.cpp b/common/networking/connection.cpp index 3ad36ffdf2..0190612093 100644 --- a/common/networking/connection.cpp +++ b/common/networking/connection.cpp @@ -16,6 +16,7 @@ #endif // Qt +#include #include // utility @@ -54,7 +55,7 @@ static void default_conn_close_callback(struct connection *pconn) { fc_assert_msg(conn_close_callback != default_conn_close_callback, "Closing a socket (%s) before calling " - "close_socket_set_callback().", + "connections_set_close_callback().", conn_description(pconn)); } @@ -69,13 +70,13 @@ void connections_set_close_callback(conn_close_fn_t func) /** Call the conn_close_callback. */ -void connection_close(struct connection *pconn, const char *reason) +void connection_close(struct connection *pconn, const QString &reason) { fc_assert_ret(NULL != pconn); if (NULL != reason && pconn->closing_reason.isEmpty()) { // NB: we don't overwrite the original reason. - pconn->closing_reason = QString::fromUtf8(reason); + pconn->closing_reason = reason; } (*conn_close_callback)(pconn); @@ -110,7 +111,7 @@ static bool buffer_ensure_free_extra_space(struct socket_packet_buffer *buf, >0 : number of bytes read =0 : non-blocking sockets only; no data read, would block */ -int read_socket_data(QTcpSocket *sock, struct socket_packet_buffer *buffer) +int read_socket_data(QIODevice *sock, struct socket_packet_buffer *buffer) { int didget; @@ -187,7 +188,11 @@ void flush_connection_send_buffer_all(struct connection *pc) } } if (pc && pc->sock) { - pc->sock->flush(); + if (auto socket = qobject_cast(pc->sock)) { + socket->flush(); + } else if (auto socket = qobject_cast(pc->sock)) { + socket->flush(); + } } } @@ -204,7 +209,11 @@ static void flush_connection_send_buffer_packets(struct connection *pc) } } if (pc && pc->sock) { - pc->sock->flush(); + if (auto socket = qobject_cast(pc->sock)) { + socket->flush(); + } else if (auto socket = qobject_cast(pc->sock)) { + socket->flush(); + } } } diff --git a/common/networking/connection.h b/common/networking/connection.h index caf39614eb..0b16367d09 100644 --- a/common/networking/connection.h +++ b/common/networking/connection.h @@ -30,7 +30,8 @@ #include "fc_types.h" // Forward declarations -class QTcpSocket; +class QIODevice; +class QString; struct conn_pattern_list; struct genhash; @@ -126,7 +127,7 @@ struct packet_header { ***********************************************************/ struct connection { int id; /* used for server/client communication */ - QTcpSocket *sock = nullptr; + QIODevice *sock = nullptr; bool used; bool established; // have negotiated initial packets struct packet_header packet_header; @@ -261,9 +262,9 @@ struct connection { typedef void (*conn_close_fn_t)(struct connection *pconn); void connections_set_close_callback(conn_close_fn_t func); -void connection_close(struct connection *pconn, const char *reason); +void connection_close(struct connection *pconn, const QString &reason); -int read_socket_data(QTcpSocket *sock, struct socket_packet_buffer *buffer); +int read_socket_data(QIODevice *sock, struct socket_packet_buffer *buffer); void flush_connection_send_buffer_all(struct connection *pc); bool connection_send_data(struct connection *pconn, const unsigned char *data, int len); diff --git a/server/connecthand.cpp b/server/connecthand.cpp index e973be6dcb..1a128a7798 100644 --- a/server/connecthand.cpp +++ b/server/connecthand.cpp @@ -957,7 +957,7 @@ bool connection_delegate_restore(struct connection *pconn) Close a connection. Use this in the server to take care of delegation stuff (reset the username of the controlled connection). */ -void connection_close_server(struct connection *pconn, const char *reason) +void connection_close_server(struct connection *pconn, const QString &reason) { // Restore possible delegations before the connection is closed. connection_delegate_restore(pconn); diff --git a/server/connecthand.h b/server/connecthand.h index 4c348fdc46..ff2e02cd72 100644 --- a/server/connecthand.h +++ b/server/connecthand.h @@ -15,6 +15,8 @@ #include "fc_types.h" +class QString; + struct connection; struct conn_list; struct packet_authentication_reply; @@ -44,4 +46,5 @@ bool connection_delegate_take(struct connection *pconn, struct player *pplayer); bool connection_delegate_restore(struct connection *pconn); -void connection_close_server(struct connection *pconn, const char *reason); +void connection_close_server(struct connection *pconn, + const QString &reason); diff --git a/server/sernet.cpp b/server/sernet.cpp index 48e8f9b23c..34645cbe89 100644 --- a/server/sernet.cpp +++ b/server/sernet.cpp @@ -24,6 +24,7 @@ // Qt #include #include +#include #include #include #include @@ -127,7 +128,7 @@ void close_connections_and_socket() conn_list_destroy(game.all_connections); conn_list_destroy(game.est_connections); - if (srvarg.announce != ANNOUNCE_NONE) { + if (srvarg.announce != ANNOUNCE_NONE && udp_socket) { udp_socket->close(); delete udp_socket; udp_socket = nullptr; @@ -314,7 +315,8 @@ static const char *makeup_connection_name(int *id) Returns 0 on success, -1 on failure (bad accept(), or too many connections). */ -int server_make_connection(QTcpSocket *new_sock, const QString &client_addr) +int server_make_connection(QIODevice *new_sock, const QString &client_addr, + const QString &ip_addr) { civtimer *timer; int i; @@ -346,8 +348,7 @@ int server_make_connection(QTcpSocket *new_sock, const QString &client_addr) sz_strlcpy(pconn->username, makeup_connection_name(&pconn->id)); pconn->addr = client_addr; - sz_strlcpy(pconn->server.ipaddr, - qUtf8Printable(new_sock->peerAddress().toString())); + sz_strlcpy(pconn->server.ipaddr, qUtf8Printable(ip_addr)); conn_list_append(game.all_connections, pconn); @@ -373,9 +374,27 @@ int server_make_connection(QTcpSocket *new_sock, const QString &client_addr) Open server socket to be used to accept client connections and open a server socket for server LAN announcements. */ -QTcpServer *server_open_socket() +optional_socket_server server_open_socket() { - auto *server = new QTcpServer; + // Local socket mode + if (!srvarg.local_addr.isEmpty()) { + auto server = std::make_unique(); + if (server->listen(srvarg.local_addr)) { + connections_set_close_callback(server_conn_close_callback); + + // Don't do LAN announcements + return server; + } else { + qCritical().noquote() + << QString(_("Server: cannot listen on local socket %1: %2")) + .arg(srvarg.local_addr) + .arg(server->errorString()); + return std::nullopt; + } + } + + // TCP socket mode + auto server = std::make_unique(); int max = srvarg.port + 100; for (; srvarg.port < max; ++srvarg.port) { @@ -400,7 +419,7 @@ QTcpServer *server_open_socket() _("Server: cannot listen on port %1: %2")) .arg(srvarg.port) .arg(server->errorString()))); - return server; + return std::nullopt; } } @@ -546,7 +565,7 @@ void get_lanserver_announcement() return; } - if (udp_socket->hasPendingDatagrams()) { + if (udp_socket && udp_socket->hasPendingDatagrams()) { QNetworkDatagram qnd = udp_socket->receiveDatagram(); auto data = qnd.data(); dio_input_init(&din, data.constData(), 1); diff --git a/server/sernet.h b/server/sernet.h index 3eefc7d60a..bd2fe4abc4 100644 --- a/server/sernet.h +++ b/server/sernet.h @@ -10,9 +10,14 @@ **************************************************************************/ #pragma once +#include +#include +#include + // Forward declarations +class QIODevice; +class QLocalServer; class QTcpServer; -class QTcpSocket; class QString; struct connection; @@ -23,13 +28,18 @@ struct connection; #define SERVER_LAN_TTL 1 #define SERVER_LAN_VERSION 2 -QTcpServer *server_open_socket(); +using socket_server = + std::variant, std::unique_ptr>; +using optional_socket_server = std::optional; + +optional_socket_server server_open_socket(); void flush_packets(); void incoming_client_packets(connection *pconn); void close_connections_and_socket(); void really_close_connections(); void init_connections(); -int server_make_connection(QTcpSocket *new_sock, const QString &client_addr); +int server_make_connection(QIODevice *new_sock, const QString &client_addr, + const QString &ip_addr); void connection_ping(struct connection *pconn); void handle_conn_pong(struct connection *pconn); void handle_client_heartbeat(struct connection *pconn); diff --git a/server/server.cpp b/server/server.cpp index 80f7885a54..655365cc83 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -127,7 +127,7 @@ void fc_interface_init_server() /** Server initialization. */ -std::pair srv_prepare() +optional_socket_server srv_prepare() { // make sure it's initialized srv_init(); @@ -137,10 +137,9 @@ std::pair srv_prepare() con_log_init(srvarg.log_filename); // logging available after this point - auto *tcp_server = server_open_socket(); - if (!tcp_server->isListening()) { - // Don't even try to start a game. - return std::make_pair(tcp_server, false); + auto server = server_open_socket(); + if (!server) { + return std::nullopt; } #if IS_BETA_VERSION @@ -170,7 +169,7 @@ std::pair srv_prepare() success = fcdb_init(qUtf8Printable(srvarg.fcdb_conf)); if (!success) { - return std::make_pair(tcp_server, false); + return std::nullopt; } } @@ -182,7 +181,7 @@ std::pair srv_prepare() if (testfilename.isEmpty()) { qFatal(_("Ruleset directory \"%s\" not found"), qUtf8Printable(srvarg.ruleset)); - return std::make_pair(tcp_server, false); + return std::nullopt; } sz_strlcpy(game.server.rulesetdir, qUtf8Printable(srvarg.ruleset)); } @@ -206,11 +205,11 @@ std::pair srv_prepare() || !send_server_info_to_metaserver(META_INFO)) { con_write(C_FAIL, _("Not starting without explicitly requested " "metaserver connection.")); - return std::make_pair(tcp_server, false); + return std::nullopt; } } - return std::make_pair(tcp_server, true); + return server; } } // anonymous namespace @@ -258,20 +257,27 @@ server::server() // Now init the old C API fc_interface_init_server(); - bool success; - std::tie(m_tcp_server, success) = srv_prepare(); - if (!success) { + auto server = srv_prepare(); + if (!server) { // Could not listen on the specified port. Rely on the caller checking // our state and not starting the event loop. return; } - m_tcp_server->setParent(this); - connect(m_tcp_server, &QTcpServer::newConnection, this, - &server::accept_connections); - connect(m_tcp_server, &QTcpServer::acceptError, - [](QAbstractSocket::SocketError error) { - qCritical("Error accepting connection: %d", error); - }); + m_server = std::move(*server); + if (std::holds_alternative>(m_server)) { + auto &tcp = std::get>(m_server); + connect(tcp.get(), &QTcpServer::newConnection, this, + &server::accept_tcp_connections); + connect(tcp.get(), &QTcpServer::acceptError, + [](QAbstractSocket::SocketError error) { + qCritical("Error accepting connection: %d", error); + }); + } else if (std::holds_alternative>( + m_server)) { + auto &local = std::get>(m_server); + connect(local.get(), &QLocalServer::newConnection, this, + &server::accept_local_connections); + } m_eot_timer = timer_new(TIMER_CPU, TIMER_ACTIVE); @@ -353,15 +359,55 @@ void server::init_interactive() */ bool server::is_ready() const { return m_ready; } +/** + * Server accepts connections over local socket: + * Low level socket stuff, and basic-initialize the connection struct. + */ +void server::accept_local_connections() +{ + // We know it's safe: this method is only called for local connections + auto &server = std::get>(m_server); + + // There may be several connections available. + while (server->hasPendingConnections()) { + auto *socket = server->nextPendingConnection(); + socket->setParent(this); + + if (server_make_connection(socket, QStringLiteral("local"), + QStringLiteral("local")) + == 0) { + // Success making the connection, connect signals + connect(socket, &QIODevice::readyRead, this, &server::input_on_socket); + connect(socket, &QLocalSocket::errorOccurred, this, + &server::error_on_socket); + + // Prevents quitidle from firing immediately + m_someone_ever_connected = true; + + // Turn off the quitidle timeout if it's running + if (m_quitidle_timer != nullptr) { + m_quitidle_timer->stop(); + m_quitidle_timer->deleteLater(); + m_quitidle_timer = nullptr; + } + } else { + socket->deleteLater(); + } + } +} + /** Server accepts connections from client: Low level socket stuff, and basic-initialize the connection struct. */ -void server::accept_connections() +void server::accept_tcp_connections() { + // We know it's safe: this method is only called for TCP connections + auto &server = std::get>(m_server); + // There may be several connections available. - while (m_tcp_server->hasPendingConnections()) { - auto *socket = m_tcp_server->nextPendingConnection(); + while (server->hasPendingConnections()) { + auto *socket = server->nextPendingConnection(); socket->setParent(this); // Lookup the host name of the remote end. @@ -391,9 +437,9 @@ void server::accept_connections() { // Use TolerantConversion so one connections from the same address on // IPv4 and IPv6 are rejected as well. - if (socket->peerAddress().isEqual( - pconn->sock->peerAddress(), - QHostAddress::TolerantConversion)) { + if (const auto *other = qobject_cast(pconn->sock); + socket->peerAddress().isEqual( + other->peerAddress(), QHostAddress::TolerantConversion)) { continue; } if (++count >= game.server.maxconnectionsperhost) { @@ -408,11 +454,14 @@ void server::accept_connections() conn_list_iterate_end; if (!success) { + socket->deleteLater(); continue; } } - if (server_make_connection(socket, remote) == 0) { + if (server_make_connection(socket, remote, + socket->peerAddress().toString()) + == 0) { // Success making the connection, connect signals connect(socket, &QIODevice::readyRead, this, &server::input_on_socket); connect(socket, @@ -482,7 +531,7 @@ void server::error_on_socket() conn_list_iterate(game.all_connections, pconn) { if (pconn->sock == socket) { - connection_close_server(pconn, _("network exception")); + connection_close_server(pconn, socket->errorString()); break; } } @@ -498,7 +547,7 @@ void server::error_on_socket() void server::input_on_socket() { // Get the socket - auto *socket = dynamic_cast(sender()); + auto *socket = dynamic_cast(sender()); if (socket == nullptr) { return; } diff --git a/server/server.h b/server/server.h index 9dac70556d..e00e0d3d60 100644 --- a/server/server.h +++ b/server/server.h @@ -19,8 +19,12 @@ #pragma once +#include "sernet.h" + // Qt +#include #include +#include class civtimer; class QTcpServer; @@ -41,7 +45,8 @@ private slots: void error_on_socket(); void input_on_socket(); void input_on_stdin(); - void accept_connections(); + void accept_local_connections(); + void accept_tcp_connections(); void send_pings(); // Higher-level stuff @@ -63,7 +68,7 @@ private slots: bool m_interactive = false; QObject *m_stdin_notifier = nullptr; // Actual type is OS-dependent - QTcpServer *m_tcp_server = nullptr; + socket_server m_server = socket_server(); civtimer *m_eot_timer = nullptr, *m_between_turns_timer = nullptr;