/// <summary> /// Adds connected client to the list of clients. /// </summary> /// <param name="client">Client to add.</param> private void AddConnectedClient(NetworkPeerClient client) { this.logger.LogTrace("({0}.{1}:{2})", nameof(client), nameof(client.Id), client.Id); this.clientsById.AddOrReplace(client.Id, client); this.logger.LogTrace("(-)"); }
/// <summary> /// Adds connected client to the list of clients. /// </summary> /// <param name="client">Client to add.</param> private void AddConnectedClient(NetworkPeerClient client) { this.logger.LogTrace("({0}.{1}:{2})", nameof(client), nameof(client.Id), client.Id); this.clientsById.AddOrReplace(client.Id, client); client.ProcessingCompletion.Task.ContinueWith(unused => this.RemoveConnectedClient(client)); this.logger.LogTrace("(-)"); }
/// <summary> /// Initializes an instance of the object. /// </summary> /// <param name="peer">Network peer the node is connected to.</param> /// <param name="client">Connected network client to the peer.</param> /// <param name="dateTimeProvider">Provider of time functions.</param> /// <param name="loggerFactory">Factory for creating loggers.</param> public NetworkPeerConnection(NetworkPeer peer, NetworkPeerClient client, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory) { this.logger = loggerFactory.CreateLogger(this.GetType().FullName, $"[{peer.PeerAddress.Endpoint}] "); this.dateTimeProvider = dateTimeProvider; this.Peer = peer; this.Client = client; this.Messages = new BlockingCollection <SentMessage>(new ConcurrentQueue <SentMessage>()); }
/// <summary> /// Implements loop accepting connections from newly connected clients. /// </summary> private async Task AcceptClientsAsync() { this.logger.LogTrace("()"); this.logger.LogTrace("Accepting incoming connections."); try { while (!this.serverCancel.IsCancellationRequested) { // Used to record any errors occurring in the thread pool task. Exception error = null; TcpClient tcpClient = await Task.Run(() => { try { Task <TcpClient> acceptTask = this.tcpListener.AcceptTcpClientAsync(); acceptTask.Wait(this.serverCancel.Token); return(acceptTask.Result); } catch (Exception e) { // Record the error. error = e; return(null); } }).ConfigureAwait(false); // Raise the error. if (error != null) { throw error; } NetworkPeerClient client = this.networkPeerFactory.CreateNetworkPeerClient(tcpClient); this.AddConnectedClient(client); this.logger.LogTrace("Connection accepted from client '{0}'.", client.RemoteEndPoint); // This should be cheaper for the accept loop thread than just calling ProcessNewClientAsync without awaiting. Task unused = Task.Run(async() => await this.ProcessNewClientAsync(client)); } } catch (OperationCanceledException) { this.logger.LogDebug("Shutdown detected, stop accepting connections."); } catch (Exception e) { this.logger.LogDebug("Exception occurred: {0}", e.ToString()); } this.logger.LogTrace("(-)"); }
/// <summary> /// Removes a client from the list of clients and disconnects it. /// </summary> /// <param name="client">Client to remove and disconnect.</param> private void RemoveConnectedClient(NetworkPeerClient client) { this.logger.LogTrace("({0}.{1}:{2})", nameof(client), nameof(client.Id), client.Id); if (!this.clientsById.TryRemove(client.Id, out NetworkPeerClient unused)) { this.logger.LogError("Internal data integration error."); } this.logger.LogTrace("(-)"); }
/// <summary> /// Initializes an instance of the object. /// </summary> /// <param name="peer">Network peer the node is connected to.</param> /// <param name="client">Connected network client to the peer.</param> /// <param name="dateTimeProvider">Provider of time functions.</param> /// <param name="loggerFactory">Factory for creating loggers.</param> public NetworkPeerConnection(NetworkPeer peer, NetworkPeerClient client, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory) { this.logger = loggerFactory.CreateLogger(this.GetType().FullName, $"[{peer.PeerAddress.Endpoint}] "); this.dateTimeProvider = dateTimeProvider; this.Peer = peer; this.Client = client; this.Cancel = new CancellationTokenSource(); this.cancelRegistration = this.Cancel.Token.Register(this.InitiateShutdown); this.Disconnected = new ManualResetEvent(false); }
/// <summary> /// Initializes an instance of the object for outbound network peers. /// </summary> /// <param name="peerAddress">Information about the peer including its network address, protocol version, time of last contact.</param> /// <param name="network">Specification of the network the node runs on - regtest/testnet/mainnet.</param> /// <param name="parameters">Various settings and requirements related to how the connections with peers are going to be established, or <c>null</c> to use default parameters.</param> /// <param name="networkPeerFactory">Factory for creating P2P network peers.</param> /// <param name="dateTimeProvider">Provider of time functions.</param> /// <param name="loggerFactory">Factory for creating loggers.</param> public NetworkPeer(NetworkAddress peerAddress, Network network, NetworkPeerConnectionParameters parameters, INetworkPeerFactory networkPeerFactory, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory) : this(false, peerAddress, network, parameters, dateTimeProvider, loggerFactory) { NetworkPeerClient client = networkPeerFactory.CreateNetworkPeerClient(parameters); this.logger = loggerFactory.CreateLogger(this.GetType().FullName, $"[{client.Id}-{peerAddress.Endpoint}] "); this.logger.LogTrace("()"); this.Connection = new NetworkPeerConnection(this, client, this.dateTimeProvider, this.loggerFactory); this.logger.LogTrace("(-)"); }
/// <summary> /// Initializes an instance of the object for outbound network peers. /// </summary> /// <param name="peerAddress">Information about the peer including its network address, protocol version, time of last contact.</param> /// <param name="network">Specification of the network the node runs on - regtest/testnet/mainnet.</param> /// <param name="parameters">Various settings and requirements related to how the connections with peers are going to be established, or <c>null</c> to use default parameters.</param> /// <param name="networkPeerFactory">Factory for creating P2P network peers.</param> /// <param name="dateTimeProvider">Provider of time functions.</param> /// <param name="loggerFactory">Factory for creating loggers.</param> public NetworkPeer(NetworkAddress peerAddress, Network network, NetworkPeerConnectionParameters parameters, INetworkPeerFactory networkPeerFactory, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory) : this(false, peerAddress, network, parameters, dateTimeProvider, loggerFactory) { this.logger.LogTrace("()"); NetworkPeerClient client = networkPeerFactory.CreateNetworkPeerClient(parameters); this.Connection = new NetworkPeerConnection(this, client, this.dateTimeProvider, this.loggerFactory); this.ConnectAsync(parameters.ConnectCancellation).GetAwaiter().GetResult(); this.InitDefaultBehaviors(parameters); this.Connection.StartReceiveMessages(); this.logger.LogTrace("(-)"); }
/// <summary> /// Removes a client from the list of clients and disconnects it. /// </summary> /// <param name="client">Client to remove and disconnect.</param> private void RemoveAndDisconnectConnectedClient(NetworkPeerClient client) { this.logger.LogTrace("({0}.{1}:{2})", nameof(client), nameof(client.Id), client.Id); if (!this.clientsById.TryRemove(client.Id, out NetworkPeerClient unused)) { this.logger.LogError("Internal data integration error."); } TaskCompletionSource <bool> completion = client.ProcessingCompletion; client.Dispose(); completion.SetResult(true); this.logger.LogTrace("(-)"); }
/// <summary> /// Implements loop accepting connections from newly connected clients. /// </summary> private async Task AcceptClientsAsync() { this.logger.LogTrace("()"); this.logger.LogTrace("Accepting incoming connections."); while (!this.serverCancel.IsCancellationRequested) { try { this.serverCancel.Token.ThrowIfCancellationRequested(); TcpClient tcpClient = await Task.Run(async() => await this.tcpListener.AcceptTcpClientAsync(), this.serverCancel.Token); NetworkPeerClient client = this.networkPeerFactory.CreateNetworkPeerClient(tcpClient); this.AddConnectedClient(client); this.logger.LogTrace("Connection accepted from client '{0}'.", client.RemoteEndPoint); Task unused = Task.Run(() => this.ProcessNewClient(client)); } catch (Exception e) { if (e is OperationCanceledException) { this.logger.LogDebug("Shutdown detected, stop accepting connections."); } else { this.logger.LogDebug("Exception occurred: {0}"); } break; } } this.logger.LogTrace("(-)"); }
/// <summary> /// Implements loop accepting connections from newly connected clients. /// </summary> private async Task AcceptClientsAsync() { this.logger.LogTrace("()"); this.logger.LogTrace("Accepting incoming connections."); try { while (!this.serverCancel.IsCancellationRequested) { // Used to record any errors occurring in the thread pool task. Exception error = null; TcpClient tcpClient = await Task.Run(() => { try { Task <TcpClient> acceptTask = this.tcpListener.AcceptTcpClientAsync(); acceptTask.Wait(this.serverCancel.Token); return(acceptTask.Result); } catch (Exception e) { // Record the error. error = e; return(null); } }).ConfigureAwait(false); // Raise the error. if (error != null) { throw error; } NetworkPeerClient client = this.networkPeerFactory.CreateNetworkPeerClient(tcpClient); this.logger.LogTrace("Connection accepted from client '{0}'.", client.RemoteEndPoint); var peerAddress = new NetworkAddress() { Endpoint = client.RemoteEndPoint, Time = this.dateTimeProvider.GetUtcNow() }; NetworkPeer networkPeer = this.networkPeerFactory.CreateNetworkPeer(peerAddress, this.Network, client, this.CreateNetworkPeerConnectionParameters()); this.ConnectedNetworkPeers.Add(networkPeer); networkPeer.StateChanged += this.Peer_StateChanged; this.AddConnectedClient(client); } } catch (OperationCanceledException) { this.logger.LogDebug("Shutdown detected, stop accepting connections."); } catch (Exception e) { this.logger.LogDebug("Exception occurred: {0}", e.ToString()); } this.logger.LogTrace("(-)"); }
/// <summary> /// Initializes an instance of the object for inbound network peers with already established connection. /// </summary> /// <param name="peerAddress">Information about the peer including its network address, protocol version, time of last contact.</param> /// <param name="network">Specification of the network the node runs on - regtest/testnet/mainnet.</param> /// <param name="parameters">Various settings and requirements related to how the connections with peers are going to be established, or <c>null</c> to use default parameters.</param> /// <param name="client">Already connected network client.</param> /// <param name="peerVersion">Version message payload received from the peer.</param> /// <param name="dateTimeProvider">Provider of time functions.</param> /// <param name="loggerFactory">Factory for creating loggers.</param> public NetworkPeer(NetworkAddress peerAddress, Network network, NetworkPeerConnectionParameters parameters, NetworkPeerClient client, VersionPayload peerVersion, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory) : this(true, peerAddress, network, parameters, dateTimeProvider, loggerFactory) { this.logger.LogTrace("()"); this.RemoteSocketEndpoint = client.RemoteEndPoint; this.RemoteSocketAddress = this.RemoteSocketEndpoint.Address; this.RemoteSocketPort = this.RemoteSocketEndpoint.Port; this.PeerVersion = peerVersion; this.Connection = new NetworkPeerConnection(this, client, this.dateTimeProvider, this.loggerFactory); this.ConnectedAt = this.dateTimeProvider.GetUtcNow(); this.logger.LogTrace("Connected to advertised node '{0}'.", this.PeerAddress.Endpoint); this.State = NetworkPeerState.Connected; this.InitDefaultBehaviors(parameters); this.Connection.StartReceiveMessages(); this.logger.LogTrace("(-)"); }
/// <inheritdoc/> public NetworkPeer CreateNetworkPeer(NetworkAddress peerAddress, Network network, NetworkPeerConnectionParameters parameters, NetworkPeerClient client, VersionPayload peerVersion) { return(new NetworkPeer(peerAddress, network, parameters, client, peerVersion, this.dateTimeProvider, this.loggerFactory)); }
/// <summary> /// Handles a newly accepted client's connection. /// </summary> /// <param name="client">Newly accepted client.</param> private async Task ProcessNewClientAsync(NetworkPeerClient client) { this.logger.LogTrace("({0}:{1})", nameof(client), client.RemoteEndPoint); bool keepClientConnected = false; EndPoint clientEndPoint = client.RemoteEndPoint; try { if (this.ConnectedNetworkPeers.Count < this.MaxConnections) { using (var cancel = CancellationTokenSource.CreateLinkedTokenSource(this.serverCancel.Token)) { cancel.CancelAfter(TimeSpan.FromSeconds(10)); while (true) { cancel.Token.ThrowIfCancellationRequested(); Message message = await client.ReadAndParseMessageAsync(this.Version, cancel.Token).ConfigureAwait(false); this.messageProducer.PushMessage(new IncomingMessage() { Client = client, Message = message, Length = message.MessageSize, NetworkPeer = null, }); if (message.Payload is VersionPayload) { this.logger.LogTrace("Connection with client '{0}' successfully initiated.", client.RemoteEndPoint); keepClientConnected = true; break; } this.logger.LogTrace("The first message of the remote peer '{0}' did not contain a version payload.", client.RemoteEndPoint); } } } else { this.logger.LogDebug("Maximum number of connections {0} reached, client '{1}' will be disconnected.", this.MaxConnections, clientEndPoint); } } catch (OperationCanceledException) { if (this.serverCancel.Token.IsCancellationRequested) { this.logger.LogTrace("Shutdown detected."); } else { this.logger.LogTrace("Inbound client '{0}' failed to send a version message within 10 seconds, dropping connection.", client.RemoteEndPoint); } } catch (Exception ex) { this.logger.LogTrace("Exception occurred while processing message from client '{0}': {1}", client.RemoteEndPoint, ex.ToString()); } if (!keepClientConnected) { // NetworkPeer is not created for this client here yet. // This is why we need to finish its completion. client.Dispose(); client.ProcessingCompletion.SetResult(true); } this.logger.LogTrace("(-)"); }
/// <inheritdoc/> public NetworkPeer CreateNetworkPeer(NetworkAddress peerAddress, Network network, NetworkPeerClient client, VersionPayload peerVersion, NetworkPeerConnectionParameters parameters = null) { Guard.NotNull(peerAddress, nameof(peerAddress)); Guard.NotNull(network, nameof(network)); Guard.NotNull(client, nameof(client)); Guard.NotNull(client.RemoteEndPoint, $"{nameof(client)}.{nameof(client.RemoteEndPoint)}"); return(new NetworkPeer(peerAddress, network, parameters, client, peerVersion, this.dateTimeProvider, this.loggerFactory)); }
/// <summary> /// Handles a newly accepted client's connection. /// </summary> /// <param name="client">Newly accepted client.</param> private void ProcessNewClient(NetworkPeerClient client) { this.logger.LogTrace("({0}:{1})", nameof(client), client.RemoteEndPoint); bool keepClientConnected = false; EndPoint clientEndPoint = client.RemoteEndPoint; try { this.serverCancel.Token.ThrowIfCancellationRequested(); if (this.ConnectedNetworkPeers.Count < this.MaxConnections) { using (var cancel = CancellationTokenSource.CreateLinkedTokenSource(this.serverCancel.Token)) { cancel.CancelAfter(TimeSpan.FromSeconds(10)); while (true) { cancel.Token.ThrowIfCancellationRequested(); PerformanceCounter counter; Message message = Message.ReadNext(client.Stream, this.Network, this.Version, cancel.Token, out counter); this.messageProducer.PushMessage(new IncomingMessage() { Client = client, Message = message, Length = counter.ReadBytes, NetworkPeer = null, }); if (message.Payload is VersionPayload) { this.logger.LogTrace("Connection with client '{0}' successfully initiated.", client.RemoteEndPoint); keepClientConnected = true; break; } this.logger.LogTrace("The first message of the remote peer '{0}' did not contain a version payload.", client.RemoteEndPoint); } } } else { this.logger.LogDebug("Maximum number of connections {0} reached, client '{1}' will be disconnected.", this.MaxConnections, clientEndPoint); } } catch (OperationCanceledException) { if (this.serverCancel.Token.IsCancellationRequested) { this.logger.LogTrace("Shutdown detected."); } else { this.logger.LogTrace("Inbound client '{0}' failed to send a message within 10 seconds, dropping connection.", client.RemoteEndPoint); } } catch (Exception ex) { this.logger.LogTrace("Exception occurred while processing message from client '{0}': {1}", client.RemoteEndPoint, ex.ToString()); } if (!keepClientConnected) { this.RemoveAndDisconnectConnectedClient(client); } this.logger.LogTrace("(-)"); }