/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2014-2021 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include static const wxString HOSTNAME( wxT( "localhost" ) ); // buffer for read and write data in socket connections #define IPC_BUF_SIZE 4096 static char client_ipc_buffer[IPC_BUF_SIZE]; void KIWAY_PLAYER::CreateServer( int service, bool local ) { wxIPV4address addr; // Set the port number addr.Service( service ); // Listen on localhost only if requested if( local ) addr.Hostname( HOSTNAME ); delete m_socketServer; m_socketServer = new wxSocketServer( addr ); m_socketServer->SetNotify( wxSOCKET_CONNECTION_FLAG ); m_socketServer->SetEventHandler( *this, ID_EDA_SOCKET_EVENT_SERV ); m_socketServer->Notify( true ); } void KIWAY_PLAYER::OnSockRequest( wxSocketEvent& evt ) { size_t len; wxSocketBase* sock = evt.GetSocket(); switch( evt.GetSocketEvent() ) { case wxSOCKET_INPUT: sock->Read( client_ipc_buffer, 1 ); if( sock->LastCount() == 0 ) break; // No data, occurs on opening connection sock->Read( client_ipc_buffer + 1, IPC_BUF_SIZE - 2 ); len = 1 + sock->LastCount(); client_ipc_buffer[len] = 0; ExecuteRemoteCommand( client_ipc_buffer ); break; case wxSOCKET_LOST: return; break; default: wxPrintf( wxT( "EDA_DRAW_FRAME::OnSockRequest() error: Invalid event !" ) ); break; } } void KIWAY_PLAYER::OnSockRequestServer( wxSocketEvent& evt ) { wxSocketBase* socket; wxSocketServer* server = (wxSocketServer*) evt.GetSocket(); socket = server->Accept(); if( socket == nullptr ) return; m_sockets.push_back( socket ); socket->Notify( true ); socket->SetEventHandler( *this, ID_EDA_SOCKET_EVENT ); socket->SetNotify( wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG ); } /** * Spin up a thread to send messages via a socket. * * No message queuing, if a message is in flight when another is posted with Send(), the * second is just dropped. This is a workaround for "non-blocking" sockets not always being * non-blocking, especially on Windows. It is kept fairly simple and not exposed to the * outside world because it should be replaced in a future KiCad version with a real message * queue of some sort, and unified with the Kiway messaging system. */ class ASYNC_SOCKET_HOLDER { public: ASYNC_SOCKET_HOLDER() : m_messageReady( false ), m_shutdown( false ) { // Do a dummy Connect so that wx will set up the socket stuff on the main thread, which is // required even if you later make socket connections on another thread. wxSocketClient* client = new wxSocketClient; wxIPV4address addr; addr.Hostname( HOSTNAME ); addr.Service( KICAD_PCB_PORT_SERVICE_NUMBER ); client->Connect( addr, false ); client->Close(); client->Destroy(); m_thread = std::thread( &ASYNC_SOCKET_HOLDER::worker, this ); } ~ASYNC_SOCKET_HOLDER() { { std::lock_guard lock( m_mutex ); m_shutdown = true; } m_cv.notify_one(); try { if( m_thread.joinable() ) m_thread.join(); } catch( ... ) { } } /** * Attempt to send a message if the thread is available. * * @param aService is the port number (i.e. service) to send to. * @param aMessage is the message to send. * @return true if the message was queued. */ bool Send( int aService, const std::string& aMessage ) { if( m_messageReady ) return false; std::lock_guard lock( m_mutex ); m_message = std::make_pair( aService, aMessage ); m_messageReady = true; m_cv.notify_one(); return true; } private: /** * Actual task that sends data to the socket server */ void worker() { int port; std::string message; std::unique_lock lock( m_mutex ); while( !m_shutdown ) { m_cv.wait( lock, [&]() { return m_messageReady || m_shutdown; } ); if( m_shutdown ) break; port = m_message.first; message = m_message.second; lock.unlock(); wxSocketClient* sock_client; wxIPV4address addr; // Create a connection addr.Hostname( HOSTNAME ); addr.Service( port ); // Mini-tutorial for Connect() :-) // (JP CHARRAS Note: see wxWidgets: sockets/client.cpp sample) // --------------------------- // // There are two ways to use Connect(): blocking and non-blocking, // depending on the value passed as the 'wait' (2nd) parameter. // // Connect(addr, true) will wait until the connection completes, // returning true on success and false on failure. This call blocks // the GUI (this might be changed in future releases to honor the // wxSOCKET_BLOCK flag). // // Connect(addr, false) will issue a nonblocking connection request // and return immediately. If the return value is true, then the // connection has been already successfully established. If it is // false, you must wait for the request to complete, either with // WaitOnConnect() or by watching wxSOCKET_CONNECTION / LOST // events (please read the documentation). // // WaitOnConnect() itself never blocks the GUI (this might change // in the future to honor the wxSOCKET_BLOCK flag). This call will // return false on timeout, or true if the connection request // completes, which in turn might mean: // // a) That the connection was successfully established // b) That the connection request failed (for example, because // it was refused by the peer. // // Use IsConnected() to distinguish between these two. // // So, in a brief, you should do one of the following things: // // For blocking Connect: // // bool success = client->Connect(addr, true); // // For nonblocking Connect: // // client->Connect(addr, false); // // bool waitmore = true; // while (! client->WaitOnConnect(seconds, millis) && waitmore ) // { // // possibly give some feedback to the user, // // update waitmore if needed. // } // bool success = client->IsConnected(); // // And that's all :-) sock_client = new wxSocketClient( wxSOCKET_BLOCK ); sock_client->SetTimeout( 1 ); // Time out in Seconds sock_client->Connect( addr, false ); sock_client->WaitOnConnect( 0, 250 ); if( sock_client->Ok() && sock_client->IsConnected() ) { sock_client->SetFlags( wxSOCKET_NOWAIT /*wxSOCKET_WAITALL*/ ); sock_client->Write( message.c_str(), message.length() ); } sock_client->Close(); sock_client->Destroy(); m_messageReady = false; lock.lock(); } } std::thread m_thread; std::pair m_message; bool m_messageReady; mutable std::mutex m_mutex; std::condition_variable m_cv; bool m_shutdown; }; std::unique_ptr socketHolder = nullptr; /** * Used by a client to sent (by a socket connection) a data to a server. * - Open a Socket Client connection. * - Send the buffer cmdline. * - Close the socket connection. * * @param aService is the service number for the TC/IP connection. * @param aMessage is the message to send. */ bool SendCommand( int aService, const std::string& aMessage ) { if( !socketHolder ) socketHolder.reset( new ASYNC_SOCKET_HOLDER() ); return socketHolder->Send( aService, aMessage ); } void SocketCleanup() { if( socketHolder ) socketHolder.reset(); }