kicad/common/eda_dde.cpp

324 lines
9.7 KiB
C++

/*
* 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 <condition_variable>
#include <mutex>
#include <thread>
#include <eda_dde.h>
#include <kiway_player.h>
#include <id.h>
#include <wx/crt.h>
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 );
if( m_socketServer )
{
// this helps prevent any events that could come in during deletion
m_socketServer->Notify( false );
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<std::mutex> 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<std::mutex> 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<std::mutex> 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<int, std::string> m_message;
bool m_messageReady;
mutable std::mutex m_mutex;
std::condition_variable m_cv;
bool m_shutdown;
};
std::unique_ptr<ASYNC_SOCKET_HOLDER> 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();
}