Java 通过TCP/IP套接字发送大数据
我有一个小项目,在Java 通过TCP/IP套接字发送大数据,java,c#,networking,tcp,large-data,Java,C#,Networking,Tcp,Large Data,我有一个小项目,在C中运行服务器,在Java中运行客户端。服务器将图像发送到客户端。 有些图像相当大(有时高达10Mb),因此我将图像字节分割,并以32768字节的块发送。 我的C#服务器代码如下: using (var stream = new MemoryStream(ImageData)) { for (int j = 1; j <= dataSplitParameters.NumberOfChunks; j++) { byte[] chunk;
C
中运行服务器,在Java
中运行客户端。服务器将图像发送到客户端。
有些图像相当大(有时高达10Mb),因此我将图像字节分割,并以32768字节的块发送。
我的C#服务器代码如下:
using (var stream = new MemoryStream(ImageData))
{
for (int j = 1; j <= dataSplitParameters.NumberOfChunks; j++)
{
byte[] chunk;
if (j == dataSplitParameters.NumberOfChunks)
chunk = new byte[dataSplitParameters.FinalChunkSize];
else
chunk = new byte[dataSplitParameters.ChunkSize];
int result = stream.Read(chunk, 0, chunk.Length);
string line = DateTime.Now + ", Status OK, " + ImageName+ ", ImageChunk, " + j + ", " + dataSplitParameters.NumberOfChunks + ", " + chunk.Length;
//write read params
streamWriter.WriteLine(line);
streamWriter.Flush();
//write the data
binaryWriter.Write(chunk);
binaryWriter.Flush();
Console.WriteLine(line);
string deliveryReport = streamReader.ReadLine();
Console.WriteLine(deliveryReport);
}
}
long dataRead = 0;
for (int j = 1; j <= numberOfChunks; j++) {
String line = bufferedReader.readLine();
tokens = line.split(", ");
System.out.println(line);
int toRead = Integer.parseInt(tokens[tokens.length - 1]);
byte[] chunk = new byte[toRead];
int read = inputStream.read(chunk, 0, toRead);
//do something with the data
dataRead += read;
String progressReport = pageLabel + ", progress: " + dataRead + "/" + dataLength + " bytes.";
bufferedOutputStream.write((progressReport + "\n").getBytes());
bufferedOutputStream.flush();
System.out.println(progressReport);
}
我做错了什么?您的Java代码似乎使用了BufferedReader
。它将数据读入自己的缓冲区,这意味着它在底层套接字输入流中不再可用——这是您的第一个问题。第二个问题是如何使用inputStream.read
——它不能保证读取您请求的所有字节,您必须在它周围设置一个循环
这不是一个特别容易解决的问题。当在同一个流中混合二进制和文本数据时,很难将其读回。在Java中,有一个名为的类可以提供一些帮助-它有一个readLine
方法来读取一行文本,还有一些方法来读取二进制数据:
DataInputStream dataInput = new DataInputStream(inputStream);
for (int j = 1; j <= numberOfChunks; j++) {
String line = dataInput.readLine();
...
byte[] chunk = new byte[toRead];
int read = dataInput.readFully(chunk);
...
}
DataInputStream dataInput=新的DataInputStream(inputStream);
对于(int j=1;j)基本问题。
一旦您将inputstream包装到bufferedreader中,您必须停止访问inputstream。bufferedreader已被缓冲,它将读取它想要的任何数据,不仅限于读取精确到下一个换行符的数据,并在那里停止
java端的BufferedReader读取的数据远远不止这些,因此它已经消耗了大量的图像数据,并且没有出路。通过制作BufferedReader,您已经无法完成这项工作,因此您无法做到这一点
根本问题。
您只有一个TCP/IP连接。在此连接上,您发送一些不相关的文本(页面、进度等),然后发送未知数量的图像数据,然后发送另一个不相关的进度更新
这从根本上说是错误的。图像解析器怎么可能知道在发送图像的中途,您会得到一个状态更新行?文本也是二进制数据,没有神奇的标识符让客户端知道:这个字节是图像数据的一部分,但这个字节是中间发送的带有进度信息的文本
简单的解决方法。
你会认为简单的解决办法是..好吧,那就停止吧!你为什么要发送这个进度?客户端完全能够知道它读取了多少字节,发送没有意义。只需..获取二进制数据。打开outputstream。发送所有数据。在客户端,打开inputstream,读取所有数据。不要参与字符串。不要使用任何带有“与字符一起工作”味道的东西(所以,BufferedReader?不。BufferedInputStream可以)
…但现在客户不知道标题,也不知道总尺寸!
所以,制作一个有线协议。它几乎是微不足道的
这是您的有线协议:
4字节,大端:SizeOfName
SizeOfName字节数。UTF-8编码的文档标题
4字节,大端:SizeOfData
SizeOfData字节数。图像数据
如果您真的希望客户端能够呈现进度条并知道标题,则不必执行任何操作,直接发送字节,并通过..关闭连接发出文件已完全发送的信号
下面是一些示例java代码:
try (InputStream in = ....) {
int nameSize = readInt(in);
byte[] nameBytes = in.readNBytes(nameSize);
String name = new String(nameBytes, StandardCharsets.UTF_8);
int dataSize = readInt(in);
try (OutputStream out =
Files.newOutputStream(Paths.get("/Users/TriSky/image.png")) {
byte[] buffer = new byte[65536];
while (dataSize > 0) {
int r = in.read(buffer);
if (r == -1) throw new IOException("Early end-of-stream");
out.write(buffer, 0, r);
dataSize -= r;
}
}
}
public int readInt(InputStream in) throws IOException {
byte[] b = in.readNBytes(4);
return ByteBuffer.wrap(b).getInt();
}
闭幕词
应用程序中的另一个错误是使用了错误的方法。Java的“读取(字节)”方法(必要时)不会完全填充该字节数组。所有读取(字节[])将执行至少读取1个字节的操作(除非流被关闭,否则它将读取none,并返回-1。其思想是:read将读取最佳的字节数:正好与当前准备提供给您的字节数相同。有多少字节?谁知道-如果忽略in.read(字节)的返回值,您的代码必然会被破坏,而您正是这样做的。您真正想要的是,例如readNBytes
,它保证它完全填充该字节数组(或者直到流结束,以先发生的为准)
请注意,在上面的传输代码中,我也使用基本读取,但在这里我不会忽略返回值。当您尝试发送图像时,您必须将图像作为普通文件打开,然后将图像子串成一些块,每个块将其更改为“base64encode”由于图像数据不是正常数据,因此当您发送并由客户端解码时,base64encode将此符号更改为正常字符,如AfHM65Hkgf7MM调试您的应用程序。您的代码假定您可以拆分到达客户端的任何行。如果二进制数据包含换行字符,会发生什么情况(0x0D 0x0A或仅0x0A)?@f1sh我不认为是这样,因为我有一个定义良好的协议:(1)服务器发送读取参数作为一行文本数据,客户端读取一行文本数据;(2)服务器发送预定义大小的二进制数据,客户端读取预定义大小的二进制数据;(3)客户端发送一行文本数据,服务器读取一行文本数据。我只是想知道这个协议是如何崩溃的。你先发送了一个日期时间。你处理了吗?并且在将原始流包装到BufferedReader中后,出于许多原因,不要使用原始流。你有没有将行
打印到控制台?这将是第一件要做的事情开始调试过程。回答得很好!你涵盖了各个方面。
try (InputStream in = ....) {
int nameSize = readInt(in);
byte[] nameBytes = in.readNBytes(nameSize);
String name = new String(nameBytes, StandardCharsets.UTF_8);
int dataSize = readInt(in);
try (OutputStream out =
Files.newOutputStream(Paths.get("/Users/TriSky/image.png")) {
byte[] buffer = new byte[65536];
while (dataSize > 0) {
int r = in.read(buffer);
if (r == -1) throw new IOException("Early end-of-stream");
out.write(buffer, 0, r);
dataSize -= r;
}
}
}
public int readInt(InputStream in) throws IOException {
byte[] b = in.readNBytes(4);
return ByteBuffer.wrap(b).getInt();
}