You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

323 lines
9.7 KiB

5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2014 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2014-2021 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <condition_variable>
  25. #include <mutex>
  26. #include <thread>
  27. #include <eda_dde.h>
  28. #include <kiway_player.h>
  29. #include <id.h>
  30. #include <wx/crt.h>
  31. static const wxString HOSTNAME( wxT( "localhost" ) );
  32. // buffer for read and write data in socket connections
  33. #define IPC_BUF_SIZE 4096
  34. static char client_ipc_buffer[IPC_BUF_SIZE];
  35. void KIWAY_PLAYER::CreateServer( int service, bool local )
  36. {
  37. wxIPV4address addr;
  38. // Set the port number
  39. addr.Service( service );
  40. // Listen on localhost only if requested
  41. if( local )
  42. addr.Hostname( HOSTNAME );
  43. if( m_socketServer )
  44. {
  45. // this helps prevent any events that could come in during deletion
  46. m_socketServer->Notify( false );
  47. delete m_socketServer;
  48. }
  49. m_socketServer = new wxSocketServer( addr );
  50. m_socketServer->SetNotify( wxSOCKET_CONNECTION_FLAG );
  51. m_socketServer->SetEventHandler( *this, ID_EDA_SOCKET_EVENT_SERV );
  52. m_socketServer->Notify( true );
  53. }
  54. void KIWAY_PLAYER::OnSockRequest( wxSocketEvent& evt )
  55. {
  56. size_t len;
  57. wxSocketBase* sock = evt.GetSocket();
  58. switch( evt.GetSocketEvent() )
  59. {
  60. case wxSOCKET_INPUT:
  61. sock->Read( client_ipc_buffer, 1 );
  62. if( sock->LastCount() == 0 )
  63. break; // No data, occurs on opening connection
  64. sock->Read( client_ipc_buffer + 1, IPC_BUF_SIZE - 2 );
  65. len = 1 + sock->LastCount();
  66. client_ipc_buffer[len] = 0;
  67. ExecuteRemoteCommand( client_ipc_buffer );
  68. break;
  69. case wxSOCKET_LOST:
  70. return;
  71. break;
  72. default:
  73. wxPrintf( wxT( "EDA_DRAW_FRAME::OnSockRequest() error: Invalid event !" ) );
  74. break;
  75. }
  76. }
  77. void KIWAY_PLAYER::OnSockRequestServer( wxSocketEvent& evt )
  78. {
  79. wxSocketBase* socket;
  80. wxSocketServer* server = (wxSocketServer*) evt.GetSocket();
  81. socket = server->Accept();
  82. if( socket == nullptr )
  83. return;
  84. m_sockets.push_back( socket );
  85. socket->Notify( true );
  86. socket->SetEventHandler( *this, ID_EDA_SOCKET_EVENT );
  87. socket->SetNotify( wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG );
  88. }
  89. /**
  90. * Spin up a thread to send messages via a socket.
  91. *
  92. * No message queuing, if a message is in flight when another is posted with Send(), the
  93. * second is just dropped. This is a workaround for "non-blocking" sockets not always being
  94. * non-blocking, especially on Windows. It is kept fairly simple and not exposed to the
  95. * outside world because it should be replaced in a future KiCad version with a real message
  96. * queue of some sort, and unified with the Kiway messaging system.
  97. */
  98. class ASYNC_SOCKET_HOLDER
  99. {
  100. public:
  101. ASYNC_SOCKET_HOLDER() :
  102. m_messageReady( false ),
  103. m_shutdown( false )
  104. {
  105. // Do a dummy Connect so that wx will set up the socket stuff on the main thread, which is
  106. // required even if you later make socket connections on another thread.
  107. wxSocketClient* client = new wxSocketClient;
  108. wxIPV4address addr;
  109. addr.Hostname( HOSTNAME );
  110. addr.Service( KICAD_PCB_PORT_SERVICE_NUMBER );
  111. client->Connect( addr, false );
  112. client->Close();
  113. client->Destroy();
  114. m_thread = std::thread( &ASYNC_SOCKET_HOLDER::worker, this );
  115. }
  116. ~ASYNC_SOCKET_HOLDER()
  117. {
  118. {
  119. std::lock_guard<std::mutex> lock( m_mutex );
  120. m_shutdown = true;
  121. }
  122. m_cv.notify_one();
  123. try
  124. {
  125. if( m_thread.joinable() )
  126. m_thread.join();
  127. }
  128. catch( ... )
  129. {
  130. }
  131. }
  132. /**
  133. * Attempt to send a message if the thread is available.
  134. *
  135. * @param aService is the port number (i.e. service) to send to.
  136. * @param aMessage is the message to send.
  137. * @return true if the message was queued.
  138. */
  139. bool Send( int aService, const std::string& aMessage )
  140. {
  141. if( m_messageReady )
  142. return false;
  143. std::lock_guard<std::mutex> lock( m_mutex );
  144. m_message = std::make_pair( aService, aMessage );
  145. m_messageReady = true;
  146. m_cv.notify_one();
  147. return true;
  148. }
  149. private:
  150. /**
  151. * Actual task that sends data to the socket server
  152. */
  153. void worker()
  154. {
  155. int port;
  156. std::string message;
  157. std::unique_lock<std::mutex> lock( m_mutex );
  158. while( !m_shutdown )
  159. {
  160. m_cv.wait( lock, [&]() { return m_messageReady || m_shutdown; } );
  161. if( m_shutdown )
  162. break;
  163. port = m_message.first;
  164. message = m_message.second;
  165. lock.unlock();
  166. wxSocketClient* sock_client;
  167. wxIPV4address addr;
  168. // Create a connection
  169. addr.Hostname( HOSTNAME );
  170. addr.Service( port );
  171. // Mini-tutorial for Connect() :-)
  172. // (JP CHARRAS Note: see wxWidgets: sockets/client.cpp sample)
  173. // ---------------------------
  174. //
  175. // There are two ways to use Connect(): blocking and non-blocking,
  176. // depending on the value passed as the 'wait' (2nd) parameter.
  177. //
  178. // Connect(addr, true) will wait until the connection completes,
  179. // returning true on success and false on failure. This call blocks
  180. // the GUI (this might be changed in future releases to honor the
  181. // wxSOCKET_BLOCK flag).
  182. //
  183. // Connect(addr, false) will issue a nonblocking connection request
  184. // and return immediately. If the return value is true, then the
  185. // connection has been already successfully established. If it is
  186. // false, you must wait for the request to complete, either with
  187. // WaitOnConnect() or by watching wxSOCKET_CONNECTION / LOST
  188. // events (please read the documentation).
  189. //
  190. // WaitOnConnect() itself never blocks the GUI (this might change
  191. // in the future to honor the wxSOCKET_BLOCK flag). This call will
  192. // return false on timeout, or true if the connection request
  193. // completes, which in turn might mean:
  194. //
  195. // a) That the connection was successfully established
  196. // b) That the connection request failed (for example, because
  197. // it was refused by the peer.
  198. //
  199. // Use IsConnected() to distinguish between these two.
  200. //
  201. // So, in a brief, you should do one of the following things:
  202. //
  203. // For blocking Connect:
  204. //
  205. // bool success = client->Connect(addr, true);
  206. //
  207. // For nonblocking Connect:
  208. //
  209. // client->Connect(addr, false);
  210. //
  211. // bool waitmore = true;
  212. // while (! client->WaitOnConnect(seconds, millis) && waitmore )
  213. // {
  214. // // possibly give some feedback to the user,
  215. // // update waitmore if needed.
  216. // }
  217. // bool success = client->IsConnected();
  218. //
  219. // And that's all :-)
  220. sock_client = new wxSocketClient( wxSOCKET_BLOCK );
  221. sock_client->SetTimeout( 1 ); // Time out in Seconds
  222. sock_client->Connect( addr, false );
  223. sock_client->WaitOnConnect( 0, 250 );
  224. if( sock_client->Ok() && sock_client->IsConnected() )
  225. {
  226. sock_client->SetFlags( wxSOCKET_NOWAIT /*wxSOCKET_WAITALL*/ );
  227. sock_client->Write( message.c_str(), message.length() );
  228. }
  229. sock_client->Close();
  230. sock_client->Destroy();
  231. m_messageReady = false;
  232. lock.lock();
  233. }
  234. }
  235. std::thread m_thread;
  236. std::pair<int, std::string> m_message;
  237. bool m_messageReady;
  238. mutable std::mutex m_mutex;
  239. std::condition_variable m_cv;
  240. bool m_shutdown;
  241. };
  242. std::unique_ptr<ASYNC_SOCKET_HOLDER> socketHolder = nullptr;
  243. /**
  244. * Used by a client to sent (by a socket connection) a data to a server.
  245. * - Open a Socket Client connection.
  246. * - Send the buffer cmdline.
  247. * - Close the socket connection.
  248. *
  249. * @param aService is the service number for the TC/IP connection.
  250. * @param aMessage is the message to send.
  251. */
  252. bool SendCommand( int aService, const std::string& aMessage )
  253. {
  254. if( !socketHolder )
  255. socketHolder.reset( new ASYNC_SOCKET_HOLDER() );
  256. return socketHolder->Send( aService, aMessage );
  257. }
  258. void SocketCleanup()
  259. {
  260. if( socketHolder )
  261. socketHolder.reset();
  262. }