套接字编程:输入流无一例外地被破坏

Socket Programming: The input stream gets corrupted without an exception

我想要达到的目标:

我正在尝试制作一个非常简单的摄像头监控系统。在这种情况下,摄像头将成为服务器,另一端将有一个客户端应用程序来观看视频。

为了简单起见,我将通过从保存的视频文件中捕获帧来模拟相机,然后通过套接字将这些帧一一发送到所有连接的客户端(是的,相机可以处理多个客户端)。在客户端,我会接收帧,然后将它们一个接一个地显示在jPanel中,以创建视频播放的效果。

我已经完成了所有这些,但它只适用于几帧,然后突然停止,无一例外。


服务器端:


这是Camera中的主要函数class:

public static void main(String[] args) throws InterruptedException, IOException, RemoteException, AlreadyBoundException {

    ServerSocket ssock = new ServerSocket(1234);
    System.out.println("Listening");
    Camera.getInstance().startCamera(); // Starts reading the frames from the video file

    while (true) {

        Socket sock = ssock.accept();
        System.out.println("Connected");

        ClientConnection con = new ClientConnection(sock); // Creates a new connection

        // Runs the connection on it's own thread
        Thread conThread = new Thread(con);
        conThread.start();

        // Keeps a reference to the connection so it can be used later to send frames
        Camera.getInstance().connections.add(con);

    }

}



来自 ClientConnection class 的片段:

构造函数:

public ClientConnection(Socket csocket) throws IOException {
    this.csocket = csocket;
    outStream = new PrintStream(csocket.getOutputStream());
    objectOutStream = new ObjectOutputStream(csocket.getOutputStream());
}



ClientConnection class 实现了 运行nable 接口,因此它可以在单独的线程上工作。 运行 方法将负责从客户端接收预定义消息 (例如 "SET_MOVIE") 并相应地执行一些操作。这些动作和它们所做的与问题无关,因此我们可以安全地忽略它们。这是 运行 方法:

@Override
public void run() {
    try {
        inStream = new Scanner(csocket.getInputStream());
        String msg;
        while (inStream.hasNext()) {
            msg = inStream.nextLine();

            if (msg.equals("SET_MOVIE")) {
                setMovie();
            } else if (msg.equals("SET_IDLE")) {
                setIdle();
            } else if (msg.equals("FORCE_STATE_ON")) {
                forceStateOn();
            } else if (msg.equals("FORCE_STATE_OFF")) {
                forceStateOff();
            } else if (msg.equals("DISCONNECT")) {
                // TO-DO
            }

        }
    } catch (IOException ex) {
        Logger.getLogger(ClientConnection.class.getName()).log(Level.SEVERE, null, ex);
    }
}



这是ClientConnectionclass中的sendFrame方法。每次有新帧可用并准备发送时都会调用它。

// SEND_FRAME here works as an indicator to the client so that it can expect
// the image and start reading it
public void sendFrame(Frame _frame) throws IOException {
    outStream.println("SEND_FRAME"); //tells the client there is a new frame
    outStream.println(_frame.getCaptureTime()); //sends the time in which the frame was captured
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ImageIO.write(_frame.getFrame(), "jpg", byteArrayOutputStream);
    byte[] size = ByteBuffer.allocate(4).putInt(byteArrayOutputStream.size()).array();
    outStream.write(size);
    outStream.write(byteArrayOutputStream.toByteArray());
    outStream.flush();
}



客户端:


这是主要方法,它只是在自己的线程上创建一个新的 CameraConnection 和 运行。

public static void main(String[] args) throws InterruptedException, IOException {
    Thread client = new Thread(new CameraConnection("Cam_1", 1234));
    client.start();
}



这是 CameraConnection 构造函数:

public CameraConnection(String name, int port) throws IOException {

    this.name = name;
    clientSocket = new Socket("localhost", port);

    // This scanner will be used to read messages sent from the server
    // such as "SEND_FRAME"
    inStream_scanner = new Scanner(clientSocket.getInputStream());

    // This inputStream will be used to read the bufferedImage in a array of bits
    inStream = clientSocket.getInputStream();

    // This is the outStream used to send messaages to the server
    outStream = new PrintStream(clientSocket.getOutputStream());
}



这是 CameraConnection:

中的 运行 方法
@Override
public void run() {

    String msg;

    while (inStream_scanner.hasNext()) {

        // Stores the incoming message and prints it
        msg = inStream_scanner.nextLine();
        System.out.println(msg);

        // Irrelevant
        if (msg.equals("NOTIFY_MOTION")) {
            onMotion();
        } 

        // Here is where the image gets read
        else if (msg.equals("SEND_FRAME")) {

            Frame f = new Frame();

            long capturedTime = inStream_scanner.nextLong();

            try {
                byte[] sizeAr = new byte[4];
                inStream.read(sizeAr);
                int size = ByteBuffer.wrap(sizeAr).asIntBuffer().get();
                byte[] imageAr = new byte[size];
                inStream.read(imageAr);
                BufferedImage image = null;
                image = ImageIO.read(new ByteArrayInputStream(imageAr));
                long receivedTime = System.currentTimeMillis();

                // Prints out the image dimension and the time in which it was received
                System.out.println("Received " + image.getHeight() + "x" + image.getWidth() + ": " + receivedTime);
                f.setCaptureTime(capturedTime);
                f.setFrame(image);
                f.setRecievedTime(receivedTime);

            } catch (Exception e) {
                System.out.println(e.toString());
            }
        }
    }
}



输出:



如上所述,它在几帧内工作正常,然后无一例外地停止,而且来自 inputStream 的扫描器开始在控制台上读取和打印奇怪的符号,就好像它已损坏一样。只要服务器不断发送帧,它就会不断打印这些奇怪的符号。这是输出的图像: screenshot from the output

  • 您不能在同一个套接字上混合使用两种类型的流或 reader 或写入器。缓冲会让你完全犯规。您需要为所有内容使用对象流。
  • 您不能假设 read() 填满了缓冲区。
  • 要读取 4 字节整数,您应该使用 readInt()(并且 writeInt() 用于写入它),而不是自己开发的代码。
  • 要阅读图片正文,您应该使用 readFully()
  • 我认为这里不需要对象流:您应该使用 DataInputStreamDataOutputStream