使用
创建HttpListener
对象时
var server = new HttpListener();
server.Prefixes.Add("http://*:8080/");
server.Start();
一切正常。但是,当我使用时
var server = new HttpListener();
server.Prefixes.Add("http://demindiro.com:8080/");
server.Start();
它抛出System.Net.Sockets.SocketException (0x80004005): The requested address is not valid in this context
(下面的完整堆栈跟踪)。
在谷歌搜索异常后,很明显它与使用的地址有关。在深入了解HttpListener和EndPointManager的 Mono 源代码之后,我确定问题可能在这段代码中(GetEPListener):
static EndPointListener GetEPListener (string host, int port, HttpListener listener, bool secure)
{
IPAddress addr;
if (host == "*")
addr = IPAddress.Any;
else if (IPAddress.TryParse(host, out addr) == false){
try {
#pragma warning disable 618
IPHostEntry iphost = Dns.GetHostByName(host);
#pragma warning restore 618
if (iphost != null)
addr = iphost.AddressList[0]; // <---
else
addr = IPAddress.Any;
} catch {
addr = IPAddress.Any;
}
// ...
}
我发现在几乎所有情况下都使用ÌPAddress.Any
,除非它可以将外部IP地址与给定的主机名相关联。
但是,我只能假设这是故意的,因为它是相当明确的,并且似乎HttpListener
对其他开发人员来说效果很好,所以在这一点上,我对于出了什么问题一无所知这里。
<小时/> 堆栈跟踪:
Unhandled Exception:
System.Net.Sockets.SocketException (0x80004005): The requested address is not valid in this context
at System.Net.Sockets.Socket.Bind (System.Net.EndPoint localEP) [0x00043] in <50d80b08c1a5449282b22aedf03ce925>:0
at System.Net.EndPointListener..ctor (System.Net.HttpListener listener, System.Net.IPAddress addr, System.Int32 port, System.Boolean secure) [0x00047] in <50d80b08c1a5449282b22aedf03ce925>:0
at System.Net.EndPointManager.GetEPListener (System.String host, System.Int32 port, System.Net.HttpListener listener, System.Boolean secure) [0x0009d] in <50d80b08c1a5449282b22aedf03ce925>:0
at System.Net.EndPointManager.AddPrefixInternal (System.String p, System.Net.HttpListener listener) [0x0005e] in <50d80b08c1a5449282b22aedf03ce925>:0
at System.Net.EndPointManager.AddListener (System.Net.HttpListener listener) [0x0009c] in <50d80b08c1a5449282b22aedf03ce925>:0
at System.Net.HttpListener.Start () [0x0000f] in <50d80b08c1a5449282b22aedf03ce925>:0
at Playground.Playground.StartHttpListener () [0x00016] in <d51cc6c047ee47c9a05c5e174876cbec>:0
at Playground.Playground.Main (System.String[] args) [0x00000] in <d51cc6c047ee47c9a05c5e174876cbec>:0
[ERROR] FATAL UNHANDLED EXCEPTION: System.Net.Sockets.SocketException (0x80004005): The requested address is not valid in this context
at System.Net.Sockets.Socket.Bind (System.Net.EndPoint localEP) [0x00043] in <50d80b08c1a5449282b22aedf03ce925>:0
at System.Net.EndPointListener..ctor (System.Net.HttpListener listener, System.Net.IPAddress addr, System.Int32 port, System.Boolean secure) [0x00047] in <50d80b08c1a5449282b22aedf03ce925>:0
at System.Net.EndPointManager.GetEPListener (System.String host, System.Int32 port, System.Net.HttpListener listener, System.Boolean secure) [0x0009d] in <50d80b08c1a5449282b22aedf03ce925>:0
at System.Net.EndPointManager.AddPrefixInternal (System.String p, System.Net.HttpListener listener) [0x0005e] in <50d80b08c1a5449282b22aedf03ce925>:0
at System.Net.EndPointManager.AddListener (System.Net.HttpListener listener) [0x0009c] in <50d80b08c1a5449282b22aedf03ce925>:0
at System.Net.HttpListener.Start () [0x0000f] in <50d80b08c1a5449282b22aedf03ce925>:0
at Playground.Playground.StartHttpListener () [0x00016] in <d51cc6c047ee47c9a05c5e174876cbec>:0
at Playground.Playground.Main (System.String[] args) [0x00000] in <d51cc6c047ee47c9a05c5e174876cbec>:0
PS:我明确要使用demindiro.com
,而不是*
。
PS2:我知道有很多关于这个特殊异常的主题,但似乎没有人关注HttpListener.Start()
抛出这个异常。
更新
我再次搜索了我的问题,但这次我专门查看了TcpListener
,我找到了这个答案:https://stackoverflow.com/a/17092670/7327379
TcpListener只能绑定到运行它的计算机的本地IP地址。因此,您指定的IP不是本地计算机的IP。您的公共IP与本地计算机的IP不同,特别是如果您正在使用某种NAT。
如果我没记错的话,只需将IPAddress.Any作为你的IP来初始化听众即可。
所以,如果我直截了当,这意味着要绑定到我的外部IP,我不得不从这个
Server <-- local IP --> Modem <-- external IP --> The Internet
到这个
Server <-- external IP --> The Internet
这是对的吗?如果是,如何在没有调制解调器的情况下将服务器连接到互联网?或者我还应该做些什么?
答案 0 :(得分:3)
考虑到http://demindiro.com似乎有效,这可能是多余的。然而...
这里有一些你需要注意的事情:
通常,主机名绑定是A Bad Idea™。如果主机名解析为多个地址 - 例如,当您启用了IPv6时{1}},或者在同一台机器上有多个IPv4地址等时 - 您无法控制它绑定到哪个地址。在localhost
的情况下,您可能(有时令人不愉快)惊讶地发现它与localhost
绑定但不与::1
绑定。
然而,问题是127.0.0.1
是一个名称,它不能解析为计算机上存在的内部IP地址,因此您无法直接绑定它。确切地说,demindiro.com
是一个主机名,它通过DNS解析为IPv4地址demindiro.com
,这几乎可以肯定不是您的计算机所具有的IP地址。
而是绑定到主机名在计算机上解析的第一个IP地址。不多也不少。
假设您修改主机文件以使109.132.169.132
解析(仅限您的计算机上)有效的本地IPv4地址,例如demindiro.com
。如果我在您的网络上并且我连接到该地址,我会得到您的服务。无论我想要访问哪个主机名,只要它解析为该地址我将获得连接。我可以通过IP或任何解析到该IP的主机名直接连接,你永远不会知道。
只有在建立连接并且解析了请求后,您才能(有时)检测哪个主机名是请求的目标。
在HTTP(S)请求的情况下,主机名通常作为请求的一部分发送。这允许Web服务器在同一地址上托管多个站点。 Web服务器接收请求,确定应在本地站点之间路由的位置,然后将请求发送到已解析的站点。在HTTPS中做起来有点困难,但问题主要由SNI解决。
但是,如果我使用没有主机名的绝对路径向您的服务发出HTTP 1.0请求,那么呢?
换句话说,您无法绑定到运行代码的计算机上不存在的地址。更重要的是,除非直接在您的计算机上终止,否则您无法直接绑定到Internet连接的公共地址。由于我们中没有多少人在我们的桌面上运行拨号或桥接PPPoE,因此您尝试绑定的地址可能无法使用。
一般来说,您需要做的是在您的互联网连接上设置某种NAT重定向,以将端口8080上的传入请求转换为公共地址192.168.0.123
,并将它们重定向到您的内部地址({{1}或者其他什么)。
假设109.132.169.132
解析为您的公共IP地址。如果没有,那么你就会遇到各种各样的问题。跨公共网络的NAT是......很奇怪。您在链中添加的每个NAT都会增加更多的复杂性,更多的失败点等等。
答案 1 :(得分:2)
您可以将您的网络可视化如下
local machine -> local interface
-> interface 1 (Private IP) <-> External IP
-> interface 2 (Private IP2) <-> External IP
现在,在大多数情况下,云中的VM将具有1个网络接口和1个本地接口。本地接口是环回接口,interface 1
将具有Private IP
。
现在,当您在代码中使用以下内容时
server.Prefixes.Add("http://*:8080/");
表示绑定到计算机上的所有接口。这意味着如果你这样做
curl http://localhost:8080
或
curl http://<privateIP>:8080
两者都可以。
现在,如果您将example.com
映射到下面的externalIP
运行命令,它也可以正常运行
curl http://example.com:8080
这是因为您的example.com
映射到<extrenalIP>
,这会将所有流量转发到<privateIP>
。现在当你在下面使用时。代码
server.Prefixes.Add("http://example.com:8080/");
转换为
server.Prefixes.Add("http://<externalIP>:8080/");
这不是上下文中的有效IP,只有机器的127.0.0.1
和<PrivateIP>
有效。因此,如果您有多个接口,那么只有您应该担心不使用*
。现在,如果您有多个界面和一个特定的界面来收听您的域名,那么您将在/etc/hosts
文件中输入相同内容,如下所示
<PrivateIP> example.com
然后你可以使用
server.Prefixes.Add("http://example.com:8080/");
现在,如果您担心有人可以使用不同的主机名指向您的计算机,并且您不希望服务器在这种情况下做出响应。然后,您必须检查代码中的Host
标头,并根据相同的要求拒绝该请求。或者,您可以使用nginx
之类的网络服务器,并仅回复针对example.com