Java 对象 LinkedList 属性:仅使用 TCP 在服务器端接收第一个元素
Java object LinkedList attribute: only receiving the first element on server-side using TCP
一点上下文:客户端正在向服务器发送一个 SOSPFPacket 对象(通过 TCP),该对象具有各种属性,例如 Vector<LSA> lsaArray
。 LSA
本身有一个 LinkedList<LinkDescription> links
属性。在我的测试用例中,发送了两条消息。在这两个消息中,向量中只有一个 LSA
。在第一条消息中,LSA 有一个 LinkDescription,在第二条消息中,它有两个。当我发送消息时,我会增加 messageId
.
服务器收到了两个具有正确 ID 的消息,但是在第二个消息中,link 只包含一个 link 而不是两个。我一头雾水...
以下是对象实现:
import java.io.*;
import java.util.Vector;
public class SOSPFPacket implements Serializable {
public final static short HELLO = 0;
public final static short LSU = 1;
public final static short OVER_BURDENED = 2;
public static int id = Integer.MIN_VALUE;
public String srcProcessIP;
public short srcProcessPort;
public String srcIP;
public String dstIP;
public short sospfType; //0 - HELLO, 1 - LinkState Update, 2 - Over Burdened
public String routerID;
public int messageId = id++;
public String neighborID; //neighbor's simulated IP address
public Vector<LSA> lsaArray = new Vector<>();
public String lsaInitiator = null;
}
import java.io.Serializable;
import java.util.LinkedList;
public class LSA implements Serializable {
public String linkStateID;
public int lsaSeqNumber = Integer.MIN_VALUE;
public LinkedList<LinkDescription> links = new LinkedList<LinkDescription>();
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(linkStateID + ":").append(lsaSeqNumber + "\n");
for (LinkDescription ld : links) {
sb.append(ld);
}
sb.append("\n");
return sb.toString();
}
}
import java.io.Serializable;
public class LinkDescription implements Serializable {
public String linkID;
public int portNum;
public int tosMetrics;
public LinkDescription() {}
public LinkDescription(String linkID, int portNum, int tosMetrics) {
this.linkID = linkID;
this.portNum = portNum;
this.tosMetrics = tosMetrics;
}
public String toString() {
return linkID + "," + portNum + "," + tosMetrics;
}
}
为了发送消息,我通过实现 Runnable
的 Client.java
线程来完成。以下是相关方法:
public void run() {
try {
_outputStream = new ObjectOutputStream(_clientSocket.getOutputStream());
sendMessage(SOSPFPacket.HELLO);
_inputStream = new ObjectInputStream(_clientSocket.getInputStream());
SOSPFPacket message = Util.receiveMessage(_inputStream);
if (message.sospfType == SOSPFPacket.OVER_BURDENED) {
System.out.println("Removing link with router " + message.srcIP + "...");
_router.removeLink(_remoteRouterIP);
return;
}
_remoteRouterDescription.setStatus(RouterStatus.TWO_WAY);
_router.addLinkDescriptionToDatabase(_remoteRouterDescription, _link.getWeight());
sendMessage(SOSPFPacket.HELLO);
message = Util.receiveMessage(_inputStream);
if (message.sospfType == SOSPFPacket.LSU) {
_router.synchronize(message.lsaArray);
}
_router.propagateSynchronization(message.lsaInitiator, message.srcIP);
} catch (IOException e) {
e.printStackTrace();
}
}
private void sendMessage(short messageType) {
try {
SOSPFPacket message = Util.makeMessage(_rd, _remoteRouterDescription, messageType, _router);
_outputStream.writeObject(message);
_outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public class Util {
public static SOSPFPacket makeMessage(RouterDescription local, RouterDescription external, short messageType, Router rd) {
SOSPFPacket message = new SOSPFPacket();
message.srcProcessIP = local.getProcessIPAddress();
message.srcProcessPort = local.getProcessPortNumber();
message.srcIP = local.getSimulatedIPAddress();
message.dstIP = external.getSimulatedIPAddress();
message.sospfType = messageType;
message.routerID = local.getSimulatedIPAddress();
message.neighborID = external.getSimulatedIPAddress();
rd.getLsd().getStore().forEach((k, v) -> message.lsaArray.addElement(v));
message.lsaInitiator = messageType == SOSPFPacket.LSU ? message.srcIP : null;
return message;
}
public static SOSPFPacket receiveMessage(ObjectInputStream inputStream) {
SOSPFPacket receivedMessage = null;
try {
receivedMessage = (SOSPFPacket) inputStream.readObject();
String messageType;
switch (receivedMessage.sospfType) {
case SOSPFPacket.HELLO:
messageType = "HELLO";
break;
case SOSPFPacket.LSU:
messageType = "LINKSTATEUPDATE";
break;
case SOSPFPacket.OVER_BURDENED:
messageType = "OVER_BURDENED";
break;
default:
messageType = "UNKNOWN_STATE";
break;
}
System.out.println("received " + messageType + " from " + receivedMessage.srcIP + ";");
} catch (ClassNotFoundException e) {
System.out.println("No message received.");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return receivedMessage;
}
}
并且服务器在收到新的连接时会实例化一个私有的ClientServiceThread
,负责接收消息。
private class ClientServiceThread implements Runnable {
Socket _clientSocket;
Thread _runner;
ClientServiceThread(Socket s) {
_clientSocket = s;
_runner = new Thread(this);
}
public Thread getRunner() { return _runner; }
public void run() {
ObjectInputStream inputStream = null;
ObjectOutputStream outputStream = null;
try {
inputStream = new ObjectInputStream(_clientSocket.getInputStream());
outputStream = new ObjectOutputStream(_clientSocket.getOutputStream());
while (true) {
try {
SOSPFPacket receivedMessage = Util.receiveMessage(inputStream);
//some logic not relevant since the receivedMessage is already not correct
}
}
}
}
}
同样,所有 SOSPFPacket
字段都被正确接收,除了 Vector<LSA> lsaArray
...
编辑:我还尝试在 _router.propagateSynchronization(message.lsaInitiator, message.srcIP);
之后发送第三个 sendMessage(SOSPFPacket.HELLO)
。这次发送的消息包含两个LSA
,第一个有两个LinkDescription
,第二个有一个。两个 LSA
都被服务器接收到,但是在第一个 LSA
中仍然只接收到第一个 LinkDescription
。所有三封邮件中的邮件 ID 都是正确的。
如果我第二次 运行 一切(即我为已经 运行 宁的路由器创建一个新的客户端和一个新的 ClientService 线程),只有这样服务器才最终收到两个 LinkDescription
首先 LSA
.
终于想通了。不知何故,问题似乎出在 Util.makeMessage
中的以下代码行:rd.getLsd().getStore().forEach((k, v) -> message.lsaArray.addElement(v));
。我用以下 LSA 构造函数将其替换为 rd.getLsd().getStore().forEach((k, v) -> message.lsaArray.add(new LSA(v)));
:
public LSA(LSA lsa) {
linkStateID = lsa.linkStateID;
lsaSeqNumber = lsa.lsaSeqNumber;
links = new LinkedList<>();
for (LinkDescription ld : lsa.links) {
LinkDescription linkD = new LinkDescription();
linkD.linkID = ld.linkID;
linkD.portNum = ld.portNum;
linkD.tosMetrics = ld.tosMetrics;
links.add(linkD);
}
}
换句话说,我需要深拷贝消息中包含的对象。
Java 发送对已序列化对象的引用,以保持对象图的完整性。
您应该在每个 writeObject()
之后调用 ObjectOutputStream.reset()
。
或使用ObjectOutputStream.writeUnshared()
,但请注意它仍然共享引用对象,即如果您尝试发送包含添加和更改元素对象的列表,它会发送新列表和新元素对象,但是不是已更改的元素对象。
一点上下文:客户端正在向服务器发送一个 SOSPFPacket 对象(通过 TCP),该对象具有各种属性,例如 Vector<LSA> lsaArray
。 LSA
本身有一个 LinkedList<LinkDescription> links
属性。在我的测试用例中,发送了两条消息。在这两个消息中,向量中只有一个 LSA
。在第一条消息中,LSA 有一个 LinkDescription,在第二条消息中,它有两个。当我发送消息时,我会增加 messageId
.
服务器收到了两个具有正确 ID 的消息,但是在第二个消息中,link 只包含一个 link 而不是两个。我一头雾水...
以下是对象实现:
import java.io.*;
import java.util.Vector;
public class SOSPFPacket implements Serializable {
public final static short HELLO = 0;
public final static short LSU = 1;
public final static short OVER_BURDENED = 2;
public static int id = Integer.MIN_VALUE;
public String srcProcessIP;
public short srcProcessPort;
public String srcIP;
public String dstIP;
public short sospfType; //0 - HELLO, 1 - LinkState Update, 2 - Over Burdened
public String routerID;
public int messageId = id++;
public String neighborID; //neighbor's simulated IP address
public Vector<LSA> lsaArray = new Vector<>();
public String lsaInitiator = null;
}
import java.io.Serializable;
import java.util.LinkedList;
public class LSA implements Serializable {
public String linkStateID;
public int lsaSeqNumber = Integer.MIN_VALUE;
public LinkedList<LinkDescription> links = new LinkedList<LinkDescription>();
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(linkStateID + ":").append(lsaSeqNumber + "\n");
for (LinkDescription ld : links) {
sb.append(ld);
}
sb.append("\n");
return sb.toString();
}
}
import java.io.Serializable;
public class LinkDescription implements Serializable {
public String linkID;
public int portNum;
public int tosMetrics;
public LinkDescription() {}
public LinkDescription(String linkID, int portNum, int tosMetrics) {
this.linkID = linkID;
this.portNum = portNum;
this.tosMetrics = tosMetrics;
}
public String toString() {
return linkID + "," + portNum + "," + tosMetrics;
}
}
为了发送消息,我通过实现 Runnable
的 Client.java
线程来完成。以下是相关方法:
public void run() {
try {
_outputStream = new ObjectOutputStream(_clientSocket.getOutputStream());
sendMessage(SOSPFPacket.HELLO);
_inputStream = new ObjectInputStream(_clientSocket.getInputStream());
SOSPFPacket message = Util.receiveMessage(_inputStream);
if (message.sospfType == SOSPFPacket.OVER_BURDENED) {
System.out.println("Removing link with router " + message.srcIP + "...");
_router.removeLink(_remoteRouterIP);
return;
}
_remoteRouterDescription.setStatus(RouterStatus.TWO_WAY);
_router.addLinkDescriptionToDatabase(_remoteRouterDescription, _link.getWeight());
sendMessage(SOSPFPacket.HELLO);
message = Util.receiveMessage(_inputStream);
if (message.sospfType == SOSPFPacket.LSU) {
_router.synchronize(message.lsaArray);
}
_router.propagateSynchronization(message.lsaInitiator, message.srcIP);
} catch (IOException e) {
e.printStackTrace();
}
}
private void sendMessage(short messageType) {
try {
SOSPFPacket message = Util.makeMessage(_rd, _remoteRouterDescription, messageType, _router);
_outputStream.writeObject(message);
_outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public class Util {
public static SOSPFPacket makeMessage(RouterDescription local, RouterDescription external, short messageType, Router rd) {
SOSPFPacket message = new SOSPFPacket();
message.srcProcessIP = local.getProcessIPAddress();
message.srcProcessPort = local.getProcessPortNumber();
message.srcIP = local.getSimulatedIPAddress();
message.dstIP = external.getSimulatedIPAddress();
message.sospfType = messageType;
message.routerID = local.getSimulatedIPAddress();
message.neighborID = external.getSimulatedIPAddress();
rd.getLsd().getStore().forEach((k, v) -> message.lsaArray.addElement(v));
message.lsaInitiator = messageType == SOSPFPacket.LSU ? message.srcIP : null;
return message;
}
public static SOSPFPacket receiveMessage(ObjectInputStream inputStream) {
SOSPFPacket receivedMessage = null;
try {
receivedMessage = (SOSPFPacket) inputStream.readObject();
String messageType;
switch (receivedMessage.sospfType) {
case SOSPFPacket.HELLO:
messageType = "HELLO";
break;
case SOSPFPacket.LSU:
messageType = "LINKSTATEUPDATE";
break;
case SOSPFPacket.OVER_BURDENED:
messageType = "OVER_BURDENED";
break;
default:
messageType = "UNKNOWN_STATE";
break;
}
System.out.println("received " + messageType + " from " + receivedMessage.srcIP + ";");
} catch (ClassNotFoundException e) {
System.out.println("No message received.");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return receivedMessage;
}
}
并且服务器在收到新的连接时会实例化一个私有的ClientServiceThread
,负责接收消息。
private class ClientServiceThread implements Runnable {
Socket _clientSocket;
Thread _runner;
ClientServiceThread(Socket s) {
_clientSocket = s;
_runner = new Thread(this);
}
public Thread getRunner() { return _runner; }
public void run() {
ObjectInputStream inputStream = null;
ObjectOutputStream outputStream = null;
try {
inputStream = new ObjectInputStream(_clientSocket.getInputStream());
outputStream = new ObjectOutputStream(_clientSocket.getOutputStream());
while (true) {
try {
SOSPFPacket receivedMessage = Util.receiveMessage(inputStream);
//some logic not relevant since the receivedMessage is already not correct
}
}
}
}
}
同样,所有 SOSPFPacket
字段都被正确接收,除了 Vector<LSA> lsaArray
...
编辑:我还尝试在 _router.propagateSynchronization(message.lsaInitiator, message.srcIP);
之后发送第三个 sendMessage(SOSPFPacket.HELLO)
。这次发送的消息包含两个LSA
,第一个有两个LinkDescription
,第二个有一个。两个 LSA
都被服务器接收到,但是在第一个 LSA
中仍然只接收到第一个 LinkDescription
。所有三封邮件中的邮件 ID 都是正确的。
如果我第二次 运行 一切(即我为已经 运行 宁的路由器创建一个新的客户端和一个新的 ClientService 线程),只有这样服务器才最终收到两个 LinkDescription
首先 LSA
.
终于想通了。不知何故,问题似乎出在 Util.makeMessage
中的以下代码行:rd.getLsd().getStore().forEach((k, v) -> message.lsaArray.addElement(v));
。我用以下 LSA 构造函数将其替换为 rd.getLsd().getStore().forEach((k, v) -> message.lsaArray.add(new LSA(v)));
:
public LSA(LSA lsa) {
linkStateID = lsa.linkStateID;
lsaSeqNumber = lsa.lsaSeqNumber;
links = new LinkedList<>();
for (LinkDescription ld : lsa.links) {
LinkDescription linkD = new LinkDescription();
linkD.linkID = ld.linkID;
linkD.portNum = ld.portNum;
linkD.tosMetrics = ld.tosMetrics;
links.add(linkD);
}
}
换句话说,我需要深拷贝消息中包含的对象。
Java 发送对已序列化对象的引用,以保持对象图的完整性。
您应该在每个 writeObject()
之后调用 ObjectOutputStream.reset()
。
或使用ObjectOutputStream.writeUnshared()
,但请注意它仍然共享引用对象,即如果您尝试发送包含添加和更改元素对象的列表,它会发送新列表和新元素对象,但是不是已更改的元素对象。