void readFully(byte[] buf, int off, int len) 读取字节,同时阻塞直至读取所有字节。 包装如下:in = new DataInputStream(new BufferedInputStream(socket.getInputStream())); out = new DataOutputStream(socket.getOutputStream()); 满足我们以前的需求!preferme ,kokobox 以及大家觉得合适吗?
粘包后,分包的具体实现如下
// 定义消息接受缓冲区 byte[] buf = new byte[4096]; // 真正用来放数据的缓冲区,要有两个包长度的大小 byte[] tmp = new byte[4096 * 2]; // tmp缓冲区中实际数据长度 int len = 0; // 本次解析中使用了的缓冲数据长度 int usedlen = 0; // 本次接收包的真实长度 int recvLen = 0; try { // 等待接受消息 while((recvLen = skt.getInputStream().read(buf)) > 0) {
楼主【jiangguilong2000】截止到2008-07-22 23:16:57的历史汇总数据(不包括此帖):
发帖的总数量:10 发帖的总分数:390 每贴平均分数:39
回帖的总数量:9 得分贴总数量:1 回帖的得分率:11%
结贴的总数量:8 结贴的总分数:270
无满意结贴数:2 无满意结贴分:40
未结的帖子数:2 未结的总分数:120
结贴的百分比:80.00 % 结分的百分比:69.23 %
无满意结贴率:25.00 % 无满意结分率:14.81 %
楼主加油
Socket socket = new Socket();
socket.setTcpNoDelay(true);
分包可能不太好解决
似乎和两端的介质等都有关系。
综合以上的说法,就是要在TCP协议以上再封装一层协议,用来做分包的信息交换.
其实HTTP的交互过程,就是非常类似这样的.当然这种情况,必须要求发送方和接收方互为服务器客户端,否则信息就是单向的了.
其实我是想让这一行高亮红色显示的,没想到编辑器不能识别,大家注意一下,其实就in.read(data);
private final static int PACKATHEADER_DATALEN_LENGTH=4;
private final static int PACKATHEADER_LENGTH=PACKATHEADER_TYPE_LENGTH+PACKATHEADER_DATALEN_LENGTH;
private BufferedReader in;
public void run() {
while (true){
try {
recv();
} catch (CrescentException e) {
Logger.error("Receive message failed.", e);
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Logger.error("Interrupted waiting for job to finish", e);
}
}
}
private void recv() throws CrescentException{
char[] temp = new char[PACKATHEADER_LENGTH];
this.recursionRead(temp,0,PACKATHEADER_LENGTH);
String dataLen = new String(temp, PACKATHEADER_TYPE_LENGTH, PACKATHEADER_DATALEN_LENGTH);
int size = Integer.parseInt(dataLen);
char[] data = new char[size];
this.recursionRead(data,0,size);
String str=String.valueOf(temp)+String.valueOf(data);
msgQueue.offer(str);
}private void recursionRead(char[] data,int length,int gap)throws CrescentException{
int lengthTemp=0;
try {
lengthTemp = in.read(data, length, gap);
} catch (IOException e) {
throw new ClientException("Transceiver-recursionRead-1001","read I/O stream failed",e);
}
int gapTemp=gap-lengthTemp;
if(gapTemp>0){
recursionRead(data,length+lengthTemp,gapTemp);
}
}
主要是大家注意recursionRead(char[] data,int length,int gap)这个函数,原理是读入期望长度的数据,但如果
读入的长度小于期望长度,算出长度差,继续读剩余的....以此递归。Performance:还是用上述的case,服务器端模拟粘包和半包发送,间隔为50ms,500ms,5000ms....结果都是:
10010006avsdfg
10020008sdafhjSD
10010006avsdfg
10020008sdafhjSD
10010006avsdfg
.....
与期望的结果相匹配,并且Performance比以前写的要好!
我就研究到这,希望各位再踊跃跟帖,有没有更好的处理办法,用NIO方式或者阻塞式socket也可以,最好能附上源关键代码(处理粘包和半包的部分)和代码运行结果。关注中!
有数据时,Buffer = Buffer + DataIn,不停的接收
收完成后,开始解析Buffer,
根据包的协议,不停的解析Buffer,并形成一个个包进行处理,处理后,Buffer = Buffer - Data,并继续解包
完成。
而用这个类来处理的.一般是我讲到的第二种处理方式.只不过,是用换行符作为包的分隔符(我讲的第二方式比较通用,特例的情况下如果传输的是ACSII字符流,可以指定换行符为包的分隔符).接收端使用BufferedReader的readLine()方法.发送端在发送字符串之后,再追发一个换行符'\n',用于进行包的分隔.
楼主可以试一下,这个效率会很高,而且,不会有粘包,半包的现象.由于网络传输数据的不确定性(也就是说有可能传输的是图像,文件什么的),所以,一般直接使用InputStream这个类来操作.它的read方法都是阻塞读的.参数为byte数组的情况下,如果流没有结束,该方法直到byte数组填满才会返回.楼主可以试一下,直接用InputStream这个类来处理.就不会出现使用BufferedReader读出半包的情况了.当然也不用再去递归补读了.直接使用InputStream一般效率还是很高的,如果还要提高效率,那就要自己编写一个缓冲区了.使用多线程(线程数量3个就可以)并发处理.性能会显著提高的.
2.起始符法. 每个数据都有特定的起始符号内的特定数据, 如果起始符范围外有数据,可以确认是程序逻辑有问题或有人搞破坏了.上面的方法开工前, 都要制定你的应用协议. 原理清楚了, 协议搞完整了, 程序就好写了.
但是,"TCP一旦建立连接后路由路径就定下来了",这句话,我不赞同.
个人理解,TCP在传输流的时候,其实是将整个流拆成很多个独立的IP报文,进行独立路由传输的,只是,到了接收端,接收端会把这些报文按照先后顺序重新组装成TCP的IO流,当然,丢包的话,会有相应的机制来确保发送端重新发送.
TCP的建立连接阶段,本人认为,其实就是传输双达成一个共识,比如双方的发送窗口,接收窗口的大小,每个报文的大小,以及超时的时间范围等等.
因为,实际上,TCP也好,UDP也好,在网络传输的过程中都遵循的是IP协议,并且,每个节点的路由也只是遵照IP协议来进行转发的.
所以,相对来讲TCP底层的IP报文,它的路由具有不确定性.
以上是我的愚见...
2.你这样的回复根本没有任何意义,就好像我们现在问向量机,正交矩阵的问题,然后你跳出来说这都是大学里学的东西,还好意思来这里问。为什么我们来这里CSDN来问,是因为我们想要更快的解决问题,得到其他人更有项目经验的回答,有些东西,不是大学里的书里能说透的,特别是实现这块。而CSDN里聚集的都是能人,我们走到这里都是为了经验交流,共同进步。大家觉得呢?
2.如果是UDP,内置支持消息边界,不用考虑粘包,只需要考虑可靠性问题.通常为减少TCP粘包以及提高TCP效率,每个包建议不超过1.3K,以避免MAC层进行分祯
只知道首先定义好socket的网络通讯协议是不会有这种问题的,具体内容楼主可以参照下各通信运营商自己开发的通讯协议,如CMPP,SMGP,SGIP等等。
void
readFully(byte[] buf)
读取字节,同时阻塞直至读取所有字节。
void
readFully(byte[] buf, int off, int len)
读取字节,同时阻塞直至读取所有字节。
包装如下:in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
out = new DataOutputStream(socket.getOutputStream());
满足我们以前的需求!preferme ,kokobox 以及大家觉得合适吗?
// 定义消息接受缓冲区
byte[] buf = new byte[4096];
// 真正用来放数据的缓冲区,要有两个包长度的大小
byte[] tmp = new byte[4096 * 2];
// tmp缓冲区中实际数据长度
int len = 0;
// 本次解析中使用了的缓冲数据长度
int usedlen = 0;
// 本次接收包的真实长度
int recvLen = 0;
try {
// 等待接受消息
while((recvLen = skt.getInputStream().read(buf)) > 0) {
// 解析获取到的消息
ClientFileInfoPack pack = new ClientFileInfoPack();
TcpDataHeader headerRecv = (TcpDataHeader)pack.getHeader(buf);
// 如果包中有上次解析剩下的余留数据,就先处理
if(len > 0) {
// 如果长度小于通讯时候定义的头长度(我们的是26个byte)
if(len<26) {
// 不够头长度,就读现在的包
System.arraycopy(buf, 0, tmp, len, recvLen);
len += recvLen;
// 再判断是不是够一个头的长度,够了就分析这个头里面描述数据体长度的字段
if(len>=26){
headerRecv = (TcpDataHeader)pack.getHeader(tmp);
if((recvLen + len) < headerRecv.length()) {
continue;
}
}
} else if (len >= 26) {// 如果余留的数据就超过头长度
// 解析这个头, 然后读数据看看有分半包和粘包的情况
headerRecv = (TcpDataHeader)pack.getHeader(tmp);
if ((recvLen + len) < headerRecv.length()) {
System.arraycopy(buf, 0, tmp, len, recvLen);
len += recvLen;
continue;
} else {
System.arraycopy(buf, 0, tmp, len, recvLen);
len += recvLen;
}
}
} else {
// 没有余留数据,那么就直接解析先在接收到的包
System.arraycopy(buf, 0, tmp, len, recvLen);
len += recvLen;
if(len>=26) {
headerRecv = (TcpDataHeader)pack.getHeader(tmp);
if(len < headerRecv.length()) {
continue;
}
} else {
continue;
}
}
// 实际本包长度
usedlen = headerRecv.length();
//////////////////////////////////////////////////////////////////////////////////////
// 彻底解析消息
MSG msg = pack.parse(tmp);
// 先判断有没通讯异常,我们规定30000以上的是网络通讯异常
if(msg.getErrorCode() > 29999) {
ClientFileInfo cfi = new ClientFileInfo();
cfi.setErrorCode(msg.getErrorCode());
infos.add(cfi);
return infos;
}
// 把剩余的多余的移到缓冲区头部,因为这必然是下一个包的开始
for(int i=usedlen; i<len; i++) {
tmp[i - usedlen] = tmp[i];
}
//////////////////////////////////////////////////////////////////////////////////////
// 因为解析了一个包,所以要减去这个包的长度
len -= usedlen;
//////////////////////////////////////////////////////////////////////////////////////
分包算法
针对三种不同的粘包现象,分包算法分别采取了相应的解决办法。其基本思路是首先将待处理的接收数据流(长度设为m)强行转换成预定的结构数据形式,并从中取出结构数据长度字段,即图5中的n,而后根据n计算得到第一包数据长度。
1)若n<m,则表明数据流包含多包数据,从其头部截取n个字节存入临时缓冲区,剩余部分数据依此继续循环处理,直至结束。
2)若n=m,则表明数据流内容恰好是一完整结构数据,直接将其存入临时缓冲区即可。
3)若n>m,则表明数据流内容尚不够构成一完整结构数据,需留待与下一包数据合并后再行处理。
对分包算法具体内容及软件实现有兴趣者,可与作者联系。
1.定义自己的协议
2.
byte[] byteArray = new byte[len];
int actualReadNum = inputStream.read(byteArray, off, len); if (actualReadNum == len) {
return byteArray;
} int readNum;
while (actualReadNum < len) {
readNum = inputStream.read(byteArray, actualReadNum, len
- actualReadNum);
if (readNum == -1) {
throw new IOException("读取失败!");
}
actualReadNum = actualReadNum + readNum;
}
return byteArray;
}