java socket 通信

pujitan978 2019-08-14 05:02:31
Java socket通信,数据包的格式 : 数据长度+类型+数据的方式
下面几种错误情况该怎么处理呢?

1)收到第一个数据包不是一个完整的(前面数据丢失),数据长度取得值是错误的,之后收信数据包长度也都是错误的,一直错了。
怎么能判断出数据长度是错误的,这个包是个不完整的包呢? 怎么把第一个不完整数据包丢掉,第二包正确收信呢?

2)假设送信的数据包是100byte,但是送信时丢了中间2byte(假设),这时我期待的长度还是100,把下包的数据接收到了2byte。导致之后数据又都乱了,发生上面1)的问题。

3)client阻塞在readUTF,如果这时服务器端关机,或者网线拔掉,或者socket被close,client还阻塞在readUTF吗?怎么能立即知道socket已经断了这件事呢?


4)writeUTF前,server端socket已经close了,怎么在发送前知道通信不可呢?


...全文
196 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhiluan7665 2019-08-16
  • 打赏
  • 举报
回复
你说的 1 和 2 是不存在的,网络要不整个数据包丢了,不会收到一半的。 粘包可以通过 四楼的方式解决。 先读长度,然后根据长度,读取数据。
guishuanglin 2019-08-15
  • 打赏
  • 举报
回复
收报文线程完整代码, 包括粘包,多包处理:


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.test.util.StaticConvert;

/**
 * 数据接收线程
 */
public class ReceiveThread implements Runnable {
	private Log logger = LogFactory.getLog(this.getClass());
	private DataInputStream inStm = null;
	private Socket socket;
	private String fn = "数据接收";

	public ReceiveThread(Socket socket, DataInputStream inStm) {
		this.socket = socket;
		this.inStm = inStm;
	}

	public void run() {
		logger.info(fn+", 启动接收线程.");
		boolean b =true;
		while (b) {
			try {
				//处理前先等几秒
				Thread.sleep(1000);
				//1 连接是否正常
				if(socket.isConnected()) {
					ByteArrayInputStream bufIn2 = null;
					ByteArrayOutputStream bufCache = new ByteArrayOutputStream();
					byte[] bytes = new byte[1024];
					byte[] bufbyte = null;
					int netlen = -1;
					int buflen = -1;
					int tmplen = -1;
					int datalen = -1;
					byte[] begByte = null;
					String hexStr1 = "";
					String hexStr2 = "";
					while ((netlen = inStm.read(bytes)) != -1) {
						//合并上次一缓存数据 + 本次读取的数据
						bufCache.write(bytes, 0, netlen);
						//收到数据可能0包,1包,N包数据
						boolean hisfra =true;
						while (hisfra) {
							//最少要4字节才处理
							if(bufCache.size() <4) {
								hisfra =false;
								continue;
							}
							//取出上次缓存,然后清空缓存待用
							buflen = bufCache.size();
							bufbyte = bufCache.toByteArray();
							bufCache.reset();
							bufCache = null;
							bufCache = new ByteArrayOutputStream();
							
							//放入读取流中处理
							bufIn2 = new ByteArrayInputStream(bufbyte, 0, buflen);
							//取报文开头4字节内容
							begByte = new byte[] {bufbyte[0], bufbyte[1], bufbyte[2], bufbyte[3]} ;
							hexStr1 = StaticConvert.parseByte2HexStr(begByte, 4);//(代码自己写)
							
							//a,报文以EB90EB90开头
							if("EB90EB90".equals(hexStr1)) {
								//数据前面的报文内容必须有规定的长度才能处理: 报文头+长度+类型 = 4+2+2=8
								byte[] headByte = new byte[8];
								if(buflen >=8) {
									bufIn2.read(headByte, 0, 8);
									//取长度数据(代码自己写)
									datalen = StaticConvert.getBJXNYLenBy2ByteL(new byte[] {headByte[1],headByte[2]} );
									
									//1) 刚好一包数据
									if(datalen >0 && buflen == datalen){
										hisfra =false;
										//处理一包数据....
										
									//2) 一包数据有多余
									}else if(datalen >0 && buflen > datalen){
										byte[] dataBytes = new byte[datalen];
										//合并已读出的字节
										System.arraycopy(headByte, 0, dataBytes, 0, 6);
										//本报文其它字节
										bufIn2.read(dataBytes, 6, datalen - headByte.length);
										
										//处理一包数据....
										
										//余下的字节: 如果是EB90EB90开头, 则放在缓存, 否则认为是无效数据.
										byte[] bytes2 = new byte[bufIn2.available()];
										tmplen = bufIn2.read(bytes2);
										begByte = new byte[] {bytes2[0], bytes2[1], bytes2[2], bytes2[3]} ;
										hexStr1 = StaticConvert.parseByte2HexStr(begByte, 4);//(代码自己写)
										
										if("EB90EB90".equals(hexStr1)) {
											bufCache.write(bytes2, 0, tmplen);
										}else {
											hisfra =false;
											hexStr1 = StaticConvert.parseByte2HexStr(bytes2, tmplen);//(代码自己写)
											hexStr2 = StaticConvert.parsePrintHexStr(hexStr1);//(代码自己写)
											logger.info(fn+", 剩余无效报文: " + hexStr2);
										}
									}else {
										hisfra =false;
										//可变报文长度不够,则放入缓冲
										bufCache.write(bufbyte, 0, buflen);
									}
								}else {
									//可变报文长度不够,则放入缓冲
									hisfra =false;
									bufCache.write(bufbyte, 0, buflen);
								}
								
							//c,无效数据
							}else {
								hisfra =false;
								hexStr1 = StaticConvert.parseByte2HexStr(bufbyte, buflen);//(代码自己写)
								hexStr2 = StaticConvert.parsePrintHexStr(hexStr1);//(代码自己写)
								logger.info(fn+", 收到无效报文: " + hexStr2);
							}
							//while
							bufIn2.reset();
							bufIn2 = null;
							bufbyte = null;
						}
					}
				}else {
					b = false;
					logger.info(fn+", 接收线程结束.");
				}
			} catch (IOException e) {
				//
				logger.error(fn+", 接收报文,网络异常.", e);
			} catch (Exception e) {
				logger.error(fn+", 接收线程异常.", e);
			} finally {
			}
		}
		socket = null;
		inStm = null;
	}
}
guishuanglin 2019-08-15
  • 打赏
  • 举报
