java Socket无法完全接收返回内容的解决方案

 更新时间:2021年10月27日 14:19:51   作者:LVXIANGAN  
这篇文章主要介绍了java Socket无法完全接收返回内容的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

最近在使用Socket通讯时,遇到了接收内容不全(返回内容 = 4字节报文长度 + 内容主体)的问题:客户端发送请求数据,服务器明明返回了73个字节内容,但客户端有时能全部接收,但有时却只能返回4个字节。

一开始是怀疑服务器返回有问题,但使用调试工具连续测试了很多次,结果显示:服务器的确每次都返回了73个字节内容。那很明显了,问题出现在客户端代码上。

错误现象

再来看看调试工具结果:

让我们来看看客户端代码,调用方法如下:(该方法适用于返回报文前两个字节表示长度的情况:2字节报文长度 + 内容主体)

public static void test() {
		SocketClient client = new SocketClient();
		// 建立socket对象
		int iret = client.connect("192.168.1.105", 1234);
		if (iret == 0) {
			// 发送数据
			client.write("helloworld".getBytes());
			// 接收数据
			byte data[] = client.read();
			if ((data != null) && (data.length != 0)) {
				// 处理接收结果
				Utils.print("响应报文字节数组---->" + Arrays.toString(data));
			}
		}		
	}

SocketClient.java源码:

public class SocketClient { 
	// 存储接收数据
	private byte m_buffer[] = new byte[0x10000];
	private Socket m_socket;
	private InputStream m_inputstream;
	private OutputStream m_outputstream;
	private BufferedInputStream m_bufferedinputstream;
	private BufferedOutputStream m_bufferedoutputstream;
	private boolean connected; 
	public int connect(String host, int port) {
		try {
			SocketAddress socketAddress = new InetSocketAddress(host, port);
			m_socket = new Socket();
			m_socket.connect(socketAddress, 5000);
			m_socket.setSoTimeout(60000);
 
			m_inputstream = m_socket.getInputStream();
			m_bufferedinputstream = new BufferedInputStream(m_inputstream);
			m_outputstream = m_socket.getOutputStream();
			m_bufferedoutputstream = new BufferedOutputStream(m_outputstream);
		} catch (Exception e) {
			return -1;
		}
		connected = true;
		return 0;
	}
 
	/**
	 * 发送请求数据
	 * 
	 * @param data
	 * @param start
	 * @param end
	 * @return
	 */
	public int write(byte data[]) {
		if (data == null || data.length == 0 || !connected) {
			return 0;
		}
		try {
			m_bufferedoutputstream.write(data, 0, data.length);
			m_bufferedoutputstream.flush();
		} catch (Exception e) {
			return -1;
		}
		return 0;
	}
	
	/**
	 * 读取返回数据
	 * 
	 * @return
	 */
	public byte[] read() {
		if (!connected) {
			return null;
		}
		int len = -1;
		try {
			// 长度不正确,有时返回4,有时返回73
			len = m_bufferedinputstream.read(m_buffer, 0, 0x10000);
		} catch (Exception e) {
			len = 0;
		}
		if (len != -1) {
			return null;
		} else {
			byte ret[] = new byte[len];
			for (int i = 0; i < len; i++) {
				ret[i] = m_buffer[i];
			}
			return ret;
		}
	}
}

通过代码调试,发现问题出现在inputsream.read方法上,java API对其描述如下:

int java. io. BufferedInputStream.read( byte[] buffer, int offset, int byteCount) throws IOException

Reads at most byteCount bytes from this stream and stores them in byte array buffer starting at offset offset. Returns the number of bytes actually read or -1 if no bytes were read and the end of the stream was encountered. If all the buffered bytes have been used, a mark has not been set and the requested number of bytes is larger than the receiver's buffer size, this implementation bypasses the buffer and simply places the results directly into buffer.

Overrides: read(...) in FilterInputStream
Parameters:
buffer the byte array in which to store the bytes read.
offset the initial position in buffer to store the bytes read from this stream.
byteCount the maximum number of bytes to store in buffer.
Returns:
the number of bytes actually read or -1 if end of stream.
Throws:
IndexOutOfBoundsException - if offset < 0 or byteCount < 0, or if offset + byteCount is greater than the size of buffer.
IOException - if the stream is already closed or another IOException occurs.

引起错误原因在于

