I am attempting to set up IPC using named pipes. I need to transfer data from one server/manager-style Windows Service to a variety of clients. The request-response procedure is pretty simple, so I have a single model for each of those. I'm planning to have the server/manager host a number of named pipe servers. When a client needs to communicate, it will create a pipe client, connect, serialize a Request model as json and send it across the pipe. The server/manager will serialize a Response model and send it back, then disconnect and start waiting for a new connection.
The problem that I'm running into is that the System.Text.Json.JsonSerializer
doesn't return when trying to deserialize data in PipeStream (NamedPipeServerStream
or NamedPipeClientStream
). I'm not sure what I'm doing wrong, as I can convert the model to json and send it across using a buffer just fine, but I'd rather serialize directly to the stream.
Here's a simplified version of the code.
The client:
using System;
using System.IO.Pipes;
using System.Text.Json;
using System.Threading.Tasks;
namespace NamedPipeDemo.Client
{
public class Program
{
public static async Task Main(string[] args)
{
while (true)
{
await using var client = new NamedPipeClientStream("PSSharp/NamedPipeDemo");
await client.ConnectAsync();
Console.Write("Enter message body for server: ");
var message = Console.ReadLine();
var request = new Request()
{
Content = message
};
Console.WriteLine("{0}: Sending data.", DateTime.Now);
await JsonSerializer.SerializeAsync(client, request);
await client.FlushAsync();
Console.WriteLine("{0}: Data written to pipe.", DateTime.Now);
var response = await JsonSerializer.DeserializeAsync<Response>(client);
Console.WriteLine("{0}: Response received from server.\n{1}", DateTime.Now, JsonSerializer.Serialize(response));
if (response is null)
{
break;
}
}
}
}
}
The server
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO.Pipes;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace NamedPipeDemo.Server
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await using var server = new NamedPipeServerStream("PSSharp/NamedPipeDemo", PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.WriteThrough);
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("{time} : Waiting for client to connect.", DateTime.Now);
await server.WaitForConnectionAsync(stoppingToken);
_logger.LogInformation("{time} : Client with identity (unknown) connected.", DateTime.Now);
// this call never returns
var request = await JsonSerializer.DeserializeAsync<Request>(server, null, stoppingToken);
var response = await GetResponseAsync(request, stoppingToken);
await JsonSerializer.SerializeAsync(server, response, null, stoppingToken);
_logger.LogInformation("{time} : Communication with client terminated. Waiting for new client.", DateTime.Now);
server.Disconnect();
}
}
protected virtual async ValueTask<Response?> GetResponseAsync(Request? request, CancellationToken cancellation)
{
if (request?.Content?.Contains("wait") ?? false)
{
_logger.LogInformation("Stimulating work.");
await Task.Delay(1000, cancellation);
}
if (request?.Content?.Contains("exit") ?? false)
{
return null;
}
var shouldEcho = request?.Content?.Contains("echo") ?? false;
var echoContent = shouldEcho ? request?.Content : "The message was processed.";
var error = default(string);
if (echoContent is null)
{
echoContent = "No content to echo.";
error = "No content to echo.";
}
var response = new Response(echoContent, error);
return response;
}
}
}
Models
using System;
using System.Text.Json.Serialization;
namespace NamedPipeDemo
{
public class Request
{
private static int s_id;
public int Id { get; private set; } = ++s_id;
public string? Content { get; set; }
}
public class Response
{
private static int s_id;
[JsonConstructor]
private Response(int id,string? error, string message)
{
Id = id;
Error = error;
Message = message;
}
public Response(string message, string? error = null)
{
Message = message;
Error = error;
}
public int Id { get; } = ++s_id;
public string? Error { get; }
public string Message { get; }
}
}