Java 对象 LinkedList 属性:仅使用 TCP 在服务器端接收第一个元素

Java object LinkedList attribute: only receiving the first element on server-side using TCP

一点上下文:客户端正在向服务器发送一个 SOSPFPacket 对象(通过 TCP),该对象具有各种属性,例如 Vector<LSA> lsaArrayLSA 本身有一个 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;
  }
}

为了发送消息,我通过实现 RunnableClient.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(),但请注意它仍然共享引用对象,即如果您尝试发送包含添加和更改元素对象的列表,它会发送新列表和新元素对象,但是不是已更改的元素对象。