havendv / h.pipes Goto Github PK
View Code? Open in Web Editor NEWA simple, easy to use, strongly-typed, async wrapper around .NET named pipes.
License: MIT License
A simple, easy to use, strongly-typed, async wrapper around .NET named pipes.
License: MIT License
When transferring messages larger than 65535 bytes on .NET 6 the server throws an exception stating
Expected <number of sent> bytes but read 65536
Send the contents of a 1MB file over the channel and catch the exception
Same issue whether I send a serializable object or a standard string
No response
No response
No response
Console
Visual Studio 2022
Seems to be related to this:
I am looking for a reliable named pipes server for communication between C ++ and C #.
Unfortunately, the H.Pipes server can easily crash due to an error in the client.
Here's an example code that shows how to make the server stop responding to new connections:
using System;
using System.IO.Pipes;
using System.Net;
using System.Threading;
namespace HPipeError
{
internal class Program
{
private static void Main(string[] args)
{
var pipeClient = new NamedPipeClientStream("named_pipe_test_server");
while (!pipeClient.IsConnected)
{
pipeClient.Connect();
Thread.Sleep(100);
}
// read string length
var buffer = new byte[sizeof(int)];
pipeClient.Read(buffer, 0, sizeof(int));
var len = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(buffer, 0));
// read string content
buffer = new byte[len];
pipeClient.Read(buffer, 0, len);
}
}
}
No response
No response
No response
No response
No response
Why is this happening?
After establishing the connection by the client, the server sends the name of the pipe created for the client through the main pipe.
The server then waits for the client to establish a connection to the newly created pipe, but unfortunately the main pipe is closed.
If the client fails to connect, the main pipe will not be recreated and you will not be able to connect to the server anymore.
So one malfunctioning client can therefore block the server.
Hi,
I was trying to implement the H.Pipes thanks to the blog of Erik Engberg's blog post. This library saved me from a huge blocker.
Currently, I am trying to implement a timeout, retry and circuit breaker policy. My option was to track the time and cancel the ConnectAsync()
method. I used the Microsoft documentation on CancellationTokenSource
.
However, I believe this needs to be handled as if HTTP requests are handled via Polly. In that case, it can be done either by allowing Polly to handle the resiliency policies or by adding those policies into the H.Pipes. directly. In either case, it requires a TimeOut exception, so that either Polly or the pipe itself can handle the retries and possible circuit breaking.
Initially, I planned to do it myself and send a PR but I just followed the docs and moved on due to project deadlines. I hope we can see these policies in H.Pipes.
Edit: The piece of code below is based on the example, in the class NamedPipeClient.cs
. It includes only timeout. I did not try to add retry yet.
private static readonly CancellationTokenSource Cts = new();
private readonly int _cancellationTimeout;
public NamedPipeClient(int timeout = 500)
{
_cancellationTimeout = timeout;
//...
}
public async Task InitializeAsync()
{
// This try/catch/finally might be included in the library.
// So ctor parameter would suffice
try
{
Cts.CancelAfter(_cancellationTimeout);
await _client.ConnectAsync(Cts.Token);
await _client.WriteAsync(new PipeMessage
{
Action = ActionType.SendText,
Text = "Hello from client",
}, Cts.Token);
}
catch (TaskCanceledException)
{
Debug.WriteLine($"Task cancelled after timeout {_cancellationTimeout}.");
Debug.WriteLine(e.Message);
///...
}
finally
{
// this is tricky. If the Cts will live throughout the lifetime of the class,
// this should be in the Dispose method of the class, not here
Cts.Dispose();
}
}
https://github.com/HavenDV/H.Pipes/runs/4315659183?check_suite_focus=true
Test method H.Pipes.Tests.DataTests.TestMessageSize1B threw exception:
System.IO.IOException: All pipe instances are busy.
It seems only in .NET Framework 4.8.
I am working on building a vsix solution that requires that an object must cross from a .net framework process to net core process. When the .net framework process signals the net core process then acts and returns the results. named pipes are involved for certain and I believe RPC is involved when the object shared back and forth has method functions to set some serialized data in the remote object.
will H.Pipes accomplish this?
I have a service that would need to accept client data that runs without elevated permissions
I use this code
_server = new PipeServer<T>(pipeName, new NewtonsoftJsonFormatter());
_server.AllowUsersReadWrite();
My developer machine can connect just fine, some other machines however can't. If I take the NuGet package where I wrapped the PipeServer and PipeClient in and create a chat app using Winforms that same machine accepts the client without any issues, both are .net 6.0
If I take the Production client and have it connect to the Winforms test service all seems to work well.
The only difference is that one is hosted in service, the other is in a normal WinForms, with no changes made to the machines that are being problematic.
What am I doing wrong, how can I solve the issue?
Description: The process was terminated due to an unhandled exception.
Exception Info: System.IO.FileLoadException
at System.Threading.Tasks.ValueTask.get_IsCompleted()
at H.Pipes.PipeClient1+<DisconnectInternalAsync>d__48[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task) at H.Pipes.PipeClient
1+<b__46_0>d[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
Hi, thanks for the great job. I have noticed several problems
_server.ClientDisconnected += async (o, args) =>
{
await args.Connection.DisposeAsync();
Debug.WriteLine($"Client {args.Connection.PipeName} disconnected");
};
After that the memory ceased to disappear. This helped free up memory.
Then the break stops the while cycle. New clients can no longer connect to the server.
My solution:
replace break;
to throw;
Question: How reasonable was the break in this place? Is my fix correct or should this cycle be created per client?
{
var buffer = new byte[length];
var read = await BaseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
if (read != length)
{
return throwIfReadLessThanLength
? throw new IOException($"Expected {length} bytes but read {read}")
: Array.Empty<byte>();
}
return buffer;
}
this code looks cleaner and easier to maintain. Same with WriteAsync.
I have a program with a client that sends a message and waits for a response, and a server that waits for clients, and when one sends a message, the server sends a response.
Sometimes the server crashes when it tries to send a response with args.Connection.WriteAsync
at System.IO.Pipes.PipeStream.CheckWriteOperations()
at System.IO.Pipes.PipeStream.Flush()
at System.IO.Pipes.PipeStream.FlushAsync(CancellationToken cancellationToken)
--- End of stack trace from previous location ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at H.Pipes.IO.PipeStreamWriter.<WriteAsync>d__8.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at H.Pipes.IO.PipeStreamWrapper.<WriteAsync>d__17.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at H.Pipes.PipeConnection`1.<WriteAsync>d__38.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Program.<>c.<<<Main>$>b__0_2>d.MoveNext() in C:\Users\Erez\source\repos\HPipeMultiClient\Server\Server.cs:line 21
Happens when running the server in debug from Visual Studio. Doesn't happen when running the server from console.
server.MessageReceived += async (sender, args) =>
{
Console.WriteLine($"Client {args.Connection.PipeName} says: {args.Message}");
var response = new Message { Response = args.Message!.Request + args.Message!.Request };
Console.WriteLine($"Sending to {args.Connection.PipeName}: {response}");
await args.Connection.WriteAsync(response);
};
No crash, send message successfully
2.0.37
Console
Visual Studio 2022
No response
Sometimes need to transfer large files. Right now PipeConnection does not provide the ability to work with Stream. It means a response side should upload part of files into memory.
Not destroyed handles after a new connection is once used (writeasynch) and disposed. It leads to OutOfMemoryException after many iterations in the same process.
Create a new pipe client:
_client = new PipeClient(uri, ".", null);
_client.ConnectAsync().Wait(30);
_client.ExceptionOccurred += Client_ExceptionOccurred;
_client.MessageReceived += Client_MessageReceived;
Write sometihng to server process:
_client.WriteAsync(messageObject).Wait();
Dispose client after all done:
_client.ExceptionOccurred -= Client_ExceptionOccurred;
_client.MessageReceived -= Client_MessageReceived;
_client.DisconnectAsync().Wait();
_client.DisposeAsync();
_client = null;
Iterate 1000 times -> there are about 1000 more handles in the process. Note: if you omit step 2 - everything works clean.
No additional handles after 1000 iterations within the same client process
No response
1.14.8
Console
Visual Studio 2022, Visual Studio 2019
No response
I have cloned the repo and tried to run the ConsoleApp sample.
By default, .NET 8.0 is selected.
When the client connects to the server it generates the exception:
ConsoleApp.MyMessage is not registered in resolver: MessagePack.Resolvers.StandardResolver
The complete message is
Client named_pipe_test_server_021d8e5e-1e5d-4d21-9abd-4cba84e491db is now connected!
Exception: MessagePack.MessagePackSerializationException: Failed to serialize System.Object value.
---> MessagePack.FormatterNotRegisteredException: ConsoleApp.MyMessage is not registered in resolver: MessagePack.Resolvers.StandardResolver
at MessagePack.FormatterResolverExtensions.Throw(Type t, IFormatterResolver resolver)
at MessagePack.FormatterResolverExtensions.GetFormatterDynamicWithVerify(IFormatterResolver resolver, Type type)
at MessagePack.Formatters.DynamicObjectTypeFallbackFormatter.Serialize(MessagePackWriter& writer, Object value, MessagePackSerializerOptions options)
at MessagePack.MessagePackSerializer.Serialize[T](MessagePackWriter& writer, T value, MessagePackSerializerOptions options)
--- End of inner exception stack trace ---
at MessagePack.MessagePackSerializer.Serialize[T](MessagePackWriter& writer, T value, MessagePackSerializerOptions options)
at MessagePack.MessagePackSerializer.Serialize[T](T value, MessagePackSerializerOptions options, CancellationToken cancellationToken)
at H.Formatters.MessagePackFormatter.SerializeInternal(Object obj) in C:\GitHub\H.Pipes\src\libs\H.Formatters.MessagePack\MessagePackFormatter.cs:line 12
at H.Formatters.FormatterBase.Serialize(Object obj) in C:\GitHub\H.Pipes\src\libs\H.Formatters\FormatterBase.cs:line 28
at H.Pipes.PipeConnection`1.WriteAsync(T value, CancellationToken cancellationToken) in C:\GitHub\H.Pipes\src\libs\H.Pipes\PipeConnection.cs:line 162
at ConsoleApp.MyServer.<>c__DisplayClass1_0.<<RunAsync>b__0>d.MoveNext() in C:\GitHub\H.Pipes\src\samples\ConsoleApp\MyServer.cs:line 30
The error does not occur with .NET 6.0 or .NET 7.0
No response
No response
No response
Console
Visual Studio 2022
No response
Inferno
the recommendation to use H.Formatters.Inferno will fail as the method of encryption is deprecated.
Hi, has anyone checked the work on .NET 8? This doesn't work for me
Using H.Pipes, H.Formatter & H.Formatters.BinaryFormatteer version 2.0.34 NuGets.
I'm writing a plugin for a WinForms 4.7.2 program (server) which is trying to talk to a .NET 6.0 WPF application (client). It uses a separate Pipes_Shared .NET Standard 2.0 class for the messages between the two applications:
[Serializable]
public class PipeMessage
{
public PipeMessage()
{
Id = Guid.NewGuid();
}
public Guid Id { get; set; }
public ActionType Action { get; set; }
public string Text { get; set; } = string.Empty;
}
public enum ActionType
{
Unknown,
SendText,
}
The two ends of the pipe connect just fine. I get a message back from the server that goes through the PipeMessage to send a "You have connected!" message to the WPF client. No problems there. But I send some text back from the client to the server, the
_PipeServer.ExceptionOccurred += (o, args) =>
{
OnExceptionOccurred(args.Exception);
};
trips with the very odd message:
Exception: System.Runtime.Serialization.SerializationException: Unable to find assembly 'pipes_shared, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null'.
at System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly()
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetType(BinaryAssemblyInfo assemblyInfo, String name)
at System.Runtime.Serialization.Formatters.Binary.ObjectMap..ctor(String objectName, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
at H.Formatters.BinaryFormatter.DeserializeInternal[T](Byte[] bytes) in D:\DotNET_Proj\H.Pipes\src\libs\H.Formatters.BinaryFormatter\BinaryFormatter.cs:line 22
at H.Formatters.FormatterBase.Deserialize[T](Byte[] bytes) in D:\DotNET_Proj\H.Pipes\src\libs\H.Formatters\FormatterBase.cs:line 39
at H.Pipes.PipeConnection`1.<<Start>b__37_0>d.MoveNext() in D:\DotNET_Proj\H.Pipes\src\libs\H.Pipes\PipeConnection.cs:line 124
How is that possible that the server found the assembly pipes_shared the first time around to generate the message to the client, but now it can't find it for deserialization?
Thanks,
The line below adds a number for the given pipe name in specific cases.
H.Pipes/src/libs/H.Pipes/PipeServer.cs
Line 155 in 3894e51
In my case, a long-running windows service acts as the named pipe server. After a while, the numbering became an issue and I missed that part. I thought it is my code. I realized the issue is by design thanks to pipelist tool of Sysinternals. Then, I checked if it is an OS thin or the library and found the line above.
On the client side, I solved the issue by enumerating the named pipes and picking the first one that starts with the specified name.
Helper class with enumeration method.
public static class PipeHelper
{
/// <summary>
/// Enumerates the named pipes excluding the ones with illegal names.
/// </summary>
/// <remarks>
/// Pipes might have illegal characters in path. Seen one from IAR containing < and >.
/// The FileSystemEnumerable.MoveNext source code indicates that another call to MoveNext will return
/// the next entry.
/// Pose a limit in case the underlying implementation changes somehow. This also means that no more
/// than 10 (default) pipes with bad names may occur in sequence.
/// </remarks>
/// <param name="retryCount"> If there are more illegal named pipes consequently then this param, it stops enumerating. </param>
/// <see cref="https://stackoverflow.com/a/53432640/5910839"/>
/// <returns>named pipe names</returns>
public static IEnumerable<string> EnumeratePipes(int retryCount = 10)
{
using var enumerator = Directory.EnumerateFiles(@"\\.\pipe\").GetEnumerator();
while (MoveNextSafe(enumerator, retryCount))
{
yield return enumerator.Current.Replace(@"\\.\pipe\", "");
}
}
private static bool MoveNextSafe(IEnumerator<string> enumerator, int retryCount)
{
for (var i = 0; i < retryCount; i++)
{
try
{
return enumerator.MoveNext();
}
catch (ArgumentException)
{
}
}
return false;
}
}
And on the named pipe client:
...
public class NamedPipeClient : IAsyncDisposable
{
private const string PipeName = "MyPipe";
private readonly PipeClient<PipeMessage> _client;
...
public NamedPipeClient(...)
{
var pipe = PipeHelper.EnumeratePipes().First(p => p.StartsWith(PipeName, StringComparison.InvariantCulture));
if (string.IsNullOrEmpty(pipe))
{
throw new InvalidOperationException("No pipe found.");
}
...
You might consider either using it as an example in a sample project or documentation. Or you can add the method in the H.Pipes. It is just an edge case users might face.
I was wondering if there is a configuration I'm missing somewhere. Most examples seem to show using the same generic type for both sending and receiving. Typically though you rarely would receive an object of the same type as the one you are sending. Is there a way to configure H.Pipes to use different types. My current workaround is just including a string in the message that I then deserialize later, but this takes a way a lot of the value that this library adds as its not super hard just to do that to the standard pipe stream without adding another dependency to a project.
CreatePipeStreamFunc = (pipeName) => new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.WriteThrough);
When customizing a NamedPipeServerStream, the client connects and prompts that reading the stream is not supported.
We have recently reproduced bug with H.Pipes.
Nuget version: 2.0.59
Our server receives many IPC messages (~1000/min) in many cases there was just high cpu usage but one of our collegues crashed IPC server
System.Text.Json.JsonException: '0x00' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
---> System.Text.Json.JsonReaderException: '0x00' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0.
at void System.Text.Json.ThrowHelper.ThrowJsonReaderException(ref Utf8JsonReader json, ExceptionResource resource, byte nextByte, ReadOnlySpan<byte> bytes)
at bool System.Text.Json.Utf8JsonReader.ConsumeValue(byte marker)
at bool System.Text.Json.Utf8JsonReader.ReadFirstToken(byte first)
at bool System.Text.Json.Utf8JsonReader.ReadSingleSegment()
at T System.Text.Json.Serialization.JsonConverter<T>.ReadCore(ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state)
--- End of inner exception stack trace ---
at void System.Text.Json.ThrowHelper.ReThrowWithPath(ref ReadStack state, JsonReaderException ex)
at T System.Text.Json.Serialization.JsonConverter<T>.ReadCore(ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state)
at TValue System.Text.Json.JsonSerializer.ReadFromSpan<TValue>(ReadOnlySpan<char> json, JsonTypeInfo<TValue> jsonTypeInfo)
at TValue System.Text.Json.JsonSerializer.Deserialize<TValue>(string json, JsonSerializerOptions options)
at T Ipc.Client.PipeMessageJsonFormatter.DeserializeInternal<T>(byte[] bytes) in /_/src/Ipc/Client/PipeMessageJsonFormatter.cs:line 23
at T H.Formatters.FormatterBase.Deserialize<T>(byte[] bytes) in /_/src/libs/H.Formatters/FormatterBase.cs:line 34
at async void H.Pipes.PipeConnection<T>.Start()+(?) => { }
This is my custom json formatter
public sealed class PipeMessageJsonFormatter : SystemTextJsonFormatter
{
protected override byte[] SerializeInternal(object? obj)
{
if (obj is PipeMessage message)
{
var serialize = JsonSerializer.Serialize<PipeMessage>(message, new JsonSerializerOptions());
var bytes = System.Text.Encoding.UTF8.GetBytes(serialize);
return bytes;
}
throw new InvalidOperationException($"Type of message {obj?.GetType()} is not supported");
}
protected override T DeserializeInternal<T>(byte[] bytes)
{
var json = System.Text.Encoding.UTF8.GetString(bytes);
var deserialized = JsonSerializer.Deserialize<T>(json);
return deserialized ?? throw new InvalidOperationException($"Unable to deserialize message: {json}");
}
}
We have created process dump before crash, but I am not sure how this could happend.
I have investigated dump and there is buffer which is pretty high 1734681213 bytes
. (I am able to provide dump)
In my opinion there are still incomming IPC messages, but nobody can read.
I am not sure if there is 100% way to reproduce this issue.
No response
2.0.59
Console
Visual Studio 2022
No response
When my client was disconnected it throws exception
My usage:
private readonly IPipeClient<PipeMessage> _client;
private async void ClientOnDisconnected(object? _, ConnectionEventArgs<PipeMessage> e)
{
_logger.LogInformation("IPC client name {name} was disconnected", e.Connection.PipeName);
while (!_client.IsConnected)
{
using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3));
try
{
await Start(cancellationTokenSource.Token).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.LogWarning(exception,"Failed to reconnect");
}
}
}
public async Task Start(CancellationToken cancellationToken = default)
{
_logger.LogInformation("Connecting to IPC {pipeName}", _client.PipeName);
await _client.ConnectAsync(cancellationToken);
}
Just stop IPC server
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Timers.Timer'.
at void System.Timers.Timer.set_Enabled(bool value)
at async Task H.Pipes.PipeClient.ConnectAsync(CancellationToken cancellationToken) in //src/libs/H.Pipes/PipeClient.cs:line 173
at async Task Ipc.Client.BaseIpcClient.Start(CancellationToken cancellationToken) in //src/Ipc/Client/BaseIpcClient.cs:line 141
at async void Ipc.Client.BaseIpcClient.ClientOnDisconnected(object , ConnectionEventArgs e) in //src/Ipc/Client/BaseIpcClient.cs:line 26
No response
No response
2.0.59
Console
Visual Studio 2022
No response
I seem to have some compiler problems such as
Severity Code Description Project File Line Suppression State
Error CS8417 'NamedPipeClientStream': type used in an asynchronous using statement must be implicitly convertible to 'System.IAsyncDisposable' or implement a suitable 'DisposeAsync' method. Did you mean 'using' rather than 'await using'? H.Pipes.Tests (net4.8) C:\Work\Temp\H.Pipes-master\H.Pipes-master\src\tests\H.Pipes.Tests\Tests.cs 89 Active
Compile solution
No response
No response
No response
No response
No response
No response
Hello,
First of all, this is a really nice project. I'm enjoying reading your code a lot, and this will definitely save me time so thanks. :)
While familiarizing myself with H.Pipes, I came across this bit in PipeStreamReader.cs
private async Task<byte[]> ReadAsync(int length, CancellationToken cancellationToken = default)
{
var buffer = new byte[length];
#if NETSTANDARD2_1 || NETCOREAPP3_1_OR_GREATER
await BaseStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
#elif NET461_OR_GREATER || NETSTANDARD2_0
await BaseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
#else
#error Target Framework is not supported
#endif
return buffer;
}
Shouldn't the return value (= number of bytes read) of ReadAsync be assigned and compared with the intended message length?
var readLength = await BaseStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
if (readLength != length)
throw new IOException($"Expected {length} bytes but read {readLength}");
I have noticed that all the read/write APIs can only read and write the generic type defined by the user when instantiating the client and server, eg. PipeServer<MyMessage>
, PipeClient<MyMessage>
.
Would you agree to a PR with the following changes:
PipeClient
, PipeServer
, SingleConnectionPipeServer
, SingleConnectionPipeClient
would have a generic and non-generic variantvoid WriteAsync<T>(T value, [...])
void WriteAsync(byte[] value, [...])
event EventHandler<ConnectionMessageEventArgs<byte[]>>? MessageReceived;
PipeXXX
SingleConnectionPipeXXX
and PipeXXX
appear to share a lot of code. They could inherit an abstract class for all the code they have in commonSingleConnectionPipeXXX
and PipeXXX
by adding a bool isSingleConnection
argument to the constructorIn essence we would have something like that:
public abstract BasePipeServer : IPipeServer
{
// Put all code shared by PipeServer and SingleConnectionPipeServer here
}
public abstract BasePipeClient : IPipeClient
{
// Put all code shared by PipeClient and SingleConnectionPipeClient here
}
We delete the SingleConnectionPipeXXX classes and add an argument in the constructor:
public PipeServer(string pipeName, bool isSingleConnection = false, IFormatter? formatter = default)
public PipeClient(string pipeName, string serverName = ".", bool isSingleConnection = false, TimeSpan? reconnectionInterval = default, IFormatter? formatter = default)
public class PipeServer: BasePipeServer
{
public event EventHandler<ConnectionMessageEventArgs<byte[]>>? MessageReceived;
public async Task WriteAsync<T>(T value, CancellationToken cancellationToken = default)
{ /* Code here */ }
public async Task WriteAsync(byte[] value, CancellationToken cancellationToken = default)
{ /* Code here */ }
}
public class PipeClient: BasePipeClient
{
public event EventHandler<ConnectionMessageEventArgs<byte[]>>? MessageReceived;
public async Task WriteAsync<T>(T value, CancellationToken cancellationToken = default)
{ /* Code here */ }
public async Task WriteAsync(byte[] value, CancellationToken cancellationToken = default)
{ /* Code here */ }
}
Repeat for SingleConnectionPipeXXX.
public class PipeServer<T>: PipeServer, IPipeServer<T>
{
public new event EventHandler<ConnectionMessageEventArgs<T?>>? MessageReceived;
public new async Task WriteAsync(T value, CancellationToken cancellationToken = default)
{ /* Code here */ }
}
public class PipeClient<T>: PipeClient, IPipeClient<T>
{
public new event EventHandler<ConnectionMessageEventArgs<T?>>? MessageReceived;
public new async Task WriteAsync(T value, CancellationToken cancellationToken = default)
{ /* Code here */ }
}
Repeat for SingleConnectionPipeXXX.
Optionally, it might be nice to make the PipeServer<T>
and PipeClient<T>
non-sealed for people who want to extend their functionalities without requiring a fork or a PR.
PipeServer
and PipeClient
also appear to share a lot of code. I could make a base class for them to avoid code duplication.
I'll be writing this for my own needs, but I would enjoy contributing to the project if possible with a PR. Let me know what you think! :)
A wrapper for other formatter that encrypts/decrypts data
At the moment, my knowledge is enough only to implement the simplest XOR encryption, which makes little sense
When establishing a connection it would be nice if one could add client metadata to the connection like a Connection Identifier (CID).
At the moment it's hard to identify a client connection on the server side. At the moment we make this part of the message but if the server gets a client message and needs to "send updates to a specific other" connected client then having a CID would be good.
await using var client = new PipeClient(pipeName) {
ClientMeta = new ClientMeta() { MachineName=Enviroment.MachineName, Role="MyRole", ...}
};
One could add the CID in the server connection handshake, the service already maintains a state for connected clients
When implementing something like this, you could do:
await server.ConnectedClients.Where(w=>s.MetaData.Role=="SomeRole").SendMsgAsync(..)
In PipeStreamWriter WriteAsync method, the semaphore is still released in the finally statement even it has not been entered if the cancellation token is cancelled before entering it. This leads to a SemaphoreFullException.
Hi, first of all let me say thanks for you great project!
I've seen in #42 that you switched away from BinaryFormatter
to SystemTextJsonFormatter
.
Nevertheless, in my .NET 8
app, I'm still getting errors (as in #42) with this sample code, so I have to specify SystemTextJsonFormatter
explicitly to get rid of errors:
// in server app
server = new PipeServer<PipeMessage>(pipeName, formatter: new SystemTextJsonFormatter());
// in client app
client = new PipeClient<PipeMessage>(pipeName, formatter: new SystemTextJsonFormatter());
BinaryFormatter
is still a default formatter, and one should specify SystemTextJsonFormatter
explicitly? Or am I doing something wrong?BinaryFormatter
is not used anywhere in the compiled app? (so this does not apply any more...)We are sporadically seeing the following exception when connecting > 1 pipeclient with pipeClient.ConnectAsync()
System.InvalidOperationException: Connection failed: Returned by server pipeName is null at H.Pipes.PipeClient
1.GetConnectionPipeName(CancellationToken cancellationToken)
at H.Pipes.PipeClient1.GetConnectionPipeName(CancellationToken cancellationToken) at H.Pipes.PipeClient
1.ConnectAsync(CancellationToken cancellationToken)`
Clients are connected in a relatively short time, the problem is already visible with 3 clients, but does not happen all the time.
Create pipe server
Create multiple pipe clients
All pipe clients connect normally and without ab exception
No response
Console
Other
No response
I have several classes defined as a Union, using the MessagePack.Union attribute.
See https://github.com/MessagePack-CSharp/MessagePack-CSharp?tab=readme-ov-file#union
I had no problem with .NET 6, but with .NET 8 I find that the Union classes are not serialized correctly,
So far as I can tell, it is necessary to provide a type parameter to MessagePackSerializer.Serialize()
, specifying the base class used for the union.
I have defined my own formatter class
public class MessagePackFormatterT<T1> : FormatterBase
where T1 : class
{
protected override byte[] SerializeInternal( object obj )
{
return MessagePackSerializer.Serialize<T1> ( obj as T1 );
}
protected override T DeserializeInternal<T>( byte[] bytes )
{
return MessagePackSerializer.Deserialize<T> ( bytes );
}
}
which I specify with both PipeClient
and the PipeSever
_client = new PipeClient<MyBase> ( PipeName, formatter: new MessagePackFormatterT<MyBase>() ) ;
_server = new PipeServer<MyBase> ( PipeName, new MessagePackFormatterT<MyBase>() );
In my application, this seems to have fixed the problem, but I'm not sure that it is a general solution.
Define a set of classes using MessageBase.Union, following the example in
https://github.com/MessagePack-CSharp/MessagePack-CSharp?tab=readme-ov-file#union
I defined the base class as an abstract class
, not an interface
.
Create a PipeServer an a PipeClient based on the union class.
Create an instance of one of the derived classes.
Send it via the PipeServer.
On receiving the message, the PipeClient will generate an error.
No response
No response
No response
Console
Visual Studio 2022
.NET 8
Maybe it would be helpful for some async scenarios. For example, control from PipeClient side time delay for server side job.
It would be very nice to be able to manually create the pipe stream during client initialization.
Can you please support this?
I'm having trouble moving a simple message (my first step with this library).
This is a minimal new .NET Framework 4.7.2 project (Desktop Application) with nothing only Load event for form.
I only added libraries: H.Pipes and H.Formatters.System.Text.Json
This is the entire form code.
using H.Formatters;
using H.Pipes;
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Test1
{
public partial class Frm_Test : Form
{
private PipeServer<MyMessage> server;
private PipeClient<MyMessage> client;
private string pipeName = "a";
private string test = "THIS IS TEST MESSAGE";
public Frm_Test() { InitializeComponent(); }
private void Frm_Test_Load(object sender, EventArgs e)
{
server = new PipeServer<MyMessage>(pipeName, formatter: new SystemTextJsonFormatter());
server.MessageReceived += (c, args) =>
{
Console.WriteLine("Received: " + args.Message.Text);
if (args.Message.Text != test) { Console.WriteLine("WRONG MESSAGE"); }
};
Task.Run(async () => { await server.StartAsync(); });
client = new PipeClient<MyMessage>(pipeName, formatter: new SystemTextJsonFormatter());
client.Connected += (o, args) =>
{
Console.WriteLine("Sending: " + test);
client.WriteAsync(new MyMessage { Text = test });
};
client.ConnectAsync();
}
}
public class MyMessage { public string Text; }
}
Console output is like this (message.Text is always empty):
Sending: THIS IS TEST MESSAGE
Received:
WRONG MESSAGE
I get the same problem even with two separated client-server applications.
Please advise me where I am making a mistake?
Hi,
In System.IO.Pipes there is a function "GetImpersonationUserName()" for getting the username of the user connecting.
Have you looked into adding similar capabilities?
This would need to be handled server side to avoid the possibility of a user impersonating another user.
While I am H Pipes in specflow, c# and nunit Framework for testing the pipe connection and passing data using pipes.
The WriteAsync function is called but it's not sending the data to the client application.
private PipeServer? _server;
[When(@"Get Sending Data")]
public async Task WhenSendingData()
{
do
{
Task.Run(() => StartServer());
} while (Console.ReadKey(true).Key != ConsoleKey.Escape);
}
public async Task StartServer()
{
while (!_token.IsCancellationRequested && _server is not { IsStarted: true })
{
try
{
_server = new PipeServer<string>("TestPipe");
_server.ClientConnected += OnClientConnected;
await _server.StartAsync(_token.Token);
}
catch (Exception ex)
{
Console.WriteLine("Failed to start server");
}
}
}
public void OnClientConnected(object? sender, ConnectionEventArgs e)
{
Console.WriteLine($"Client has connected to {e.Connection.PipeName}");
Console.WriteLine($"Client has connected to {e.Connection}");
e.Connection.WriteAsync("Welcome!");
}
There is no bug but it's not communicating. If i close the c++ execution, it's giving me pipe closed error. The id for Pipe is also created
Test.SignalPipe_ba006207-cdaf-4365-99b8-a06258d3c65e
Please not the same is working well in console application where I could send message but not working in specflow testing application.
No response
No response
<PackageReference Include="H.Formatters.BinaryFormatter" Version="2.0.59" />
<PackageReference Include="H.Pipes" Version="2.0.59" />
<PackageReference Include="H.Pipes.AccessControl" Version="2.0.59" />
Console
Visual Studio 2022
No response
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.