回复
我们还有其它规约, 比如国家的遥测规约就是 EB90EB90 开始 4 字节标志 报文列子: EB 90 EB 90 46 00 46 00 68 47 00 00 01 00 67 00 06 01 00 00 00 0F 08 17 04 13 11 16 读数据时, 1,读前面 4字节数据, 如果标志不是 EB 90 EB 90 就是错误的数据, 直接丢掉, 2,读长度值, 知道长度之后, 就读后面的数据, 必须达到长度, 如果长度不够则放入缓冲中, 等下次读取数据与缓冲合并. 3,根据长度读完之后, 数据还有多, 就判断后面是不是 EB 90 EB 90, 是则就是下一段报文开始(粘了别的包开头了), 放入缓冲中等下次再处理.
guishuanglin 2019-08-15
  • 打赏
  • 举报
回复
引用 楼主 pujitan978 的回复:
Java socket通信,数据包的格式 : 数据长度+类型+数据的方式 下面几种错误情况该怎么处理呢? 1)收到第一个数据包不是一个完整的(前面数据丢失),数据长度取得值是错误的,之后收信数据包长度也都是错误的,一直错了。 怎么能判断出数据长度是错误的,这个包是个不完整的包呢? 怎么把第一个不完整数据包丢掉,第二包正确收信呢? 2)假设送信的数据包是100byte,但是送信时丢了中间2byte(假设),这时我期待的长度还是100,把下包的数据接收到了2byte。导致之后数据又都乱了,发生上面1)的问题。 3)client阻塞在readUTF,如果这时服务器端关机,或者网线拔掉,或者socket被close,client还阻塞在readUTF吗?怎么能立即知道socket已经断了这件事呢? 4)writeUTF前,server端socket已经close了,怎么在发送前知道通信不可呢?
你这个格式不好搞, 如果要处理粘包问题, 必须要加规约开始标志: 比如: 国家的 104规约, 开始处就是68开头的标志. 因此规约格式为: [规约标志 + 数据长度+类型+数据的方式] 才好处理, 否则你的长度与数据会分不清, 不知道是不是开始. 看我这个报文格式与解释例子:

