Java - 可序列化和通过套接字发送对象

3
我收到一个错误消息:
IOException on socket listen: java.io.NotSerializableException: java.net.Socket

我尝试使用以下代码通过socket发送一个对象:

尝试通过以下代码通过socket发送对象:

ObjectOutputStream outObjects = new  ObjectOutputStream(socket.getOutputStream());
outObjects.writeObject(currentPlayer);

output.flush();

第二行出现错误,但是我已经将 Player 类(当前 currentPlayer 对象的类)实现序列化(implements Serializable)。但是 Player 类中的一个成员是 Socket 对象。我尝试重新定义和序列化 Socket 对象,但无法解决问题。我错在哪里了?


展示 currentPlayer 的代码。 - Bozho
6个回答

4

无法对Socket对象进行序列化,因此应将socket字段设置为瞬态。如果需要一些socket属性,您可以在Player中添加额外的字段来表示这些属性。


谢谢!我也在考虑这个问题,但是希望有某种可能性 :) 因为几乎所有的类都实现了Serializable接口,所以我会去掉addr、localport和port。谢谢! - Mikeel

3

这意味着Socket类不可序列化。

您需要将Socket成员设置为transient,这样它就不会被序列化。

但一定要确保在私有的readObject(ObjectInputStream)方法中重新创建Socket对象,否则可能会遇到空指针异常。


1

(编辑以回答评论)

尝试将socket成员声明为transient,像这样:

private transient Socket socket;

这将阻止序列化机制尝试发送socket的值。请注意,您无法有意义地发送包含表示操作系统资源(如套接字句柄)的值的对象到另一个进程。在最好的情况下,任何尝试在接收方使用此类对象都会导致抛出异常。在最坏的情况下,这样的尝试可能会引入微妙的错误(数据被发送到错误的用户)。因此,这些对象从不可序列化(除非有人犯了错误)。

考虑以下(简化的)示例:在UNIX上,套接字由整数(所谓的文件描述符)表示,该整数在创建套接字时由操作系统返回。如果将此值发送到另一个进程,则不能保证此数字实际上是指接收进程中打开的有效文件句柄。更糟糕的是,如果接收进程中存在具有相同数字值的文件句柄,则几乎不可能引用在发送进程中打开的相同套接字。因此,如果在发送数据时实际上将该数字用作接收进程中的文件描述符,则几乎肯定会将数据发送到意外的位置。

如果发送和接收进程在同一台机器上,就有办法将“套接字”从一个进程传输到另一个进程。但我怀疑,从Java访问这些操作系统调用的方法并不容易。

但是我需要使用Player对象发送它。 - Mikeel

1

有几种方法可以解决这个问题,最快的方法是将您的Socket成员标记为transient。这可以确保Java默认序列化将忽略Socket,您的副本应该包含null。

private transient Socket sock;

或者您可以提供自己的序列化方法,这需要您自己编写和读取所有成员。您可以使用在Serializable中定义的方法(请参见下文),或者改为实现Externalizable

 private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
 private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;

最好的方法是将玩家状态与网络代码分离,从而得到一个仅包含可序列化成员的类。作为奖励,发送类不再包含无效的成员字段。

1

implements Serializable本身并不能使对象可序列化。对象本身中的所有序列化字段必须依次是可序列化的(以及它们包含的任何字段)。

由于套接字不可序列化,因此您不应尝试将其发送到线路上。您可以通过将其声明为瞬态来排除它的序列化。然后反序列化的对象当然没有套接字,因此您还可能想要查看对象中的可选readObject和writeObject方法,这些方法将用于序列化和反序列化。

请参阅有关序列化的本文


1
一个socket是不可序列化的,因为它与机器、操作系统等紧密相关。要使一个对象可序列化,它的类必须实现Serializable接口,并且必须包含所有可递归序列化的对象。
因此,如果要序列化您的玩家,您必须将其socket属性从序列化中排除。您可以通过将该属性声明为瞬态来完成:
private transient Socket mySocket;

当玩家进行反序列化时,其套接字将为空,很可能无法正常工作。您应该重新审查设计,因为序列化需要套接字才能工作的对象可能没有意义。


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