using System; using System.Collections.Generic; using System.Net.Sockets; using log4net; using NPSharp.RPC.Messages; namespace NPSharp.RPC { /// /// Represents a low-level client stream which can communicate with an NP server using RPC packets. /// public class RPCClientStream { private NetworkStream _ns; private uint _id; private readonly ILog _log; private readonly string _host; private readonly ushort _port; private readonly Dictionary> _callbacks = new Dictionary>(); /// /// Initializes an RPC connection stream with a specified host and port. /// /// The host to connect to. /// The port to use. Default: 3025. public RPCClientStream(string host, ushort port = 3025) { _host = host; _port = port; _log = LogManager.GetLogger("RPC"); } /// /// Opens the RPC stream to the NP server. /// /// True if the connection succeeded, otherwise false. public bool Open() { _log.Debug("Open() start"); // Connection already established? if (_ns != null) throw new InvalidOperationException("Connection already opened"); var tcp = new TcpClient(); try { tcp.Connect(_host, _port); } catch { return false; } _ns = tcp.GetStream(); _log.Debug("Open() end"); return true; } /// /// Closes the connection with the NP server. /// /// public void Close(int timeout = 2000) { // Connection already closed? if (_ns == null) throw new InvalidOperationException("Connection already closed"); try { _ns.Close(timeout); _ns.Dispose(); } finally { _ns = null; } } /// /// Attaches a callback to the next message being sent out. This allows handling response packets. /// /// The method to call when we receive a response to the next message public void AttachCallback(Action callback) { _log.DebugFormat("AttachCallback for packet id {0}", _id); if (_callbacks.ContainsKey(_id)) throw new Exception("There is already a callback for the current message. You can only add max. one callback."); _callbacks.Add(_id, callback); } // TODO: Exposure of message ID needed or no? /// /// Sends out an RPC message. /// /// The RPC message to send out. /// The new ID of the message. public uint Send(RPCClientMessage message) { if (_ns == null) throw new InvalidOperationException("You need to open the stream first."); var buffer = message.Serialize(_id); _ns.Write(buffer, 0, buffer.Length); _ns.Flush(); _log.DebugFormat("Sent packet ID {1} (type {0})", message.GetType().Name, _id); return _id++; } /// /// Waits for the next RPC message from the server and reads it. /// /// The received server message. public RPCServerMessage Read() { if (_ns == null) throw new InvalidOperationException("You need to open the stream first."); var message = RPCServerMessage.Deserialize(_ns); if (message == null) { _log.Debug("Recv NULL message"); return null; } _log.DebugFormat("Received packet ID {1} (type {0})", message.GetType().Name, message.MessageId); if (!_callbacks.ContainsKey(message.MessageId)) return message; _callbacks[message.MessageId].Invoke(message); // TODO: Callback cleanup return message; } } }