I have a simple service which communicates over named pipes.
This is what the client looks like:
internal class ServicePipeManager : IDisposable
{
private NamedPipeClientStream pipe;
private DataContractSerializer serializer = new DataContractSerializer(typeof(ServiceMessage), ServiceMessage.GatherSerializationTypes());
private MemoryStream bufferStream = new MemoryStream();
public void Connect()
{
pipe = new NamedPipeClientStream(".", "MyService", PipeDirection.InOut);
pipe.Connect(100);
}
public ServiceResponse Send(ServiceRequest request)
{
var id = Guid.NewGuid();
request.MessageID = id;
SendRequest(request);
var length = pipe.ReadInt32();
bufferStream.SetLength(length);
bufferStream.Seek(0, SeekOrigin.Begin);
pipe.Read(bufferStream.GetBuffer(), 0, length);
var response = serializer.ReadObject(bufferStream);
if (response is ExceptionResponse ex)
ex.Exception.ToException().Rethrow();
if (response is not ServiceResponse result)
throw new Exception("Response type is not a ServiceResponse");
if (result.MessageID != id)
throw new Exception("Result messageID did not match request");
return result;
}
private void SendRequest(ServiceRequest request)
{
bufferStream.SetLength(0);
bufferStream.Seek(0, SeekOrigin.Begin);
serializer.WriteObject(bufferStream, request);
pipe.WriteInt32((int)bufferStream.Length);
pipe.Write(bufferStream.GetBuffer(), 0, (int)bufferStream.Length);
}
}
This is what the messages look like:
[DataContract]
public class ServiceMessage
{
[DataMember]
public Guid MessageID { get; set; }
public static List<Type> GatherSerializationTypes()
{
var result = new List<Type>();
foreach (var type in typeof(ServicePipeManager).Assembly.GetTypes())
{
if (type.GetCustomAttributes(true).OfType<DataContractAttribute>().Any())
result.Add(type);
}
return result;
}
}
[DataContract]
public abstract class ServiceRequest : ServiceMessage
{
public abstract ServiceResponse HandleRequest(RequestState state);
}
[DataContract]
public abstract class ServiceResponse : ServiceMessage
{
}
[DataContract]
public class ExceptionResponse : ServiceResponse
{
[DataMember]
public ExceptionSerializer Exception { get; set; }
}
This is what the service looks like:
public class Service
{
public void Run()
{
var thread = new Thread(ListenerThread);
thread.Start(null);
}
private void ListenerThread(object ignore)
{
try
{
while (true)
{
var stream = new NamedPipeServerStream("MyService", PipeDirection.InOut, 100, PipeTransmissionMode.Byte);
stream.WaitForConnection();
var thread = new Thread(ProcessThread);
thread.Start(stream);
}
}
catch
{
}
}
private void ProcessThread(object streamAsObject)
{
var stream = streamAsObject as NamedPipeServerStream;
if (stream == null)
return;
using (stream)
try
{
var serializer = new DataContractSerializer(typeof(ServiceMessage), ServiceMessage.GatherSerializationTypes());
var ms = new MemoryStream();
while (stream.IsConnected)
{
int length = stream.ReadInt32();
ms.Seek(0, SeekOrigin.Begin);
ms.SetLength(length);
stream.Read(ms.GetBuffer(), 0, length);
var message = (ServiceRequest)serializer.ReadObject(ms);
HandleMessage(serializer, stream, message, ms);
}
}
catch
{
}
}
public static void HandleMessage(
DataContractSerializer serializer, NamedPipeServerStream stream,
ServiceMessage message, MemoryStream buffer)
{
ServiceResponse response = null;
try
{
var req = (ServiceRequest)message;
var state = new RequestState(...);
response = req.HandleRequest(state);
}
catch (Exception ex)
{
response = new ExceptionResponse { Exception = ExceptionSerializer.FromException(ex) };
}
response.MessageID = message.MessageID;
buffer.Seek(0, SeekOrigin.Begin);
buffer.SetLength(0);
serializer.WriteObject(buffer, response);
stream.WriteInt32((int)buffer.Length);
stream.Write(buffer.GetBuffer(), 0, (int)buffer.Length);
}
}
Occasionally, the service ends up in a state where the client just gets back out of the pipe what it sent in, instead of getting a proper response. It throws the “Response type is not a ServiceResponse” exception because the data type it’s getting back is ServiceRequest, not a ServiceResponse. Given the simple nature of the code, I don’t see any way that it could be sending back the same data it received. Is there something about NamedPipeClientStream that behaves this way? Or am I missing an obvious bug in my code? ServiceRequest.HandleRequest has to return a ServiceResponse type, so I don’t see any way for Service.HandleMessage() to send back a ServiceRequest.