为什么java.net.SocksSocketImpl是Java中默认的java.net.Socket实现



这个问题很简短。 为什么 SOCKS 感知套接字实现是抽象java.net.Socket类实现的默认选择? 天真地,我希望java.net.PlainSocketImpl.

背景有点复杂。

我正在尝试杀死 GLASSFISH-12213(或者我真的在尝试解决它)。 错误本身的细节并不是很重要 - 有一个本机库不是线程安全的,并且来自GlassFish编写的LDAP领域的一些并发使用会导致JVM崩溃。

为了解决这个问题,我开始逆向工作:我可以避免首先调用本机库吗? 这导致我出于各种原因查看sun.net.spi.DefaultProxySelector,它负责为给定的 URI 找到合适的代理(或不代理)。 那里有一个地方可以进行本机方法调用,这就是 JVM 崩溃发生的地方。 如果我能避免那个电话,我就会做生意。

我可以避免该调用的一种方法是,如果我能确保 sun.net.spi.NetProperties.getBoolean("java.net.useSystemProxies") 的值为 false。 如果是这种情况,那么我前面提到的本机方法就不会被调用。 false是默认值,我在这方面根本没有修改任何内容。

(所以实际上确实如此;在我观察到错误的那台机器上运行的 GlassFish 实例中得到了明显的证明。 因此,我不确定这段代码可能仍在加载本机库;这是另一天的主题。

因此,在这条道路上,我进一步备份:在DefaultProxySelector.select(uri)调用中作为URI scheme传递的协议是什么? 也许我可以以某种方式影响愚蠢的事情,仍然以某种方式跳过那个本地电话。

事实证明,该协议是socket的(我曾假设它可能类似于ldap,但没有)。 这个事实和我被证明的假设向我表明,LDAP-realm-land中的某个地方正在手动打开一个直接套接字(即不使用HttpUrlConnection或其他抽象之类的东西)。 果然,Sun 编写的 LDAP-JNDI 桥就是这样做的;传入的 URI socket://somehost:389

因此,尽管除了使用直接默认值之外,我没有设置任何代理信息、配置任何内容或执行任何其他操作,但事实证明 JDK 尝试使用 SOCKS 代理。 有关详细信息,请参阅java.net.Socket中的setImpl()方法和SocksSocketImpl.java的第 364 行左右,并对其进行跟踪。

(这最终表明,我可以通过简单地将socksNonProxyHosts=*系统属性添加到组合中来跳过整个代码路径。 哎呀,这种行为不应该是默认的吗?

结果 - 并且再次相信DefaultProxySelector出于某种原因将其hasSystemProxies字段设置为true,尽管我或 GlassFish 没有更改配置,由花园品种 Sun LDAP 连接创建的花园品种套接字导致 SOCKS 代理服务器的本机查找。 也许只有我一个人,但这让我感到疯狂。

那么读到这篇文章的人——也许你是JDK团队的成员,或者认识某个人,或者知道这里的历史——知道为什么java.net.Socket的默认实现总是寻找SOCKS代理吗?

更新:我可以看到答案是:因此,如果您在某处设置了一个系统代理,并且它启用了 SOCKS,则所有内容都会流过它。 但是,如果java.net.useSystemProxies的默认值是 false ,那么(默认情况下)寻找 SOCKS 代理有什么意义呢?

默认套接字实现是SocksSocketImpl的,因为 JRE 可能已通过系统属性 -D socksProxyHost 和 -D socksProxyPortProxySelector.setDefault()或通过 JRE 安装的默认ProxySelector从外部配置为使用 SOCKS。

PlainSocketImpl 不会查阅这些属性或类(因为它是一个普通套接字,应该不知道有关代理的任何信息),因此这些外部配置将被忽略SocksSocketImpl并不总是调用它们来检查它们。 我同意,当没有人对 JRE 说任何关于 SOCKS 的事情时,你得到一个SocksSocketImpl似乎很奇怪,但我想 java.net.SocketProxySelector 的架构不允许它在套接字实例化时预先选择正确的 impl。

我认为您(或领导当前对该 Glassfish 错误进行调查的人)可能以错误的方式解决这个问题:与其试图颠覆 JRE 选择套接字和代理的方式,为什么不修复默认的 ProxySelector 和/或操作系统本机调用,以便在 Java 对有关代理的信息进行标准查询时, 事情不会破裂。 我认为修复程序是在代理查找过程和类的内脏中,而不是更高。


也许问我问的另一种方法是:如果DefaultProxySelector可以告诉我用于套接字连接的代理,为什么 Socket 不首先调用它来帮助它选择一个合理的实现?

我认为问题在于java.net.Socket支持多种编程模型。 有一个明显的new Socket(host, port)构造函数,但也有一个默认的构造函数new Socket()可以先构造,然后在将来的某个任意时间可以调用其connect(SocketAddress)方法。

由于ProxySelector可以用来确定是否应使用代理的部分条件是要连接到的远程主机和远程端口的名称(即提供给connect的信息),因此java.net.Socket构造函数现在知道是否需要代理还为时过早。

无论出于何种原因(我们可以假设历史的吗?/向后兼容性的原因吗?:),java.net.Socket impl 设置的唯一构造函数,而且,就像我说的,在某些情况下判断是否需要代理还为时过早。

最新更新