客户端在发送数据后,过快地执行read操作,而这时服务端尚未完全返回全部内容,因此只能读到部分字节。于是换了个思路:

public class SocketClient { 
	private Socket m_socket;
	private InputStream m_inputstream;
	private OutputStream m_outputstream;
	private BufferedInputStream m_bufferedinputstream;
	private BufferedOutputStream m_bufferedoutputstream;
	private boolean connected; 
	public int connect(String host, int port) {
		try {
			SocketAddress socketAddress = new InetSocketAddress(host, port);
			m_socket = new Socket();
			m_socket.connect(socketAddress, 5000);
			m_socket.setSoTimeout(60000);
 
			m_inputstream = m_socket.getInputStream();
			m_bufferedinputstream = new BufferedInputStream(m_inputstream);
			m_outputstream = m_socket.getOutputStream();
			m_bufferedoutputstream = new BufferedOutputStream(m_outputstream);
		} catch (Exception e) {
			return -1;
		}
		connected = true;
		return 0;
	}
 
	/**
	 * 发送请求数据
	 * 
	 * @param data
	 * @param start
	 * @param end
	 * @return
	 */
	public int write(byte data[]) {
		if (data == null || data.length == 0 || !connected) {
			return 0;
		}
		try {
			m_bufferedoutputstream.write(data, 0, data.length);
			m_bufferedoutputstream.flush();
		} catch (Exception e) {
			return -1;
		}
		return 0;
	}
	
	/**
	 * 读取返回数据
	 * 
	 * @return
	 */
	public byte[] read() {
		if (!connected) {
			return null;
		}
		try {
			return readStream(m_bufferedinputstream);
		} catch (Exception e) {
			return null;
		}
	}
	
	/**
	 * @功能 读取流
	 * @param inStream
	 * @return 字节数组
	 * @throws Exception
	 */
	public static byte[] readStream(InputStream inStream) throws Exception {
		ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int len = -1;
		while ((len = inStream.read(buffer)) != -1) {
			outSteam.write(buffer, 0, len);
		}
		outSteam.close();
		inStream.close();
		return outSteam.toByteArray();
	}
	
	public static void test() {
		SocketClient client = new SocketClient();
		// 建立socket对象
		int iret = client.connect("192.168.1.105", 1234);
		if (iret == 0) {
			// 发送数据
			client.write("helloworld".getBytes());
			// 接收数据
			byte data[] = client.read();
			if ((data != null) && (data.length != 0)) {
				// 处理接收结果
				Utils.print("响应报文字节数组---->" + Arrays.toString(data));
			}
		}		
	}
}

测试通过.....

可参考以下解决思路

