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.

317 lines
9.5 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. delete m_socketServer;
  44. m_socketServer = new wxSocketServer( addr );
  45. m_socketServer->SetNotify( wxSOCKET_CONNECTION_FLAG );
  46. m_socketServer->SetEventHandler( *this, ID_EDA_SOCKET_EVENT_SERV );
  47. m_socketServer->Notify( true );
  48. }
  49. void KIWAY_PLAYER::OnSockRequest( wxSocketEvent& evt )
  50. {
  51. size_t len;
  52. wxSocketBase* sock = evt.GetSocket();
  53. switch( evt.GetSocketEvent() )
  54. {
  55. case wxSOCKET_INPUT:
  56. sock->Read( client_ipc_buffer, 1 );
  57. if( sock->LastCount() == 0 )
  58. break; // No data, occurs on opening connection
  59. sock->Read( client_ipc_buffer + 1, IPC_BUF_SIZE - 2 );
  60. len = 1 + sock->LastCount();
  61. client_ipc_buffer[len] = 0;
  62. ExecuteRemoteCommand( client_ipc_buffer );
  63. break;
  64. case wxSOCKET_LOST:
  65. return;
  66. break;
  67. default:
  68. wxPrintf( wxT( "EDA_DRAW_FRAME::OnSockRequest() error: Invalid event !" ) );
  69. break;
  70. }
  71. }
  72. void KIWAY_PLAYER::OnSockRequestServer( wxSocketEvent& evt )
  73. {
  74. wxSocketBase* socket;
  75. wxSocketServer* server = (wxSocketServer*) evt.GetSocket();
  76. socket = server->Accept();
  77. if( socket == nullptr )
  78. return;
  79. m_sockets.push_back( socket );
  80. socket->Notify( true );
  81. socket->SetEventHandler( *this, ID_EDA_SOCKET_EVENT );
  82. socket->SetNotify( wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG );
  83. }
  84. /**
  85. * Spin up a thread to send messages via a socket.
  86. *
  87. * No message queuing, if a message is in flight when another is posted with Send(), the
  88. * second is just dropped. This is a workaround for "non-blocking" sockets not always being
  89. * non-blocking, especially on Windows. It is kept fairly simple and not exposed to the
  90. * outside world because it should be replaced in a future KiCad version with a real message
  91. * queue of some sort, and unified with the Kiway messaging system.
  92. */
  93. class ASYNC_SOCKET_HOLDER
  94. {
  95. public:
  96. ASYNC_SOCKET_HOLDER() :
  97. m_messageReady( false ),
  98. m_shutdown( false )
  99. {
  100. // Do a dummy Connect so that wx will set up the socket stuff on the main thread, which is
  101. // required even if you later make socket connections on another thread.
  102. wxSocketClient* client = new wxSocketClient;
  103. wxIPV4address addr;
  104. addr.Hostname( HOSTNAME );
  105. addr.Service( KICAD_PCB_PORT_SERVICE_NUMBER );
  106. client->Connect( addr, false );
  107. client->Close();
  108. client->Destroy();
  109. m_thread = std::thread( &ASYNC_SOCKET_HOLDER::worker, this );
  110. }
  111. ~ASYNC_SOCKET_HOLDER()
  112. {
  113. {
  114. std::lock_guard<std::mutex> lock( m_mutex );
  115. m_shutdown = true;
  116. }
  117. m_cv.notify_one();
  118. try
  119. {
  120. if( m_thread.joinable() )
  121. m_thread.join();
  122. }
  123. catch( ... )
  124. {
  125. }
  126. }
  127. /**
  128. * Attempt to send a message if the thread is available.
  129. *
  130. * @param aService is the port number (i.e. service) to send to.
  131. * @param aMessage is the message to send.
  132. * @return true if the message was queued.
  133. */
  134. bool Send( int aService, const std::string& aMessage )
  135. {
  136. if( m_messageReady )
  137. return false;
  138. std::lock_guard<std::mutex> lock( m_mutex );
  139. m_message = std::make_pair( aService, aMessage );
  140. m_messageReady = true;
  141. m_cv.notify_one();
  142. return true;
  143. }
  144. private:
  145. /**
  146. * Actual task that sends data to the socket server
  147. */
  148. void worker()
  149. {
  150. int port;
  151. std::string message;
  152. std::unique_lock<std::mutex> lock( m_mutex );
  153. while( !m_shutdown )
  154. {
  155. m_cv.wait( lock, [&]() { return m_messageReady || m_shutdown; } );
  156. if( m_shutdown )
  157. break;
  158. port = m_message.first;
  159. message = m_message.second;
  160. lock.unlock();
  161. wxSocketClient* sock_client;
  162. wxIPV4address addr;
  163. // Create a connection
  164. addr.Hostname( HOSTNAME );
  165. addr.Service( port );
  166. // Mini-tutorial for Connect() :-)
  167. // (JP CHARRAS Note: see wxWidgets: sockets/client.cpp sample)
  168. // ---------------------------
  169. //
  170. // There are two ways to use Connect(): blocking and non-blocking,
  171. // depending on the value passed as the 'wait' (2nd) parameter.
  172. //
  173. // Connect(addr, true) will wait until the connection completes,
  174. // returning true on success and false on failure. This call blocks
  175. // the GUI (this might be changed in future releases to honor the
  176. // wxSOCKET_BLOCK flag).
  177. //
  178. // Connect(addr, false) will issue a nonblocking connection request
  179. // and return immediately. If the return value is true, then the
  180. // connection has been already successfully established. If it is
  181. // false, you must wait for the request to complete, either with
  182. // WaitOnConnect() or by watching wxSOCKET_CONNECTION / LOST
  183. // events (please read the documentation).
  184. //
  185. // WaitOnConnect() itself never blocks the GUI (this might change
  186. // in the future to honor the wxSOCKET_BLOCK flag). This call will
  187. // return false on timeout, or true if the connection request
  188. // completes, which in turn might mean:
  189. //
  190. // a) That the connection was successfully established
  191. // b) That the connection request failed (for example, because
  192. // it was refused by the peer.
  193. //
  194. // Use IsConnected() to distinguish between these two.
  195. //
  196. // So, in a brief, you should do one of the following things:
  197. //
  198. // For blocking Connect:
  199. //
  200. // bool success = client->Connect(addr, true);
  201. //
  202. // For nonblocking Connect:
  203. //
  204. // client->Connect(addr, false);
  205. //
  206. // bool waitmore = true;
  207. // while (! client->WaitOnConnect(seconds, millis) && waitmore )
  208. // {
  209. // // possibly give some feedback to the user,
  210. // // update waitmore if needed.
  211. // }
  212. // bool success = client->IsConnected();
  213. //
  214. // And that's all :-)
  215. sock_client = new wxSocketClient( wxSOCKET_BLOCK );
  216. sock_client->SetTimeout( 1 ); // Time out in Seconds
  217. sock_client->Connect( addr, false );
  218. sock_client->WaitOnConnect( 0, 250 );
  219. if( sock_client->Ok() && sock_client->IsConnected() )
  220. {
  221. sock_client->SetFlags( wxSOCKET_NOWAIT /*wxSOCKET_WAITALL*/ );
  222. sock_client->Write( message.c_str(), message.length() );
  223. }
  224. sock_client->Close();
  225. sock_client->Destroy();
  226. m_messageReady = false;
  227. lock.lock();
  228. }
  229. }
  230. std::thread m_thread;
  231. std::pair<int, std::string> m_message;
  232. bool m_messageReady;
  233. mutable std::mutex m_mutex;
  234. std::condition_variable m_cv;
  235. bool m_shutdown;
  236. };
  237. std::unique_ptr<ASYNC_SOCKET_HOLDER> socketHolder = nullptr;
  238. /**
  239. * Used by a client to sent (by a socket connection) a data to a server.
  240. * - Open a Socket Client connection.
  241. * - Send the buffer cmdline.
  242. * - Close the socket connection.
  243. *
  244. * @param aService is the service number for the TC/IP connection.
  245. * @param aMessage is the message to send.
  246. */
  247. bool SendCommand( int aService, const std::string& aMessage )
  248. {
  249. if( !socketHolder )
  250. socketHolder.reset( new ASYNC_SOCKET_HOLDER() );
  251. return socketHolder->Send( aService, aMessage );
  252. }
  253. void SocketCleanup()
  254. {
  255. if( socketHolder )
  256. socketHolder.reset();
  257. }