diff --git a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs
index 8b01b4c9..9a514df9 100644
--- a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs
+++ b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs
@@ -1,6 +1,5 @@
using System;
using System.Threading;
-using Renci.SshClient.Common;
using Renci.SshClient.Messages;
using Renci.SshClient.Messages.Connection;
@@ -16,20 +15,34 @@ namespace Renci.SshClient.Channels
private uint _maximumPacketSize = 0x4000;
- private int _chanelOpenFailedAttempts = 0;
+ ///
+ /// Counts faile channel open attempts
+ ///
+ private int _failedOpenAttempts;
+
+ ///
+ /// Indicates weither close channel message was sent
+ ///
+ private bool _closeMessageSent = false;
public abstract ChannelTypes ChannelType { get; }
- public uint ClientChannelNumber { get; set; }
+ public uint ClientChannelNumber { get; private set; }
- public uint ServerChannelNumber { get; set; }
+ public uint ServerChannelNumber { get; private set; }
- public uint WindowSize { get; set; }
+ public uint WindowSize { get; private set; }
- public uint PacketSize { get; set; }
+ public uint PacketSize { get; private set; }
public bool IsOpen { get; private set; }
+ public event EventHandler Opened;
+
+ public event EventHandler Closed;
+
+ public event EventHandler OpenFailed;
+
protected Session Session { get; private set; }
public Channel(Session session, uint channelId, uint windowSize, uint packetSize)
@@ -61,13 +74,12 @@ namespace Renci.SshClient.Channels
public virtual void Open()
{
- this.Session.MessageReceived += SessionInfo_MessageReceived;
-
// Open session channel
if (!this.IsOpen)
{
this.SendChannelOpenMessage();
this.Session.WaitHandle(this._channelOpenWaitHandle);
+ // TODO: Throw exception if channel could not be open, this can happend after sever failed open attempts
}
}
@@ -75,10 +87,7 @@ namespace Renci.SshClient.Channels
{
if (this.IsOpen)
{
- this.SendMessage(new ChannelCloseMessage
- {
- ChannelNumber = this.ServerChannelNumber,
- });
+ this.SendChannelCloseMessage();
// Wait for channel to be closed
this.Session.WaitHandle(this._channelClosedWaitHandle);
@@ -87,6 +96,11 @@ namespace Renci.SshClient.Channels
this.CloseCleanup();
}
+ internal void HandleChannelMessage(ChannelMessage message)
+ {
+ this.HandleMessage((dynamic)message);
+ }
+
protected virtual void OnChannelData(string data)
{
}
@@ -105,7 +119,6 @@ namespace Renci.SshClient.Channels
protected virtual void OnChannelClose()
{
- // TODO: Channel is closed when it sent and received Close message
}
protected virtual void OnChannelFailed(uint reasonCode, string description)
@@ -117,17 +130,6 @@ namespace Renci.SshClient.Channels
this.Session.SendMessage(message);
}
- private void SessionInfo_MessageReceived(object sender, MessageReceivedEventArgs e)
- {
- ChannelMessage message = e.Message as ChannelMessage;
-
- // Handle only messages belong to this channel or channel open confirmation
- if (message.ChannelNumber == this.ClientChannelNumber || e.Message is ChannelOpenConfirmationMessage)
- {
- this.HandleMessage((dynamic)e.Message);
- }
- }
-
#region Message handlers
private void HandleMessage(T message) where T : Message
@@ -137,30 +139,33 @@ namespace Renci.SshClient.Channels
private void HandleMessage(ChannelOpenConfirmationMessage message)
{
- // Make sure we open channel only for requested channel number
- if (this.ClientChannelNumber != message.ChannelNumber)
- return;
-
this.ServerChannelNumber = message.ServerChannelNumber;
- this.IsOpen = true;
this.WindowSize = message.InitialWindowSize;
this.PacketSize = message.MaximumPacketSize;
+
+ this.IsOpen = true;
+
+ this.RaiseOpened();
+
this._channelOpenWaitHandle.Set();
}
private void HandleMessage(ChannelOpenFailureMessage message)
{
- if (_chanelOpenFailedAttempts > this.Session.ConnectionInfo.RetryAttempts)
+ if (this._failedOpenAttempts < this.Session.ConnectionInfo.RetryAttempts)
{
- this.IsOpen = false;
-
- this._channelOpenWaitHandle.Set();
- this.OnChannelFailed(message.ReasonCode, message.Description);
+ this.SendChannelOpenMessage();
+ this._failedOpenAttempts++;
}
else
{
- // Send channel open message again
- this.SendChannelOpenMessage();
+ this.RaiseOpenFailed();
+
+ this.CloseCleanup();
+
+ this.OnChannelFailed(message.ReasonCode, message.Description);
+
+ this._channelOpenWaitHandle.Set();
}
}
@@ -215,6 +220,10 @@ namespace Renci.SshClient.Channels
private void HandleMessage(ChannelCloseMessage message)
{
+ this.OnChannelClose();
+
+ this.RaiseClosed();
+
this.CloseCleanup();
this._channelClosedWaitHandle.Set();
@@ -238,7 +247,31 @@ namespace Renci.SshClient.Channels
#endregion
- private void SendChannelOpenMessage()
+ private void RaiseOpened()
+ {
+ if (this.Opened != null)
+ {
+ this.Opened(this, new ChannelEventArgs(this.ClientChannelNumber));
+ }
+ }
+
+ private void RaiseClosed()
+ {
+ if (this.Closed != null)
+ {
+ this.Closed(this, new ChannelEventArgs(this.ClientChannelNumber));
+ }
+ }
+
+ private void RaiseOpenFailed()
+ {
+ if (this.OpenFailed != null)
+ {
+ this.OpenFailed(this, new ChannelEventArgs(this.ClientChannelNumber));
+ }
+ }
+
+ internal void SendChannelOpenMessage()
{
this.SendMessage(new ChannelOpenMessage
{
@@ -249,14 +282,31 @@ namespace Renci.SshClient.Channels
});
}
+ private void SendChannelCloseMessage()
+ {
+ if (this._closeMessageSent)
+ return;
+
+ lock (this)
+ {
+ if (this._closeMessageSent)
+ return;
+
+ this.SendMessage(new ChannelCloseMessage
+ {
+ ChannelNumber = this.ServerChannelNumber,
+ });
+ this._closeMessageSent = true;
+ }
+ }
+
private void CloseCleanup()
{
- if (this.IsOpen)
- {
- this.IsOpen = false;
+ if (!this.IsOpen)
+ return;
- this.Session.MessageReceived -= SessionInfo_MessageReceived;
- }
+ this.IsOpen = false;
+ this.SendChannelCloseMessage();
}
#region IDisposable Members
diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelEventArgs.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelEventArgs.cs
new file mode 100644
index 00000000..b7d7fc01
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Renci.SshClient.Channels
+{
+ internal class ChannelEventArgs : EventArgs
+ {
+ public uint ChannelNumber { get; private set; }
+
+ public ChannelEventArgs(uint channelNumber)
+ {
+ this.ChannelNumber = channelNumber;
+ }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Common/BlockingStack.cs b/Renci.SshClient/Renci.SshClient/Common/BlockingStack.cs
index c1938a19..b9b195fc 100644
--- a/Renci.SshClient/Renci.SshClient/Common/BlockingStack.cs
+++ b/Renci.SshClient/Renci.SshClient/Common/BlockingStack.cs
@@ -6,8 +6,6 @@ namespace Renci.SshClient.Common
{
public class BlockingStack : IEnumerable, ICollection, IEnumerable
{
- //private EventWaitHandle _updated = new AutoResetEvent(false);
-
private Stack _stack;
public BlockingStack()
@@ -18,7 +16,6 @@ namespace Renci.SshClient.Common
public BlockingStack(IEnumerable collection)
{
this._stack = new Stack(collection);
- //this._updated.Set();
}
public BlockingStack(int capacity)
@@ -85,7 +82,6 @@ namespace Renci.SshClient.Common
while (this._stack.Count == 0)
{
Monitor.Wait(this);
- //this._updated.WaitOne();
}
var result = this._stack.Pop();
Monitor.PulseAll(this);
@@ -98,7 +94,6 @@ namespace Renci.SshClient.Common
lock (this)
{
this._stack.Push(item);
- //this._updated.Set();
Monitor.PulseAll(this);
}
}
diff --git a/Renci.SshClient/Renci.SshClient/ConnectionInfo.cs b/Renci.SshClient/Renci.SshClient/ConnectionInfo.cs
index f1dcdbcf..b4bf2090 100644
--- a/Renci.SshClient/Renci.SshClient/ConnectionInfo.cs
+++ b/Renci.SshClient/Renci.SshClient/ConnectionInfo.cs
@@ -23,7 +23,7 @@
// Set default connection values
this.Port = 22;
this.Timeout = 1000 * 10; // Set default timeout to 10 sec
- this.RetryAttempts = 3;
+ this.RetryAttempts = 10;
this.MaxSessions = 10;
}
}
diff --git a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj
index 9d3db1bd..1bb4cc31 100644
--- a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj
+++ b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj
@@ -61,6 +61,7 @@
+
diff --git a/Renci.SshClient/Renci.SshClient/Session.cs b/Renci.SshClient/Renci.SshClient/Session.cs
index 3422799f..4ebafd2b 100644
--- a/Renci.SshClient/Renci.SshClient/Session.cs
+++ b/Renci.SshClient/Renci.SshClient/Session.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
@@ -76,7 +75,7 @@ namespace Renci.SshClient
///
/// Keeps track of all open channels
///
- private IDictionary _openChannels = new Dictionary();
+ private Dictionary _openChannels = new Dictionary();
///
/// Exception that need to be thrown by waiting thread
@@ -88,11 +87,6 @@ namespace Renci.SshClient
///
private bool _isAuthenticated;
- ///
- /// Specifies weither socket is connected or not
- ///
- private bool _isSocketConnected;
-
///
/// holds number to be used for session channels
///
@@ -149,7 +143,7 @@ namespace Renci.SshClient
{
get
{
- return this._isSocketConnected && this._isAuthenticated;
+ return this._socket != null && this._socket.Connected && this._isAuthenticated;
}
}
@@ -180,7 +174,10 @@ namespace Renci.SshClient
this.ClientVersion = string.Format("SSH-2.0-Renci.SshClient.{0}", this.GetType().Assembly.GetName().Version);
// Prepopulate chanel numbers that will be used
- System.Threading.Tasks.Parallel.For(0, connectionInfo.MaxSessions, (i) => { this._channelNumbers.Push((uint)i); });
+ for (int i = connectionInfo.MaxSessions - 1; i > 0; i--)
+ {
+ this._channelNumbers.Push((uint)i - 1 + 100);
+ }
}
private static IDictionary> _channels = new Dictionary>()
@@ -202,7 +199,20 @@ namespace Renci.SshClient
throw new InvalidOperationException("Not connected");
}
- return _channels[typeof(T)](this, this._channelNumbers.WaitAndPop()) as T;
+ T channel;
+ var clientChannelId = this._channelNumbers.WaitAndPop();
+
+ lock (this._openChannels)
+ {
+ channel = _channels[typeof(T)](this, clientChannelId) as T;
+
+ channel.Closed += Channel_Closed;
+ channel.OpenFailed += Channel_Closed;
+
+ this._openChannels.Add(clientChannelId, channel);
+ }
+
+ return channel;
}
public void Connect()
@@ -245,10 +255,6 @@ namespace Renci.SshClient
this._socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);
- this._isSocketConnected = true;
-
- //this._socketStream = new NetworkStream(this._socket);
-
// Get server version from the server,
// ignore text lines which are sent before if any
using (var ns = new NetworkStream(this._socket))
@@ -384,7 +390,7 @@ namespace Renci.SshClient
/// The message.
internal virtual void SendMessage(Message message)
{
- if (!this._isSocketConnected)
+ if (!this._socket.Connected)
return;
// TODO: Refactor so we lock only _outboundPacketSequence and _inboundPacketSequence relevant operations
@@ -432,7 +438,6 @@ namespace Renci.SshClient
var encryptedData = packetData.ToList();
if (this.ClientCipher != null)
{
- //encryptedData = new List(this.Encrypt(packetData));
encryptedData = new List(this.ClientCipher.Encrypt(packetData));
}
@@ -461,14 +466,13 @@ namespace Renci.SshClient
///
protected virtual Message ReceiveMessage()
{
- if (!this._isSocketConnected)
+ if (!this._socket.Connected)
return null;
// No lock needed since all messages read by only one thread
List decryptedData;
- //var blockSize = this.Decryption == null ? (byte)8 : (byte)this.Decryption.InputBlockSize;
var blockSize = this.ServerCipher == null ? (byte)8 : (byte)this.ServerCipher.BlockSize;
// Read packet lenght first
@@ -480,7 +484,6 @@ namespace Renci.SshClient
}
else
{
- //decryptedData = new List(this.Decrypt(data));
decryptedData = new List(this.ServerCipher.Decrypt(data));
}
@@ -503,7 +506,6 @@ namespace Renci.SshClient
}
else
{
- //decryptedData.AddRange(this.Decrypt(data));
decryptedData.AddRange(this.ServerCipher.Decrypt(data));
}
bytesToRead -= blockSize;
@@ -543,14 +545,16 @@ namespace Renci.SshClient
protected virtual void HandleMessage(T message) where T : Message
{
// Do nothing as message could be proccessed by other module
+ if (message is ChannelMessage)
+ {
+ this.HandleMessage(message as ChannelMessage);
+ }
}
#region Handle transport messages
protected virtual void HandleMessage(DisconnectMessage message)
{
- this._isSocketConnected = false; // Connection is terminated after this message
-
this.DisconnectCleanup();
}
@@ -637,6 +641,25 @@ namespace Renci.SshClient
#endregion
+ #region Handle channel messages
+
+ protected virtual void HandleMessage(ChannelMessage message)
+ {
+ var channel = this._openChannels[message.ChannelNumber];
+ channel.HandleChannelMessage(message);
+ }
+
+ private void Channel_Closed(object sender, ChannelEventArgs e)
+ {
+ lock (this._openChannels)
+ {
+ this._openChannels.Remove(e.ChannelNumber);
+ this._channelNumbers.Push(e.ChannelNumber);
+ }
+ }
+
+ #endregion
+
///
/// Disconnects client from the server with specified reason code.
///
@@ -669,11 +692,11 @@ namespace Renci.SshClient
while (totalBytesRead < length)
{
- //int bytesRead = this._socketStream.Read(buffer, 0, length);
int bytesRead = this._socket.Receive(buffer, length, SocketFlags.None);
if (bytesRead == 0)
{
+ this._socket.Close();
throw new IOException("Unable to read data to the transport connection: An established connection was aborted by the software in your host machine");
}
@@ -697,17 +720,7 @@ namespace Renci.SshClient
/// The data.
protected void Write(byte[] data)
{
- try
- {
- //this._socketStream.Write(data, 0, data.Length);
- //this._socketStream.Flush();
- this._socket.Send(data);
- }
- catch (Exception)
- {
- this._isSocketConnected = false;
- throw;
- }
+ this._socket.Send(data);
}
///
@@ -787,21 +800,12 @@ namespace Renci.SshClient
///
private void DisconnectCleanup()
{
- // Stop running listener thread
-
-
- //if (this._socketStream != null)
- // this._socketStream.Close();
-
// Close all open channels if any
if (this.IsConnected)
{
- foreach (var channelId in this._openChannels.Values)
+ foreach (var channel in this._openChannels.Values)
{
- this.SendMessage(new ChannelCloseMessage
- {
- ChannelNumber = channelId,
- });
+ channel.Close();
}
}
@@ -810,8 +814,6 @@ namespace Renci.SshClient
{
this._socket.Close();
}
-
- this._isSocketConnected = false;
}
///
@@ -821,7 +823,7 @@ namespace Renci.SshClient
{
try
{
- while (this._isSocketConnected)
+ while (this._socket.Connected)
{
dynamic message = this.ReceiveMessage();
@@ -830,8 +832,6 @@ namespace Renci.SshClient
throw new NullReferenceException("The 'message' variable cannot be null");
}
- Debug.WriteLine(((Message)message).MessageType);
-
// Handle session messages first
this.HandleMessage(message);
@@ -841,7 +841,7 @@ namespace Renci.SshClient
}
catch (IOException)
{
- this._isSocketConnected = false;
+ // Ignore this error since socket was disconected
}
catch (Exception exp)
{
@@ -869,43 +869,6 @@ namespace Renci.SshClient
}
}
- ///
- /// Handles message.
- ///
- /// The message.
- private void HandleMessage(ChannelOpenConfirmationMessage message)
- {
- // Keep track of open channels
- lock (this._openChannels)
- {
- this._openChannels.Add(message.ChannelNumber, message.ServerChannelNumber);
- }
- }
-
- ///
- /// Handles message.
- ///
- /// The message.
- private void HandleMessage(ChannelCloseMessage message)
- {
- // Keep track of open channels
- lock (this._openChannels)
- {
- this._openChannels.Remove(message.ChannelNumber);
- this._channelNumbers.Push(message.ChannelNumber);
- }
- }
-
- private void HandleMessage(ChannelFailureMessage message)
- {
- // Keep track of open channels
- lock (this._openChannels)
- {
- this._openChannels.Remove(message.ChannelNumber);
- this._channelNumbers.Push(message.ChannelNumber);
- }
- }
-
#region IDisposable Members
private bool disposed = false;
@@ -926,11 +889,6 @@ namespace Renci.SshClient
// and unmanaged resources.
if (disposing)
{
- //if (this._socketStream != null)
- //{
- // this._socket.Dispose();
- //}
-
// Dispose managed resources.
if (this._socket != null)
{
@@ -968,6 +926,5 @@ namespace Renci.SshClient
}
#endregion
-
}
}