目录
写在前面
代码实现
服务端代码
客户端代码
调用示例
写在前面
使用 NamedPipeServerStream 和 NamedPipeClientStream 类,实现命名管道方式的网络通讯,支持跨网络和多个服务器实例的全双工通信、基于消息的通信以及客户端模拟;需要特别说明的是TokenImpersonationLevel 的四个枚举项,对应了 SecurityAnonymous、SecurityIdentification、SecurityImpersonation和SecurityDelegation,分别代表如下四种模拟的等级。
- 匿名(Anonymous):无法获取有关客户端的标识信息,且无法模拟客户端;
- 识别(Identification):可以获取有关客户端的信息(如安全标识符和特权),但是无法模拟客户端;
- 模拟(Impersonation):可以在本地模拟客户端的安全上下文。,但无法在远程系统上模拟客户端;
- 委托(Delegation):可以在本地和远程系统上模拟客户端的安全上下文。
不同的模拟等级具有不同的权限,当权限足够时服务端才能响应对应级别的操作请求。
代码实现
服务端代码
using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;public class PipeServer
{private static int numThreads = 4;private static string token = "天王盖地虎,今晚打老虎";public static void Main(){int i;Thread?[] servers = new Thread[numThreads];Console.WriteLine("\n*** Named pipe server stream with impersonation example ***\n");Console.WriteLine("Waiting for client connect...\n");for (i = 0; i < numThreads; i++){servers[i] = new Thread(ServerThread);servers[i]?.Start();}Thread.Sleep(250);while (i > 0){for (int j = 0; j < numThreads; j++){if (servers[j] != null){if (servers[j]!.Join(250)){Console.WriteLine("Server thread[{0}] finished.", servers[j]!.ManagedThreadId);servers[j] = null;i--; // decrement the thread watch count}}}}Console.WriteLine("\nServer threads exhausted, exiting.");}private static void ServerThread(object? data){var pipeServer = new NamedPipeServerStream("test.named.pipe", PipeDirection.InOut, numThreads);var threadId = Thread.CurrentThread.ManagedThreadId;// Wait for a client to connectpipeServer.WaitForConnection();Console.WriteLine("Client connected on thread[{0}].", threadId);try{// Read the request from the client. Once the client has// written to the pipe its security token will be available.StreamString ss = new StreamString(pipeServer);// Verify our identity to the connected client using a// string that the client anticipates.ss.WriteString(token); //对暗号string filename = ss.ReadString();// Read in the contents of the file while impersonating the client.ReadFileToStream fileReader = new ReadFileToStream(ss, filename);// Display the name of the user we are impersonating.Console.WriteLine("Reading file: {0} on thread[{1}] as user: {2}.",filename, threadId, pipeServer.GetImpersonationUserName());pipeServer.RunAsClient(fileReader.Start);}// Catch the IOException that is raised if the pipe is broken// or disconnected.catch (IOException e){Console.WriteLine("ERROR: {0}", e.Message);}pipeServer.Close();}
}// Defines the data protocol for reading and writing strings on our stream
public class StreamString
{private Stream ioStream;private UnicodeEncoding streamEncoding;public StreamString(Stream ioStream){this.ioStream = ioStream;streamEncoding = new UnicodeEncoding();}public string ReadString(){int len = ioStream.ReadByte() * 256;len += ioStream.ReadByte();byte[] inBuffer = new byte[len];ioStream.Read(inBuffer, 0, len);return streamEncoding.GetString(inBuffer);}public int WriteString(string outString){byte[] outBuffer = streamEncoding.GetBytes(outString);int len = outBuffer.Length;if (len > UInt16.MaxValue){len = (int)UInt16.MaxValue;}ioStream.WriteByte((byte)(len / 256));ioStream.WriteByte((byte)(len & 255));ioStream.Write(outBuffer, 0, len);ioStream.Flush();// 加上了包头,就是上面写入的定义内容长度的两个字节,所以长度加2return outBuffer.Length + 2;}
}// Contains the method executed in the context of the impersonated user
public class ReadFileToStream
{private string fn;private StreamString ss;public ReadFileToStream(StreamString str, string filename){fn = filename;ss = str;}public void Start(){string contents = File.ReadAllText(fn);ss.WriteString(contents);}
}
客户端代码
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Security.Principal;
using System.Text;
using System.Threading;public class PipeClient
{private static int numClients = 4;private static string token = "天王盖地虎,今晚打老虎";public static void Main(string[] args){if (args.Length > 0){if (args[0] == "spawnclient"){var pipeClient = new NamedPipeClientStream("127.0.0.1", "test.named.pipe",PipeDirection.InOut, PipeOptions.None,TokenImpersonationLevel.Impersonation);Console.WriteLine("Connecting to server...\n");pipeClient.Connect();var ss = new StreamString(pipeClient);// Validate the server's signature string.if (ss.ReadString() == token) //对暗号{// The client security token is sent with the first write.// Send the name of the file whose contents are returned// by the server.ss.WriteString("E:\\projects\\test_response.txt");// Print the file to the screen.Console.Write(ss.ReadString());}else{Console.WriteLine("Server could not be verified.");}pipeClient.Close();// Give the client process some time to display results before exiting.Thread.Sleep(4000);Console.ReadLine();}}else{Console.WriteLine("\n*** Named pipe client stream with impersonation example ***\n");StartClients();}}// Helper function to create pipe client processesprivate static void StartClients(){string currentProcessName = Environment.CommandLine;// Remove extra characters when launched from Visual StudiocurrentProcessName = currentProcessName.Trim('"', ' ');currentProcessName = Path.ChangeExtension(currentProcessName, ".exe");Process?[] plist = new Process?[numClients];Console.WriteLine("Spawning client processes...\n");if (currentProcessName.Contains(Environment.CurrentDirectory)){currentProcessName = currentProcessName.Replace(Environment.CurrentDirectory, String.Empty);}// Remove extra characters when launched from Visual StudiocurrentProcessName = currentProcessName.Replace("\\", String.Empty);currentProcessName = currentProcessName.Replace("\"", String.Empty);int i;for (i = 0; i < numClients; i++){// Start 'this' program but spawn a named pipe client.plist[i] = Process.Start(currentProcessName, "spawnclient");}while (i > 0){for (int j = 0; j < numClients; j++){if (plist[j] != null){if (plist[j]!.HasExited){Console.WriteLine($"Client process[{plist[j]?.Id}] has exited.");plist[j] = null;i--; // decrement the process watch count}else{Thread.Sleep(250);}}}}Console.WriteLine("\nClient processes finished, exiting.");}
}// Defines the data protocol for reading and writing strings on our stream.
public class StreamString
{private Stream ioStream;private UnicodeEncoding streamEncoding;public StreamString(Stream ioStream){this.ioStream = ioStream;streamEncoding = new UnicodeEncoding();}public string ReadString(){int len = ioStream.ReadByte() * 256;len += ioStream.ReadByte();var inBuffer = new byte[len];ioStream.Read(inBuffer, 0, len);return streamEncoding.GetString(inBuffer);}public int WriteString(string outString){byte[] outBuffer = streamEncoding.GetBytes(outString);int len = outBuffer.Length;if (len > UInt16.MaxValue){len = (int)UInt16.MaxValue;}ioStream.WriteByte((byte)(len / 256));ioStream.WriteByte((byte)(len & 255));ioStream.Write(outBuffer, 0, len);ioStream.Flush();return outBuffer.Length + 2; // 加上了包头,就是上面写入的定义内容长度的两个字节,所以长度加2}
}
调用示例
服务端控制台输出:
客户端控制台输出: