如何使用内存映射文件在同一台计算机上的服务器和客户端之间进行通信?

How do I communicate between a server and a client on the same computer using memory mapped files?

我真的希望有人能帮助我理解内存映射文件的工作原理。我一直在网上做很多研究,但有时寻求帮助更容易。

我正在 运行 的服务器上创建文档创建系统。我想创建一个服务,它将按需向客户端程序提供模板文件(它本身就是对请求文件的实际客户端的服务)。

我希望模板文件保留在内存中,这样每次用户请求文件时服务器就不必访问硬盘。

我无法使用管道连接,因为相关文件太大。我可以使用套接字式连接,或将文件分解成更小的部分,但这似乎是一个不如内存映射文件好的解决方案,内存映射文件似乎旨在解决这个确切的问题,而且似乎是最快的方法在两个进程之间共享大文件。

在我将文件名更改为@"Global\" + 文件名之前,我无法让内存映射文件工作(参见代码)。这工作了一段时间,但随后停止工作 - 但我不知道我改变了什么。我明白了 here。我认为无论 "Global\" 前缀做什么都与我的问题的解决方案有关,因为这似乎表明 Windows 内存以与文件系统格式化类似的方式分段,并且不同的进程可能有不同的访问级别。

这是来自服务的代码:

public partial class TemplateDistributorService : ServiceBase
{
    protected ServiceHost host;
    public static Dictionary<string, System.IO.MemoryMappedFiles.MemoryMappedFile> memFiles;
    public static Dictionary<string, int> fileSizes;

    public TemplateDistributorService()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        memFiles = new Dictionary<string, System.IO.MemoryMappedFiles.MemoryMappedFile>();
        fileSizes = new Dictionary<string, int>();
        foreach (var path in args)
        {
            string fileName = @"Global\" + System.IO.Path.GetFileNameWithoutExtension(path);
            var sz = new System.IO.FileInfo(path).Length;
            //all files must be less than 2GB for easier programming.  I don't anticipate
            //this being a problem, and if it does become one, we can up this to the max system file size (4 GB)
            if (sz <= System.Int32.MaxValue)
            {
                using (System.IO.MemoryMappedFiles.MemoryMappedFile mmf =
                    System.IO.MemoryMappedFiles.MemoryMappedFile.CreateNew(fileName, 
                    sz, System.IO.MemoryMappedFiles.MemoryMappedFileAccess.ReadWrite))
                {
                    fileSizes[fileName] = System.Convert.ToInt32(sz);
                    using (var stream = mmf.CreateViewStream())
                    {
                        using (var writer = new System.IO.BinaryWriter(stream))
                        {
                            byte[] fileBytes = System.IO.File.ReadAllBytes(path);
                            writer.Write(fileBytes);
                        }
                    }
                    memFiles[fileName] = mmf;
                }
            }
        }
        host = new ServiceHost(typeof(TemplateDistributor));
        host.AddServiceEndpoint(typeof(TemplateDistributorInterface),
          new NetNamedPipeBinding(),
          "net.pipe://localhost/PipedTemplateAccess");

        host.Open();
    }

    protected override void OnStop()
    {
        host.Close();
    }
}

[ServiceContract]
public interface TemplateDistributorInterface
{
    [OperationContract]
    int AddFile(string path, string fileName);

    [OperationContract]
    int FileSize(string fileName);
}

