套接字编程:输入流无一例外地被破坏
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()
。
- 我认为这里不需要对象流:您应该使用
DataInputStream
和 DataOutputStream
。
我想要达到的目标:
我正在尝试制作一个非常简单的摄像头监控系统。在这种情况下,摄像头将成为服务器,另一端将有一个客户端应用程序来观看视频。
为了简单起见,我将通过从保存的视频文件中捕获帧来模拟相机,然后通过套接字将这些帧一一发送到所有连接的客户端(是的,相机可以处理多个客户端)。在客户端,我会接收帧,然后将它们一个接一个地显示在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()
。 - 我认为这里不需要对象流:您应该使用
DataInputStream
和DataOutputStream
。