我在java中用socket读取server端发过来的二进制流,从包头前面11个字节中读取包体的字节总数,然后再循环读取包体的字节数,但当server端的数据字节比较大时就不能完全读过来 ,如从server端发送了 3万个字节,这时client端程序就会不能完全读出server端的字节,可能只能读2万多个; 当字节数少于2000 以下时,就基本没有出现过问题。
我把程序贴出来,这段程序有什么问题么?接收大量的数据该如何处理为好?
想了很久,没想明白是怎么回事,望各位不吝赐教!!!
public class SocketService {
private static Socket socket;
private static InputStream is;
private static OutputStream os;
private static BufferedInputStream br;
public static byte[] getReceiveBytes(Catalog catalog,byte[] b) {
byte[] receive_body_byte = {};
try {
socket = new Socket(catalog.getPrivateTcpIp(), Integer.parseInt(catalog.getPrivateTcpIPPort()));
is = new DataInputStream(socket.getInputStream());
os = new DataOutputStream(socket.getOutputStream());
os.write(b);
br = new BufferedInputStream(is);
// 包体长度 packlength。
String packlength = "";
// 先收11个字节,函数解析出数据包长度
byte[] encry_byte = new byte[11];
int sizeHead = br.read(encry_byte);
for (int m = 0; m < sizeHead; m++) {
if (m > 6) {
packlength = packlength + Util.byte2HexStr(encry_byte[m]);
}
}
receive_body_byte = new byte[Integer.parseInt(packlength, 16)];
// 接收剩下的字节
int size = br.read(receive_body_byte);
//打印接收到的字节
System.out.println("\n-------接收到的字节总数是-------------- "+receive_body_byte.length);
os.flush();
is.close();
os.close();
br.close();
} catch (Exception e) {
System.out.print("**********Socket异常!!!!*********"+e.getMessage());
return null;
}
return receive_body_byte;
}
========= 上面的程序不知哪里有问题,于是我就又优化了一下,代码如下,但遇到大数据还是不能全读出来===================
public class SocketService {
private static Socket socket;
private static InputStream is;
private static OutputStream os;
private static BufferedInputStream br;
public static byte[] getReceiveBytes(Catalog catalog,byte[] b) {
byte[] receive_body_byte = {};
try {
socket = new Socket(catalog.getPrivateTcpIp(), Integer.parseInt(catalog.getPrivateTcpIPPort()));
is = new DataInputStream(socket.getInputStream());
os = new DataOutputStream(socket.getOutputStream());
os.write(b);
br = new BufferedInputStream(is);
// 包体长度 packlength。
String packlength = "";
// 先收11个字节,函数解析出数据包长度
byte[] encry_byte = new byte[11];
int sizeHead = br.read(encry_byte);
for (int m = 0; m < sizeHead; m++) {
if (m > 6) {
packlength = packlength + Util.byte2HexStr(encry_byte[m]);
}
}
receive_body_byte = new byte[Integer.parseInt(packlength, 16)];
int size = 0
// 分次取server端的数据,即每次从流中取1024个字节,不足1024就一次取过来
while(true){
int a = receive_body_byte.length/1024;
if(a>=1){
int last=0;
Thread.sleep(280);
for(int i=0;i<a;i++){
//Thread.sleep(50);
byte[] btmp = new byte[1024];
br.read(btmp);
getBytes(receive_body_byte,i*1024,btmp);
last = last + i*1024;
Thread.sleep(100);
}
//然后取剩余不足1024个的字节
int aa = receive_body_byte.length%1024;
byte[] bmp = new byte[aa];
//Thread.sleep(200);
if(aa!=0){
br.read(bmp);
System.out.println(" aa------------"+aa);
p(bmp);
Thread.sleep(100);
getBytes(receive_body_byte,a*1024,bmp);
}
break;
}else{
br.read(receive_body_byte);
break;
}
}
//打印接收到的字节
System.out.println("\n-------接收到的字节总数是-------------- "+receive_body_byte.length);
os.flush();
is.close();
os.close();
br.close();
} catch (Exception e) {
System.out.print("**********Socket异常!!!!*********"+e.getMessage());
return null;
}
return receive_body_byte;
}
类似的问题,我也遇到过,我使用如下代码以后,就没有这个问题了,供你参考,
// 读取实际的对象内容
buffer = new byte[objContentleng];
int nIdx = 0;
int nTotalLen = buffer.length;
int nReadLen = 0;
while (nIdx < nTotalLen)
{
nReadLen = bis.read(buffer, nIdx, nTotalLen - nIdx);
if (nReadLen > 0)
{
nIdx = nIdx + nReadLen;
}
else
{
break;
}
}
这种不能完全读取数据的问题我在win32Socket编程里遇到过,它有个recv()函数,调用这个函数是指定了接受数据长度的len_1,但是它并不读取len_1个数据,而是返回一个数len_2,len_2就是实际接收的长度,所以要自己判断一下再封装一个函数。你在看看Api文档,有没有这种问题。
3W个字节不是一次性一个包通过网络的,有些己进入网络,有些数据还在发送方的网卡buffer中,read(byte[])可能会提前返回,你可以:
1.发送时分包分段.
2.读的时候调用readFully方法,它内部实现不等同read(byte[]).
3.干脆就一个字节一个字节的读.
如下:
InputStream ins=socket.getInputStream();
DataInputStream ds=new DataInputStream(ins);
byte[] data=new byte[1W];
ds.readFully(data);
[quote]W个字节不是一次性一个包通过网络的,有些己进入网络,有些数据还在发送方的网卡buffer中[/quote]
确实是这个样子。
我提一个建议,你可以试一试jboss下面的Netty。Netty对于我们来说屏蔽了流的读取和异常处理。你所需要做的仅仅是对在客户端或者服务器端对接受或者发送的数据进行加码或者解码,不需要编写流的写入写出部分的代码
JDK API中的说法
read(byte[] buffer)从输入流读取一些字节,保存到buffer中,返回读取的字节数。方法阻塞直到输入数据可用,遇到文件尾(返回-1),或扔出异常。
换言之,该方法不一定读取buffer.length个字节,尤其是对网络输入流而言。针对你的代码,举一个极端的例子[code="java"]
receive_body_byte = new byte[Integer.parseInt(packlength, 16)[color=red]*1000[/color]];
int size = br.read(receive_body_byte); //这个方法难道就永远不返回了?[/code]
编辑功能都没有么?
很诧异,为什么不用ObjectInput(Output)Stream来处理~
兄弟,TCP的数据包并不保证一次传过来,所以你需要获取长度之后,不停的循环,直到读到足够的数据位置。
汗,真有人用这个ObjectInput(Output)Stream?你是真不知道这里面的陷阱?
第一,所有参与方(服务器和客户端)都必须使用java,而且,所有人的java版本必须一致。。。
什么叫: 可能只能读2万多个?
读多少个字节你不知道啊?
你可以用循环读字节,看看它为什么没有读完就返回了。
自己项目里,我开一个socket同步N个文件到服务器上。
说一下我的理解:既然你知道jdk的read(byte[])方法实际读取的字节数可能小于byte.length,那你的代码在逻辑上就确实都有那么点问题,就拿你取报文头字节的代码来说:[quote]
// 包体长度 packlength。
String packlength = "";
// 先收11个字节,函数解析出数据包长度
byte[] encry_byte = new byte[11];
int sizeHead = br.read(encry_byte);
for (int m = 0; m < sizeHead; m++) {
if (m > 6) {
packlength = packlength + Util.byte2HexStr(encry_byte[m]);
}
}
[/quote]
这段代码保证不了就读取了字节流的前11个字节吧,所以这里你应该设一个期望值11,反复的读直到读到11个字节了为止,当然因为要读的数据还很少,一般不会出现读不满11字节的情况存在,但是严谨上处理是应该这样的,那同样的道理,对后面报文体的内容的处理也应该是如此,就不多说了。
个人意见,仅供参考。
TCP应该会做流量控制,不至于是应用层没有读完就会往应用层填鸭。
首先要理解什么是Socket数据流,而不是数据段。
那个readFully在DataInputStream里