【问题标题】:Check if WCF(namedpipes) host is available?检查 WCF(namedpipes) 主机是否可用?
【发布时间】:2011-09-26 08:29:58
【问题描述】:

嗨,

我们有一个只作为单例执行的 winform 应用程序,如果第二个实例尝试启动这个新实例,它将连接到当前实例并通过命名管道传输参数。

问题在于,在启动第一个实例时,将尝试连接到现有主机。如果主机不存在(如本例中),则会抛出异常。处理这个异常没有问题,但是我们的开发人员经常使用“Break on Exception”,这意味着每次我们启动应用程序时,开发人员都会得到两个(在这种情况下)关于异常的中断。 Thay 每次开始都必须按两次 F5。

有什么方法可以检查服务是否可用,如果不可用则抛出异常?

致以最诚挚的问候

编辑1:

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr OpenFileMapping(uint dwDesiredAccess, bool bInheritHandle, string lpName);

以下代码显示:错误 152 无法将类型“System.IntPtr”隐式转换为“Orbit.Client.Main.Classes.Controllers.MyClientController.SafeFileMappingHandle”

using (SafeFileMappingHandle fileMappingHandle
                = OpenFileMapping(FILE_MAP_READ, false, sharedMemoryName))
            {

【问题讨论】:

    标签: wcf exception-handling named-pipes


    【解决方案1】:

    如果已经有 WCF 服务器在命名管道端点上侦听,则会创建一个共享内存对象,服务器通过该对象发布管道的实际名称。 See here for details of this.

    您可以使用类似以下的代码检查此共享内存对象的存在,如果没有服务器正在运行,则不会抛出,只是返回 false。 (我已经从我已经工作的代码中提取了它,然后对其进行了编辑以执行您想要的操作 - 但没有测试编辑后的版本,因此如果您必须修复程序集/命名空间引用等以使其运行,请道歉。)

    public static class ServiceInstanceChecker
    {
    
        public static bool DoesAServerExistAlready(string hostName, string path)
        {
            return IsNetNamedPipeSharedMemoryMetaDataPublished(DeriveSharedMemoryName(hostName, path));
        }
    
    
        private static string DeriveSharedMemoryName(string hostName, string path)
        {
            StringBuilder builder = new StringBuilder();
            builder.Append(Uri.UriSchemeNetPipe);
            builder.Append("://");
            builder.Append(hostName.ToUpperInvariant());
            builder.Append(path);
            byte[] uriBytes = Encoding.UTF8.GetBytes(builder.ToString());
    
            string encodedNameRoot;
            if (uriBytes.Length >= 0x80)
            {
                using (HashAlgorithm algorithm = new SHA1Managed())
                {
                    encodedNameRoot = ":H" + Convert.ToBase64String(algorithm.ComputeHash(uriBytes));
                }
            }
            else
            {
                encodedNameRoot = ":E" + Convert.ToBase64String(uriBytes);
            }
            return Uri.UriSchemeNetPipe + encodedNameRoot;
        }
    
        private static bool IsNetNamePipeSharedMemoryMetaDataPublished(string sharedMemoryName)
        {
            const uint FILE_MAP_READ = 0x00000004;
            const int ERROR_FILE_NOT_FOUND = 2;
            using (SafeFileMappingHandle fileMappingHandle 
                = OpenFileMapping(FILE_MAP_READ, false, sharedMemoryName))
            {
                if (fileMappingHandle.IsInvalid)
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    if (ERROR_FILE_NOT_FOUND == errorCode) return false; 
                    throw new Win32Exception(errorCode); // The name matched, but something went wrong opening it
                }
                return true;
            }
        }
    
        private class SafeFileMappingHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public SafeFileMappingHandle() : base(true) { }
            public SafeFileMappingHandle(IntPtr handle) : base(true) { base.SetHandle(handle); }
    
            protected override bool ReleaseHandle()
            {
                return CloseHandle(base.handle);
            }
        }
    
    }
    

    您传入的主机名和路径是从 WCF 服务 url 派生的。主机名是特定主机名(例如localhost)或+,或*,具体取决于HostNameComparisonMode 的设置。

    编辑:您还需要一些用于 Win API 函数的 P/Invoke 声明:

    [DllImport("kernel32.dll", SetLastError = true)] 
    static extern bool CloseHandle(IntPtr hObject); 
    
    [DllImport("kernel32.dll", SetLastError = true)] 
    static extern SafeFileMappingHandle OpenFileMapping(
      uint dwDesiredAccess,
      bool inheritHandle,
      string name
    );
    

    EDIT2:我们需要调整 DeriveSharedMemoryName 的返回值以指定本地内核命名空间,假设您的应用程序没有以提升的权限运行。将此函数的最后一行更改为:

    return @"Local\" + Uri.UriSchemeNetPipe + encodedNameRoot;
    

    您还需要正确指定主机名参数以匹配绑定中使用的 hostNameComparisonMode 设置。据我记得,这默认为 NetNamedPipeBinding 中的 StrongWildcard 匹配,因此您可能需要传入 "+" 而不是 "localhost"

    【讨论】:

    • > 谢谢,但是在我的代码中实现这个时我遇到了一些问题。 FileRights 未知,SafeFileMappingHandle.ReleaseHandle > CloseHandle 未知,IsNetNamedPipeSharedMemoryMetaDataPublished 未知?
    • 您需要将 CloseHandle 和 OpenFileMapping 的 P/Invoke 声明添加到同名的 Win API 函数中。
    • 查看我的编辑...从 .NET Framework 实现中学到的句法技巧
    • 谢谢!但这不起作用。 DoesAServerExistAlready 始终返回 false。参数如下所示:DoesAServerExistAlready("localhost", "/MyService") 通道如下所示: using (ChannelFactory factory = new ChannelFactory(new NetNamedPipeBinding(), new EndpointAddress("net.pipe) ://localhost/MyService")))
    • 我确实警告过你我没有测试过这个 :-) 查看我的进一步编辑以获得快速修复。如果您的应用程序有可能以提升的权限运行以便能够创建全局内核对象,则您需要进一步更改,以便代码检查全局和本地命名空间。如果应用程序的不同实例可能具有不同的绑定配置,则可能需要检查 hostName 参数的所有变体,以防实例具有不同的 HostNameComparisonMode。
    【解决方案2】:

    您能否尝试列出可用的命名管道

    String[] listOfPipes = System.IO.Directory.GetFiles(@"\.\pipe\");

    然后确定您的命名管道是否在其中?

    【讨论】:

    • 这没有成功的希望,因为他的代码将不知道要查找什么管道名称。每次打开服务时,管道名称都会更改。
    【解决方案3】:

    我的解决方案如下:

    if (Debugger.IsAttached) 
    return true;
    

    这将确保检查服务的代码在调试期间永远不会运行。

    致以最诚挚的问候

    【讨论】:

      猜你喜欢
      • 2010-11-07
      • 2012-09-09
      • 1970-01-01
      • 2022-01-09
      • 1970-01-01
      • 1970-01-01
      • 2015-08-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多