------------- 冻结数据请求报文分析-------------
索引:00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
报文:68 66 00 66 00 68 47 05 01 2f 00 68 00 06 2f 00 00 00 00 00 0c 06 13 00 00 00 10 0c 06 13 02 75 16
0-1,  共1字节, 起始字符(1 字节,68H)							68
1-3,  共2字节, 长度L(2 字节,只取D2-D15,D1-D0补10后值是0066)		66 00
3-5,  共2字节, 长度L2(重复,2 字节,只取D2-D15)					66 00
5-6,  共2字节, 起始字符(重复,1 字节,68H)						68

6-7,  共1字节, 控制域C(1 字节,47H)								47
7-11, 共4字节, 地址域A(4 字节)									05 01 2f 00
11-12,共1字节, 应用服务数据单元-类型标识(1 字节,64H)			68
12-13,共1字节, 应用服务数据单元-可变结构限定词(VSQ,1字节,00H) 	00
13-14,共1字节, 应用服务数据单元-传送原因(1 字节,06H)			06	
14-16,共2字节, 应用服务数据单元-公共地址(2 字节)				2f 00

16-23,共7字节, 应用服务数据单元-元公共描述-开始时间( 7 字节)	00 00 00 00 0c 06 13
23-30,共7字节, 应用服务数据单元-元公共描述-结束时间( 7 字节)	00 00 00 10 0c 06 13
30-31,共1字节, 应用服务数据单元-元公共描述-冻结周期( 1 字节)	02

31-32,len-2,共1字节, 校验和CS(1 字节,12->0C)					75
32-33,len-1,共1字节, 结束字符(1 字节,16H)						16
瘦死的黑骆驼 2019-08-15
  • 打赏
  • 举报
回复
3楼的朋友对问题1、2的回答是不确切的。因为在tcp通信中存在一个nagle算法,用来优化tcp的传输效率的,超过mss的数据包才会立即发送出去,否则很有可能存在多个小的数据包被打包成一个包发送,接收方收到的数据必然就是一个粘包,而且接收方的缓冲区如果没有及时处理,也会存在多个数据包,也会造成粘包。 对于断包,好像是有个什么字节大小的限制,发送太大的包可能会被分片发送,具体是什么不记得了,抱歉 总之对于tcp,如果要清晰消息边界,最可靠的是自定义协议格式 此番回复,如有不当,还望指正
tianfang 2019-08-14
  • 打赏
  • 举报
回复
TCP的机制,网络层负责打包,接收方收到不完整包,会要求重发,而不是交给应用层,应用层收不到残缺的包 1 单一的tcp包,要么完整收到,要么报错。你要自己处理多个包,不如打成一个包,非要多个,参考HTTP Transfer-Encoding: chunked 2 同1 不会发生 3 client read的时候,是去读网络层。服务器断线,网络层不能立即知道,直到超时才知道;服务器主动断开,客户端掉线,网络层可以立即知道,向client报错 4 tcp 4次握手才关闭,正常的关闭,网络层收到server端信息,会向应用层通知连接不可用。网络,服务器侧异常,网络层需要到达超时时间才知道
瘦死的黑骆驼 2019-08-14
  • 打赏
  • 举报
回复
补充一下,socket有个setSoTimeout(int timeout)方法表示多久收不到数据就表示超时,这时会抛出异常,如果像之前我说的每隔10秒发送一次心跳包,则设置这个方法就可以生效了,等待捕获异常即可
瘦死的黑骆驼 2019-08-14
  • 打赏
  • 举报
回复
1、你的问题1和2主要是粘包和断包的问题,其实只要是第一次的处理是正确的后面的就没有问题。按照你的协议 数据长度+类型+数据的方式,猜测是前面长度转换的4个字节,所以你应该每一次先保证读取到4个字节,然后再一次性读取那四个字节转换后的长度(length - 4)个字节,如何保证读取长度,下面给个例子

// 取前四位字节,算出此次请求的字节长度
				byte[] lengthData = new byte[4];
				int readCount = 0;
				// 读取消息头
				while (readCount < lengthData.length) {
					readCount += inputStream.read(lengthData, readCount, lengthData.length - readCount);
					if (readCount == -1) {
						log.info("服务端断开1!");
						return;
					}
				}
2、你的问题3和4是想知道是否连接正常。通常我会发送心跳包数据,可以自定义一个心跳包类型的数据,每隔多长时间发送一次,比如10s,如果说20s或者30s都没有收到此类数据,则判定断线,将socket关闭,如果是客户端此时可考虑断线重连。(其他的通过异常能处理到的那些是可以判定的)

67,513

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