如何确定IIS Express实例正在使用哪个端口?

7
我想以编程方式结束一个占用特定端口的运行中的IIS实例,但似乎无法确定哪个IIS实例正在使用特定端口。

netstat.exe只显示该进程具有PID 4,但那是系统进程。"netsh http show urlacl"根本不显示已占用的端口。

IIS Express托盘程序以某种方式了解到这一点。 当我尝试在端口被占用时启动另一个IIS Express实例时,我会收到以下错误:
"Port '40000' is already being used by process 'IIS Express' (process ID '10632')。

是否有人知道如何获取此信息?


你解决过这个问题吗?我有类似的需求,但是PID 4没有提供帮助。谢谢! - Colin Zabransky
4个回答

3
似乎PID为4(系统),因为实际的监听套接字在名为http的服务下。我查看了iisexpresstray.exe用于提供所有运行中IISExpress应用程序列表的内容。幸运的是,它是易于反编译的托管.NET代码(全部在iisexpresstray.dll中)。它似乎至少有三种不同的方法来获取进程的端口号:
1. 从命令行参数中读取/ port(不可靠,我们知道) 2. 运行netsh http show servicestate view = requestq并解析输出 3. 调用Microsoft.Web.RuntimeStatusClient.GetWorkerProcess(pid)并解析站点URL
不幸的是,iisexpresstray.dll中大部分有用的东西,如IisExpressHelper类,都被声明为internal(虽然我想象有工具可以生成包装器或复制程序集并公开所有内容)。

我选择使用Microsoft.Web.dll。它在我的GAC中,但由于某种原因没有出现在Visual Studio可添加引用的程序集列表中,所以我只是从我的GAC中复制了该文件。一旦我有了Microsoft.Web.dll,就只需要使用以下代码:

    using (var runtimeStatusClient = new RuntimeStatusClient())
    {
      var workerProcess = runtimeStatusClient.GetWorkerProcess(process.Id);
      // Apparently an IISExpress process can run multiple sites/applications?
      var apps = workerProcess.RegisteredUrlsInfo.Select(r => r.Split('|')).Select(u => new { SiteName = u[0], PhysicalPath = u[1], Url = u[2] });
      // If we just assume one app
      return new Uri(apps.FirstOrDefault().Url).Port;
     }

您也可以调用RuntimeClient.GetAllWorkerProcesses仅检索实际的工作进程。

我还研究了RegisteredUrlsInfo(在Microsoft.Web.dll中),发现它使用了两个COM接口:

  1. IRsca2_Core (F90F62AB-EE00-4E4F-8EA6-3805B6B25CDD)
  2. IRsca2_WorkerProcess (B1341209-7F09-4ECD-AE5F-3EE40D921870)

最后,我了解到有一个版本的Microsoft.Web.Administration可以读取IISExpress应用程序信息,但信息非常有限,而且我在系统上找到的那个甚至不允许我在没有管理员权限的情况下实例化ServerManager


1
这里是一个C#实现,用于按照@makhdumi的建议调用netsh.exe:
使用方法:
static public bool TryGetCurrentProcessRegisteredHttpPort(out List<int> ports, out Exception ex)
{
    NetshInvoker netsh = new NetshInvoker();
    return netsh.TryGetHttpPortUseByProcessId(Process.GetCurrentProcess().Id, out ports, out ex);
}

Implementation:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace YourCompanyName.Server.ServerCommon.Utility
{
    /// <summary>
    /// Invoke netsh.exe and extract information from its output.
    /// Source: @crokusek, https://dev59.com/So7ea4cB1Zd3GeqPA23V
    ///         @GETah, https://stackoverflow.com/a/8274758/538763
    /// </summary>
    public class NetshInvoker
    {
        const string NetshHttpShowServiceStateViewRequestqArgs = "http show servicestate view=requestq";

        public NetshInvoker()
        {
        }

        /// <summary>
        /// Call netsh.exe to determine the http port number used by a given windowsPid (e.g. an IIS Express process)
        /// </summary>
        /// <param name="windowsPid">For example an IIS Express process</param>
        /// <param name="port"></param>
        /// <param name="ex"></param>
        /// <returns></returns>
        public bool TryGetHttpPortUseByProcessId(Int32 windowsPid, out List<Int32> ports, out Exception ex)
        {
            ports = null;

            try
            {
                if (!TryQueryProcessIdRegisteredUrls(out Dictionary<Int32, List<string>> pidToUrlMap, out ex))
                    return false;

                if (!pidToUrlMap.TryGetValue(windowsPid, out List<string> urls))
                {
                    throw new Exception(String.Format("Unable to locate windowsPid {0} in '{1}' output.",
                        windowsPid, "netsh " + NetshHttpShowServiceStateViewRequestqArgs));
                }

                if (!urls.Any())
                {
                    throw new Exception(String.Format("WindowsPid {0} did not reference any URLs in '{1}' output.",
                        windowsPid, "netsh " + NetshHttpShowServiceStateViewRequestqArgs));
                }

                ports = urls
                    .Select(u => new Uri(u).Port)
                    .ToList();

                return true;
            }
            catch (Exception ex_)
            {
                ex = ex_;
                return false;
            }
        }

        private bool TryQueryProcessIdRegisteredUrls(out Dictionary<Int32, List<string>> pidToUrlMap, out Exception ex)
        {
            if (!TryExecNetsh(NetshHttpShowServiceStateViewRequestqArgs, out string output, out ex))
            {
                pidToUrlMap = null;
                return false;
            }

            bool gotRequestQueueName = false;
            bool gotPidStart = false;
            int currentPid = 0;
            bool gotUrlStart = false;

            pidToUrlMap = new Dictionary<int, List<string>>();

            foreach (string line in output.Split('\n').Select(s => s.Trim()))
            {
                if (!gotRequestQueueName)
                {
                    gotRequestQueueName = line.StartsWith("Request queue name:");
                }
                else if (!gotPidStart)
                {
                    gotPidStart = line.StartsWith("Process IDs:");
                }
                else if (currentPid == 0)
                {
                    Int32.TryParse(line, out currentPid);   // just get the first Pid, ignore others.
                }
                else if (!gotUrlStart)
                {
                    gotUrlStart = line.StartsWith("Registered URLs:");
                }
                else if (line.ToLowerInvariant().StartsWith("http"))
                {
                    if (!pidToUrlMap.TryGetValue(currentPid, out List<string> urls))
                        pidToUrlMap[currentPid] = urls = new List<string>();

                    urls.Add(line);
                }
                else // reset
                {
                    gotRequestQueueName = false;
                    gotPidStart = false;
                    currentPid = 0;
                    gotUrlStart = false;
                }
            }
            return true;
        }

        private bool TryExecNetsh(string args, out string output, out Exception exception)
        {
            output = null;
            exception = null;

            try
            {
                // From @GETah, https://stackoverflow.com/a/8274758/538763

                Process p = new Process();
                p.StartInfo.FileName = "netsh.exe";
                p.StartInfo.Arguments = args;
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.RedirectStandardOutput = true;
                p.Start();

                output = p.StandardOutput.ReadToEnd();
                return true;
            }
            catch (Exception ex)
            {
                exception = ex;
                return false;
            }
        }
    }
}

1
在我的情况下,我只需在任务管理器中输出“命令行”列,就可以明显地看出哪个是IISExpress: 关于描述的WTF


-1

您可以运行以下命令来获取可执行文件及其PID的信息

netstat -a -n -o -b | find "iisexpress.exe"

4
这不会输出任何内容。如我在问题中提到的那样,该端口实际上被进程ID为4的系统所占用。 - BobJohnDoe1980

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接