Search code examples
c#named-pipessystem.io.pipelines

C# Named Pipelines - Wrong encoding?


I'm working with Named Pipelines for the first time. My end goal to make one exe that can work as both client, server, and independent. The server can have elevated privileges.

I'm having difficulty with the connection back and forth. First is that every write back to the client except for the first one starts with a '?'. Internet says its an encoding issue but it's the same function (writeClient) that is doing it so I can't determine why the first time would be different. (also printout confirmed both the writer and reader say the same encoding each time) Second is that I wanted to make sure I was closing the connections appropriately. It took a bit of trial and error.

Readout from Client
In this version, I told it to print the encoding as well to confirm it didn't change.

Connected.
Version Installed: 1.2.8
?Latest Version: 1.2.85
?Update Needed
?Run again without '-Check' to perform a silent installation.
?Close
Connection closed.

Main Program

using System;
using System.Diagnostics;
using System.Xml.Linq;
using System.Runtime.Versioning;
using System.IO.Pipes;
using System.Text;
using System.Net;
using System.ServiceProcess;
using System.Security.Authentication.ExtendedProtection;

[SupportedOSPlatform("windows")]

class DAQUpdate
{
    static void Main(string[] args)
    {       
        if (args.Length > 0)
        {

                //Check if the same program is running as a service. If so, use that as it will have elevated previleges 
                try
                {
                    using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "DAQUpdater", PipeDirection.InOut))
                    {
                        //Connect to the service with a 1 second timeout. This will return an error on timeout
                        pipeClient.Connect(1);
                        //Double check if connected for reasons.
                        if(pipeClient.IsConnected)
                        {
                            Console.WriteLine("Connected.");

                            // Send the args to the server
                            using (StreamWriter writer = new StreamWriter(pipeClient,Encoding.UTF8,-1,true))
                            {
                                //Packages the args with ; to separate since I can't pass objects
                                writer.WriteLine(string.Join(';', args));
                                writer.Flush();
                            }

                            //Read all responses from the server as it gives them. The final response will be Close
                            string response = "";                               
                            using (StreamReader reader = new StreamReader(pipeClient, Encoding.UTF8))
                            {
                                while(!response.Contains("Close"))
                                {                                    
                                    response = reader.ReadLine();
                                    Console.WriteLine(response);
                                }
                                pipeClient.Close();
                            }
                            Console.WriteLine("Connection closed.");
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    Console.WriteLine("No Connection.");
                }            
        }
        else
        {
            NamedPipeServer.runServer();
        }
    }
    public static void update(string[] args)
    {
        NamedPipeServer.writeClient("hello");
        NamedPipeServer.writeClient(args[0]);
        NamedPipeServer.writeClient("Testing");
        NamedPipeServer.writeClient("Close");
    }
    
}

Server Functions

// NamedPipeServer.cs

using System.IO.Pipes;
using System.Text;

public class NamedPipeServer
{
    private static NamedPipeServerStream pipeServer;
    public static void runServer()
    {
        try
        {            
            while(true)
            {
                //Changed to global so other functions can call writeClient and send messages to the client from anywhere
                pipeServer = new NamedPipeServerStream("DAQUpdater", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);

                {
                    Console.WriteLine("Waiting for connection...");
                    pipeServer.WaitForConnection();
                    Console.WriteLine("Client connected.");

                    // Read and process the command from the client
                    using (StreamReader reader = new StreamReader(pipeServer, Encoding.UTF8,true,-1,true ))
                    {
                        string command = reader.ReadLine();
                        string[] commandargs = command.Split(';');
                        Console.WriteLine($"Received command: {commandargs[0]}");

                        //Run the check for updates program which will also write to the client
                        DAQUpdate.update(commandargs);

                        // Send a response back to the client if needed
                        using (StreamWriter writer = new StreamWriter(pipeServer, Encoding.UTF8, -1 , true))
                        {
                            writer.WriteLine("Close");
                            writer.Flush();
                        }
                    }
                    pipeServer.Close();
                    Console.WriteLine("Connection closed.");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
    public static void writeClient(string messageTxt)
    {
        //allows other functions to send update messages to the client
        if(pipeServer != null)
        {
            if(pipeServer.IsConnected)
            {
                using (StreamWriter writer = new StreamWriter(pipeServer, Encoding.UTF8, -1 , true))
                {
                    Console.WriteLine(writer.Encoding);
                    writer.WriteLine(messageTxt);
                    writer.Flush();
                }
            }
        }                
    }
}



Solution

  • You're creating a separate StreamWriter for each line, and that will start by emitting the preamble for the Encoding that you specify - Encoding.UTF8 includes a preamble of the byte-order mark.

    I suspect you're not seeing it for the first line because StreamReader may well be silently stripping it from the start of input. (I can't remember enough about the details of when it's emitted/stripped by the writer/reader, to be honest.)

    I suggest you create a simple static property somewhere of Utf8EncodingWithoutPreamble, initialized via new Utf8Encoding(false). If you use that each time you write, you won't get a preamble.

    Or you could create a single StreamWriter and keep that open, so it'll write just the one preamble which will be stripped.

    Or you could dispense with the StreamWriters entirely and just use:

    pipeServer.Write(Encoding.Utf8.GetBytes(messageTxt + "\n"));
    

    (Aside from anything else, I'd suggest using a well-defined line break instead of the default of "whatever the platform default line break is.)