《CLR via C#》读书笔记-异步编程(一)

在学习《CLR via C#》的27.2小节中使用了管道。因此先对管道(pipe)的相关知识进行梳理
Pipe
其在System.IO.Pipes命名空间下,此命名空间内的类主要任务就是完成不同进程之间的通信。其包含了anonymous pipe(匿名管道)和Named pipe(命名管道)。以下内容主要讲述命名管道的相关知识
1、命名管道的本质是一个使用“共享内存”模式的Stream,不同的进程采用CLR规定的规则(即使用Pipe),才可以进行进程间的通信。
以下直接使用代码进行说明。

using System;
using System.IO;
using System.IO.Pipes;  //要引入命名空间
using System.Text;
using System.Threading;

public class PipeServer
{
    private static int numThreads = 4;

    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].IsBackground=true;
            //因为通过Thread创建的线程默认是前台线程,若前台线程会导致一个问题(特别是在UI界面上[例如winform]),造成winform应用程序不终止。因此在使用时要特别注意这点。

            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)
    {
        NamedPipeServerStream pipeServer =
            new NamedPipeServerStream("testpipe", PipeDirection.InOut, numThreads);

        int threadId = Thread.CurrentThread.ManagedThreadId;

        // Wait for a client to connect
        //此处,服务器上的线程会阻塞,因为要等待客户端的链接。直到有客户端发起链接请求,线程才能继续执行,否则线程阻塞
        pipeServer.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的具体定义参见其定义
            StreamString ss = new StreamString(pipeServer);

            // Verify our identity to the connected client using a
            // string that the client anticipates.

            ss.WriteString("I am the one true server!");
            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());

            //***RunAsClient的具体说明参见代码下方

            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;

    //本类在创建实例时,会将NamedPipeServerStream传入,因此在本类里面所有的ioStream变量,就是pipeServerStream
    public StreamString(Stream ioStream)
    {
        this.ioStream = ioStream;
        streamEncoding = new UnicodeEncoding();
    }

    public string ReadString()
    {
        int len = 0;

        //可以地方很好玩,之前一直在想为啥要这么写,是有内部的原因吗?
        //哈哈,看了WriteString就明白,这就是“原作者”写的玩的
        //“原作者”想在读取内容的前两位存储内容的长度。只要按照这个规矩,客户端在发送前会将内容的长度写在最前面两位,
        // 这样服务器端就知道需要创建多大的byte数组,这样就可以避免浪费空间。
        // 若自己在写demo时,因为知道自己的文件内容,因此可以直接在服务器端定义byte数组的长度
        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;
        }
        //这个地方就解释了ReadString方法中为什么会有先读取两字节的操作了
        ioStream.WriteByte((byte)(len / 256));
        ioStream.WriteByte((byte)(len & 255));
        ioStream.Write(outBuffer, 0, len);
        ioStream.Flush();

        return 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);
    }
}

RunAsClient方法第一次没明白什么意思。这个方法从字面意思是指:服务器“变身”客户端。通过网上的查询,自己对这个方法的理解如下:当服务器实例(NamedPipeServerStream)调用本方法时,会获取连接到本服务器上的客户端的Token,使得客户端的权限应用到服务器上。换句话说,本方法并不是说服务器端使用某种机制“变身”成客户端,而是客户端的权限授予服务器端,便于服务器对客户端上的文件进行操作。
说明1:以下的场景最为常用。服务器与客户端建立连接后,服务器端需要读取客户端上的文件内容。但此时服务器端并没有读取客户端文件的权限,需要客户端授权。因此,当服务器端调用RunAsClient后,客户端的相关权限就授予了服务器端,服务器就可以对文件进行操作了。从这个层面上讲是相当于客户端(方法中使用了“s)。
说明2:若在同一台电脑上创建了服务器端和客户端,则不使用RunAsClient也不会报错。但若使用远程连接,若不使用RunAsClient而直接使用读取数据的方法则会出现问题。
因此上面的例子中有如下的代码:

//若直接使用如下代码,在本机不会报错,但在远程桌面连接时,会出现异常
// fileReader.Start();  
pipeServer.RunAsClient(fileReader.Start);

fileReader.Start的定义如下:

    public void Start()
    {
        string contents = File.ReadAllText(fn);
        ss.WriteString(contents);
    }

读取客户端的文件内容。因此就需要使用RunAsClient方法,若不使用本方法,则就会报异常。
客户端的代码如下:

using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Security.Principal;
using System.Diagnostics;
using System.Threading;

public class PipeClient
{
    private static int numClients = 4;

    public static void Main(string[] Args)
    {
        if (Args.Length > 0)
        {
            if (Args[0] == "spawnclient")
            {
                NamedPipeClientStream pipeClient =
                    new NamedPipeClientStream(".", "testpipe",
                        PipeDirection.InOut, PipeOptions.None,
                        TokenImpersonationLevel.Impersonation);

                Console.WriteLine("Connecting to server...\n");
                pipeClient.Connect();

                StreamString ss = new StreamString(pipeClient);
                // Validate the server's signature string
                if (ss.ReadString() == "I am the one true server!")
                {
                    // 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("c:\\textfile.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);
            }
        }
        else
        {
            Console.WriteLine("\n*** Named pipe client stream with impersonation example ***\n");
            StartClients();
        }
    }

    // Helper function to create pipe client processes
    private static void StartClients()
    {
        int i;
        string currentProcessName = Environment.CommandLine;
        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 Studio
        currentProcessName = currentProcessName.Replace("\\", String.Empty);
        currentProcessName = currentProcessName.Replace("\"", String.Empty);

        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[{0}] has exited.",
                            plist[j].Id);
                        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;
        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();

        return outBuffer.Length + 2;
    }
}

以上代码全部复制于MSDN,具体网址如下:MSDN上关于Pipe的使用
小节
在测试命名管道相关的程序中存在这样一个问题。若远程server端设置了用户名/密码,则客户端会报异常,无法连接到服务器端。另外,需要验证一个问题,要连接到server端是否需要将防火墙关闭!此问题需要验证!

参考文献:
1、http://www.cnblogs.com/langu/archive/2013/02/22/2922542.html
2、http://blog.csdn.net/jcx5083761/article/details/7955489
3、MSDN:https://msdn.microsoft.com/zh-cn/library/bb546085.aspx
4、StackOverflow:http://stackoverflow.com/questions/23832090/how-do-you-loaduserpofile-from-within-namepipeserverstream-runasclient
5、CodeProject:http://www.codeproject.com/Articles/125810/A-complete-Impersonation-Demo-in-Csharp-N

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值