2014-05-07 15:39:49 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2014-05-08 12:54:30 +00:00
|
|
|
|
using System.Linq;
|
2014-05-07 15:39:49 +00:00
|
|
|
|
using System.Net.Sockets;
|
2014-05-07 20:32:18 +00:00
|
|
|
|
using log4net;
|
2014-05-08 08:25:43 +00:00
|
|
|
|
using NPSharp.RPC.Messages;
|
2014-05-07 15:39:49 +00:00
|
|
|
|
|
|
|
|
|
namespace NPSharp.RPC
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Represents a low-level client stream which can communicate with an NP server using RPC packets.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class RPCClientStream
|
|
|
|
|
{
|
|
|
|
|
private NetworkStream _ns;
|
|
|
|
|
private uint _id;
|
2014-05-08 08:25:43 +00:00
|
|
|
|
private readonly ILog _log;
|
2014-05-07 15:39:49 +00:00
|
|
|
|
|
|
|
|
|
private readonly string _host;
|
|
|
|
|
private readonly ushort _port;
|
|
|
|
|
|
2014-05-08 12:54:30 +00:00
|
|
|
|
private readonly Dictionary<uint, Tuple<DateTime, Action<RPCServerMessage>>> _callbacks = new Dictionary<uint, Tuple<DateTime, Action<RPCServerMessage>>>();
|
2014-05-07 15:39:49 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initializes an RPC connection stream with a specified host and port.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="host">The host to connect to.</param>
|
|
|
|
|
/// <param name="port">The port to use. Default: 3025.</param>
|
|
|
|
|
public RPCClientStream(string host, ushort port = 3025)
|
|
|
|
|
{
|
|
|
|
|
_host = host;
|
|
|
|
|
_port = port;
|
2014-05-07 20:32:18 +00:00
|
|
|
|
_log = LogManager.GetLogger("RPC");
|
2014-05-07 15:39:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Opens the RPC stream to the NP server.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>True if the connection succeeded, otherwise false.</returns>
|
|
|
|
|
public bool Open()
|
|
|
|
|
{
|
2014-05-08 08:25:43 +00:00
|
|
|
|
_log.Debug("Open() start");
|
|
|
|
|
|
2014-05-07 15:39:49 +00:00
|
|
|
|
// 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();
|
2014-05-08 08:25:43 +00:00
|
|
|
|
|
|
|
|
|
_log.Debug("Open() end");
|
2014-05-07 15:39:49 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Closes the connection with the NP server.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="timeout"></param>
|
|
|
|
|
public void Close(int timeout = 2000)
|
|
|
|
|
{
|
|
|
|
|
// Connection already closed?
|
|
|
|
|
if (_ns == null)
|
|
|
|
|
throw new InvalidOperationException("Connection already closed");
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2014-05-08 12:54:30 +00:00
|
|
|
|
_callbacks.Clear();
|
2014-05-07 15:39:49 +00:00
|
|
|
|
_ns.Close(timeout);
|
|
|
|
|
_ns.Dispose();
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_ns = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attaches a callback to the next message being sent out. This allows handling response packets.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="callback">The method to call when we receive a response to the next message</param>
|
2014-05-08 12:54:30 +00:00
|
|
|
|
/// <param name="timeout">Time in seconds from now in which this callback will expire for the next packet</param>
|
|
|
|
|
public void AttachCallback(Action<RPCServerMessage> callback, double timeout)
|
2014-05-07 15:39:49 +00:00
|
|
|
|
{
|
2014-05-08 12:54:30 +00:00
|
|
|
|
_cleanupCallbacks();
|
2014-05-08 08:25:43 +00:00
|
|
|
|
_log.DebugFormat("AttachCallback for packet id {0}", _id);
|
2014-05-07 15:39:49 +00:00
|
|
|
|
if (_callbacks.ContainsKey(_id))
|
|
|
|
|
throw new Exception("There is already a callback for the current message. You can only add max. one callback.");
|
2014-05-08 12:54:30 +00:00
|
|
|
|
_callbacks.Add(_id, new Tuple<DateTime, Action<RPCServerMessage>>(DateTime.Now + TimeSpan.FromSeconds(timeout), callback));
|
2014-05-07 15:39:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Exposure of message ID needed or no?
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sends out an RPC message.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="message">The RPC message to send out.</param>
|
|
|
|
|
/// <returns>The new ID of the message.</returns>
|
|
|
|
|
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);
|
2014-05-07 20:32:18 +00:00
|
|
|
|
_ns.Flush();
|
|
|
|
|
|
|
|
|
|
_log.DebugFormat("Sent packet ID {1} (type {0})", message.GetType().Name, _id);
|
2014-05-07 15:39:49 +00:00
|
|
|
|
|
|
|
|
|
return _id++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Waits for the next RPC message from the server and reads it.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>The received server message.</returns>
|
|
|
|
|
public RPCServerMessage Read()
|
|
|
|
|
{
|
|
|
|
|
if (_ns == null)
|
|
|
|
|
throw new InvalidOperationException("You need to open the stream first.");
|
|
|
|
|
|
|
|
|
|
var message = RPCServerMessage.Deserialize(_ns);
|
|
|
|
|
|
|
|
|
|
if (message == null)
|
2014-05-08 08:25:43 +00:00
|
|
|
|
{
|
|
|
|
|
_log.Debug("Recv NULL message");
|
2014-05-07 15:39:49 +00:00
|
|
|
|
return null;
|
2014-05-08 08:25:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-05-07 15:39:49 +00:00
|
|
|
|
if (!_callbacks.ContainsKey(message.MessageId))
|
|
|
|
|
return message;
|
|
|
|
|
|
2014-05-08 12:54:30 +00:00
|
|
|
|
_cleanupCallbacks();
|
|
|
|
|
if (_callbacks.ContainsKey(message.MessageId))
|
|
|
|
|
_callbacks[message.MessageId].Item2.Invoke(message);
|
2014-05-07 15:39:49 +00:00
|
|
|
|
|
|
|
|
|
return message;
|
|
|
|
|
}
|
2014-05-08 12:54:30 +00:00
|
|
|
|
|
|
|
|
|
private void _cleanupCallbacks()
|
|
|
|
|
{
|
|
|
|
|
var cbr = (from item in _callbacks where item.Value.Item1 < DateTime.Now select item.Key).ToArray();
|
|
|
|
|
foreach (var cb in cbr)
|
|
|
|
|
_callbacks.Remove(cb);
|
|
|
|
|
}
|
2014-05-07 15:39:49 +00:00
|
|
|
|
}
|
|
|
|
|
}
|