protected byte[] readMessage(BufferedInputStream is) throws IOException {
		//        MyLog.d(TAG,"=======>readMessage--inputStream=" );
		        int offset = 0;
		        int messageStartOffset = -1;
		        int wait = 0;
		        int messageEndOffset = -1;
		        int findStartOffset = -1;
 
		        while(messageEndOffset==-1||(messageEndOffset+2)>offset){
		            if(is.available()==0){
		                try {
		                    Thread.sleep(MESSAGE_WAIT_INTERVAL);
		                    wait += MESSAGE_WAIT_INTERVAL;
		                } catch (InterruptedException ex) {
		                }
		                if(wait>=MESSAGE_OVERTIME){
		                //超时错误
		                    throw new RuntimeException(EXCEPTION_TIMEOUT);
		                }
		                continue;
		            }
		            
		            offset += is.read(messageBuffer, offset, is.available());//读出数据
		            TestMessage.showBytes(messageBuffer, 0, offset, "MESSAGE");
		            if(messageStartOffset==-1){ //未找到报文头
		                if(findStartOffset<0)
		                    findStartOffset = 0;
		                messageStartOffset = findStartOffset(messageBuffer, findStartOffset, offset);//查找报文头
		                MyLog.e(TAG, "messageStartOffset="+messageStartOffset);
		                if(messageStartOffset>=0){//找到报文头
		                    if(messageStartOffset<2){
		                        //报文错误
		                        throw new RuntimeException(EXCEPTION_MSG_PARSE_ERROR);
		                    }else{
		                        int iMessageLength = ((messageBuffer[messageStartOffset-2]&0xff)<<8)+
		                         (messageBuffer[messageStartOffset-1]&0xff);
		//                        MyLog.e(TAG, "iMessageLength="+iMessageLength);
		                        int ignoreInvalidLength = messageStartOffset-4;
		                        messageEndOffset = iMessageLength + ignoreInvalidLength;
		//                        MyLog.e(TAG, "messageStartOffset="+messageStartOffset);
		                        MyLog.e(TAG, "messageEndOffset="+messageEndOffset);

如果想要让程序保证读取到count个字节,最好用以下代码:

int count = 100;  
byte[] b = new byte[count];  
int readCount = 0; // 已经成功读取的字节的个数  
while (readCount < count) {  
    readCount += inStream.read(b, readCount, count - readCount);  
}  

这样就能保证读取100个字节,除非中途遇到IO异常或者到了数据流的结尾情况!

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java中的权重算法(如Dubbo的负载均衡权重)详解

    Java中的权重算法(如Dubbo的负载均衡权重)详解

    这篇文章主要介绍了Java中的权重算法(如Dubbo的负载均衡权重)详解,负载均衡,其含义就是指将负载进行平衡、分摊到多个操作单元上进行运行,例如FTP服务器、Web服务器、企业核心应用服务器和其它主要任务服务器等,从而协同完成工作任务,需要的朋友可以参考下
    2023-08-08
  • 老生常谈spring boot中的定时任务

    老生常谈spring boot中的定时任务

    SpringBoot中的定时任务主要通过@Scheduled注解以及SchedulingConfigurer接口实现,本文给大家介绍spring boot中的定时任务,感兴趣的朋友跟随小编一起看看吧
    2024-05-05
  • Spring Boot+Nginx实现大文件下载功能

    Spring Boot+Nginx实现大文件下载功能

    相信很多小伙伴,在日常开放中都会遇到大文件下载的情况,大文件下载方式也有很多,比如非常流行的分片下载、断点下载;当然也可以结合Nginx来实现大文件下载,在中小项目非常适合使用,这篇文章主要介绍了Spring Boot结合Nginx实现大文件下载,需要的朋友可以参考下
    2024-05-05
  • 解决MyBatis Mapper的XML文件SQL语句无法自动提示问题(亲测有效)

    解决MyBatis Mapper的XML文件SQL语句无法自动提示问题(亲测有效)

    这篇文章主要给大家介绍了如何解决MyBatis Mapper的XML文件SQL语句无法自动提示的问题,文中有详细的原因分析,以及通过图文介绍的解决方案,需要的朋友可以参考下
    2023-10-10
  • Java实现简易拼图游戏的方法详解

    Java实现简易拼图游戏的方法详解

    这篇文章主要介绍了如何利用Java语言实现简易拼图游戏,帮助大家更好的理解和使用Java开发游戏,感兴趣的朋友可以跟随小编一起学习一下
    2022-05-05
  • 聊聊Spring——AOP详解(AOP概览)

    聊聊Spring——AOP详解(AOP概览)

    这篇文章主要介绍了Spring——AOP详解(AOP概览),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 基于AspectJ注解方式实现AOP

    基于AspectJ注解方式实现AOP

    这篇文章主要介绍了基于AspectJ注解方式实现AOP,使用AspectJ的注解可以更方便地编写和管理切面逻辑,而Spring AOP也是使用了AspectJ提供的注解来实现切面编程,需要的朋友可以参考下
    2023-09-09
  • SpringBoot+RabbitMQ实现消息可靠传输详解

    SpringBoot+RabbitMQ实现消息可靠传输详解

    消息的可靠传输是面试必问的问题之一,保证消息的可靠传输主要在生产端开启 comfirm 模式,RabbitMQ 开启持久化,消费端关闭自动 ack 模式。本文将详解SpringBoot整合RabbitMQ如何实现消息可靠传输,需要的可以参考一下
    2022-05-05
  • java基础学习JVM中GC的算法

    java基础学习JVM中GC的算法

    这篇文章主要介绍了java基础学习JVM中GC的算法,通过图文加深对GC算法思路的理解。
    2017-11-11
  • java线程的基础实例解析

    java线程的基础实例解析

    java中线程的基本方法的熟练使用是精通多线程编程的必经之路,线程相关的基本方法有wait,notify,notifyAll,sleep,join,yield等,本文浅要的介绍一下它们的使用方式
    2021-06-06

最新评论