实现accept(SocketImpl s)和对象复制。

4

我正在使用 原始套接字(RAW sockets) 实现一个 正确认证和重传(Positive Acknowledgement and Retransmission) 传输协议,这种情况下需要 SocketImpl 的子类。在实现 accept 方法时,我需要类似以下的东西:

protected void accept(SocketImpl s) {
    ...
    s.copy(socket);
}

在这里,socket 是一个已经计算好的 SocketImpl 对象,需要将它复制到一个已初始化SocketImpl 对象 s 中。也就是说,我需要一个可变拷贝方法 s.copy(socket),将源 socket 复制到目标 s 中。我知道有 Object clone() 方法,但它返回一个新对象,而我需要改变 s

顺便说一句,有些人认为,在这种情况下改变参数 s 是一种糟糕的设计。这不仅是 Java 标准库中的唯一例子。 ServerSocket 类的 implAccept(Socket s) 也是一个例子。但这就是 Sun/Oracle 工程师的设计。我想知道为什么这是一个糟糕的设计。

乍一看,提供一个通用的浅拷贝 target.copy(source) 似乎有些意义,其中 target = souce.clone() 等价于 target = new TheClass(); target.copy(source);但由于它不存在(可能有充分的理由,请解释),唯一的办法是编写一个自定义的按字段复制方法

我是对的吗?谢谢。


2
你的最终意图是什么?扩展SocketImpl(或任何套接字)是相当不寻常的事情。 - Victor Sorokin
@Victor:虽然名字不暗示,但SocketImpl是抽象的。因此,扩展它应该是其最常见的用法之一 ;) - DaveFar
1
@DaveBall 是的,但在JDK 1.6.33中扩展它的所有3个类都是JDK内部的,其中2个类中有本地方法(这意味着它们实质上是JDK实现的一部分,不应由应用程序员直接扩展)。在Java中,去“克隆”或复制套接字并不是你应该做的事情。有操作系统内核来处理这些事情。 - Victor Sorokin
@Victor:FYI,我正在使用原始IP套接字实现传输协议,因此SocketImpl子类是必需的。 - francesc
2个回答

1

您误解了这个方法的用途和工作原理。我已经做过很多次了。提供给implAccept()SocketImpl是为新接受的套接字而提供的,而不是ServerSocket。所以您不需要第二份副本。您只需要将此SocketImpl包装在一个Socket中,或者您自己派生的Socket类中。甚至有一个Socket构造函数专门用于此目的。


也许我没理解,但是我正在尝试使用原始套接字实现一种教育性的正确认可重传协议,因此必须扩展 SocketImpl。基本上 accept --调用--> implAccept --调用--> getImpl().accept(SocketImpl s)。这就是我要覆盖的方法。s 是一个已经创建好的对象,所以我没有其他办法,只能对其进行变异。如果我错了,请像往常一样启迪我。 - francesc
@francesc,除非您使用JNI,否则无法以有意义的方式覆盖SocketImpl的“accept”。但是,如果您要使用JNI,则没有必要扩展JDK类。似乎人们在Java下使用“rocksaw” Java库进行原始套接字操作,请参见https://dev59.com/lG865IYBdhLWcg3wW9RE - Victor Sorokin
@Victor:显然我正在使用JNI和比“rocksaw”更简单的原始套接字接口,这是为了教育目的。 - francesc
@francesc,那我很抱歉给你带来了一些噪音,但如果您能更新您的问题并包含这些信息,对于未来的回答者将是有益的。 - Victor Sorokin

0

我猜测没有标准的target.copy(source)(即可变复制方法),因为它比克隆需要更少的情况:

  • 你需要根据你的情况和所有字段来决定是否应该复制或相同(就像克隆与深度克隆一样);
  • 通常可以设计一个方案,使您只需在外部切换引用,因此在您的情况下,您可以仅在所有地方使用引用s而不是当前对象,这更简单且更有效率。如果需要复制的值,则对应于克隆(或使用复制构造函数);
  • 您可以通过包装参数(在您的情况下是对象s)轻松实现它。

最后一点还回答了您的第二个问题:除了逐个字段复制之外,您还可以实现一个转发类,将SocketImpl的所有导出方法转发到包装的核心SocketImpl:

使用您当前的实现类C作为私有实现,并使用protected void accept(SocketImpl s)仅抛出异常; 编写一个转发类D,其中
  • 包装一个SocketImpl(例如C的对象),保存在私有字段wrapped
  • 扩展SocketImpl并将除accept之外的所有方法转发到wrapped
  • 实现protected void accept(SocketImpl s),只需使用{ s.wrapped = this; }¹;
如果您希望在某些情况下复制所有字段,请通过accept(s.clone())(或在accept内部克隆,如果您希望在所有情况下都进行复制)传递复制参数; 仅提供类型为D的对象,以便永远不会抛出C中的异常。

¹ 这个accept的实现仅适用于类型为D的参数。否则,这种可怕的设计(必须改变作为参数给出的SocketImpl)需要accept使用丑陋的技术,例如反射(请参见例如org.apache.harmony.luni.net.PlainSocketImpl在http://www.docjar.com/html/api/org/apache/harmony/luni/net/PlainSocketImpl.java.html中)。


首先,就像有浅拷贝语义的 clone 一样,我们很可能会有一个标准的 target.copy(source),它将浅拷贝源到目标。其次,我不能自由选择我想要的设计。在 accept(SocketImpl s) 中,s 是一个已经创建好的对象(请参见我的回答 @EJP),我需要修改它。 - francesc
感谢您详细的解释。也许我还没有完全理解...但是s是复制的目标,而不是源,所以我需要改变它,执行this.wrapped = s不会以任何方式修改s - francesc
哦,accept的API只说“接受连接”。 :( 那么你的意思是这个this.accept(SocketImpl s)的后置条件是将s的字段设置为与此相同的字段吗?这听起来像是一个可怕的设计(必须改变其他对象的字段)。您确定不能反过来设计它,即方法只能改变此对象吗? - DaveFar
基本上是的,thisServerSocket 类型,但具有更新s所需的信息。implAccept(Socket s)也希望改变s。这就是Sun工程师的想法 :-) - francesc
我不理解你回答中为什么需要反射技术,它只是一个简单的 accept(SocketImpl s) { s.create(true); s.setPort(port); s.setAddress(addr); ...} - francesc
好的,我写得太快了...:MySocketImpl si = s; s.create(true); si.setPort(port); si.setAddress(addr); 或者如果你喜欢的话,可以使用 ((MySocketImpl)s).setPort(port)... - francesc

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