public class TemplateDistributor : TemplateDistributorInterface
{
    public int AddFile(string path, string fileName)
    {
        //prepending the filename with 'Global\' is really important.  Not sure why, but you can't access the memory mapped file without it
        //
        //string fileName = @"Global\" + System.IO.Path.GetFileNameWithoutExtension(path);
        if (TemplateDistributorService.memFiles.ContainsKey(fileName))
        {
            if (TemplateDistributorService.memFiles[fileName] != null)
            {
                return 0;
            }
            else
            {
                return -1;
            }
        }
        var sz = new System.IO.FileInfo(path).Length;
        if (sz > System.Int32.MaxValue)
        {
            return -1;
        }
        using (System.IO.MemoryMappedFiles.MemoryMappedFile mmf =
            System.IO.MemoryMappedFiles.MemoryMappedFile.CreateNew(fileName, sz, System.IO.MemoryMappedFiles.MemoryMappedFileAccess.ReadWrite))
        {
            TemplateDistributorService.fileSizes[fileName] = System.Convert.ToInt32(sz);
            using (var stream = mmf.CreateViewStream())
            {
                using (var writer = new System.IO.BinaryWriter(stream))
                {
                    byte[] fileBytes = System.IO.File.ReadAllBytes(path);
                    writer.Write(fileBytes);
                }
            }
            TemplateDistributorService.memFiles[fileName] = mmf;
        }
        return 1;
    }

    public int FileSize(string fileName)
    {
        if (TemplateDistributorService.fileSizes.ContainsKey(fileName))
        {
            return TemplateDistributorService.fileSizes[fileName];
        }
        else
        {
            return 0;
        }
    }
}

这是客户端代码(抛出:"An unhandled exception of type 'System.IO.FileNotFoundException' occurred in System.Core.dll" 当我尝试打开内存映射文件时):

[ServiceContract]
public interface TemplateDistributorInterface
{
    [OperationContract]
    int AddFile(string path, string fileName);

    [OperationContract]
    int FileSize(string fileName);
}

class Program
{
    static void Main(string[] args)
    {
        TestDocService();
    }

    static void TestDocService()
    {
        var path = @"C:\Users\jack.geiger\Documents\BuschFTP.docx";

        ChannelFactory<TemplateDistributorInterface> streamPipeFactory =
            new ChannelFactory<TemplateDistributorInterface>(
              new NetNamedPipeBinding(),
              new EndpointAddress(
                "net.pipe://localhost/PipedTemplateAccess"));
        TemplateDistributorInterface streamPipeProxy = streamPipeFactory.CreateChannel();
        string fileName = @"Global\Restricted\" + System.IO.Path.GetFileNameWithoutExtension(path);
        var f = streamPipeProxy.AddFile(path, fileName);
        f = streamPipeProxy.AddFile(path, fileName);

        //prepending the filename with 'Global\' is really important.  Not sure why, but you can't access the memory mapped file without it
        //
        //mmf is being created in service, but I can't find it here for some reason

        using (System.IO.MemoryMappedFiles.MemoryMappedFile mmf = 
            System.IO.MemoryMappedFiles.MemoryMappedFile.OpenExisting(
                fileName, 
                System.IO.MemoryMappedFiles.MemoryMappedFileRights.Read, 
                System.IO.HandleInheritability.None))
        {
            using (var stream = mmf.CreateViewStream(0, 0, System.IO.MemoryMappedFiles.MemoryMappedFileAccess.Read))
            {
                using (System.IO.BinaryReader binReader = new System.IO.BinaryReader(stream))
                {
                    var fileSize = streamPipeProxy.FileSize(fileName);
                    var bts = binReader.ReadBytes(streamPipeProxy.FileSize(fileName));
                    using (System.IO.MemoryStream memStr = new System.IO.MemoryStream())
                    {
                        memStr.Read(bts, 0, bts.Length);
                        memStr.Seek(0, System.IO.SeekOrigin.Begin);
                        using (WordprocessingDocument template = WordprocessingDocument.Create(memStr, WordprocessingDocumentType.Document))
                        {
                            template.MainDocumentPart.Document.RemoveAllChildren();
                        }
                    }
                }
            }
        }
    }

问题是我在“使用”子句中创建内存映射文件。这是删除内存映射文件。非常感谢您的帮助@RbMm!你帮我解决了这个问题,我认为如果没有你的帮助,我很难解决这个问题。这是我们的聊天记录:

https://chat.whosebug.com/rooms/132839/discussion-between-jack-geiger-and-rbmm