java Socket無(wú)法完全接收返回內(nèi)容的解決方案
最近在使用Socket通訊時(shí),遇到了接收內(nèi)容不全(返回內(nèi)容 = 4字節(jié)報(bào)文長(zhǎng)度 + 內(nèi)容主體)的問(wèn)題:客戶端發(fā)送請(qǐng)求數(shù)據(jù),服務(wù)器明明返回了73個(gè)字節(jié)內(nèi)容,但客戶端有時(shí)能全部接收,但有時(shí)卻只能返回4個(gè)字節(jié)。
一開(kāi)始是懷疑服務(wù)器返回有問(wèn)題,但使用調(diào)試工具連續(xù)測(cè)試了很多次,結(jié)果顯示:服務(wù)器的確每次都返回了73個(gè)字節(jié)內(nèi)容。那很明顯了,問(wèn)題出現(xiàn)在客戶端代碼上。
錯(cuò)誤現(xiàn)象
再來(lái)看看調(diào)試工具結(jié)果:
讓我們來(lái)看看客戶端代碼,調(diào)用方法如下:(該方法適用于返回報(bào)文前兩個(gè)字節(jié)表示長(zhǎng)度的情況:2字節(jié)報(bào)文長(zhǎng)度 + 內(nèi)容主體)
public static void test() { SocketClient client = new SocketClient(); // 建立socket對(duì)象 int iret = client.connect("192.168.1.105", 1234); if (iret == 0) { // 發(fā)送數(shù)據(jù) client.write("helloworld".getBytes()); // 接收數(shù)據(jù) byte data[] = client.read(); if ((data != null) && (data.length != 0)) { // 處理接收結(jié)果 Utils.print("響應(yīng)報(bào)文字節(jié)數(shù)組---->" + Arrays.toString(data)); } } }
SocketClient.java源碼:
public class SocketClient { // 存儲(chǔ)接收數(shù)據(jù) 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; } /** * 發(fā)送請(qǐng)求數(shù)據(jù) * * @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; } /** * 讀取返回?cái)?shù)據(jù) * * @return */ public byte[] read() { if (!connected) { return null; } int len = -1; try { // 長(zhǎng)度不正確,有時(shí)返回4,有時(shí)返回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; } } }
通過(guò)代碼調(diào)試,發(fā)現(xiàn)問(wèn)題出現(xiàn)在inputsream.read方法上,java API對(duì)其描述如下:
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.
引起錯(cuò)誤原因在于
客戶端在發(fā)送數(shù)據(jù)后,過(guò)快地執(zhí)行read操作,而這時(shí)服務(wù)端尚未完全返回全部?jī)?nèi)容,因此只能讀到部分字節(jié)。于是換了個(gè)思路:
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; } /** * 發(fā)送請(qǐng)求數(shù)據(jù) * * @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; } /** * 讀取返回?cái)?shù)據(jù) * * @return */ public byte[] read() { if (!connected) { return null; } try { return readStream(m_bufferedinputstream); } catch (Exception e) { return null; } } /** * @功能 讀取流 * @param inStream * @return 字節(jié)數(shù)組 * @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對(duì)象 int iret = client.connect("192.168.1.105", 1234); if (iret == 0) { // 發(fā)送數(shù)據(jù) client.write("helloworld".getBytes()); // 接收數(shù)據(jù) byte data[] = client.read(); if ((data != null) && (data.length != 0)) { // 處理接收結(jié)果 Utils.print("響應(yīng)報(bào)文字節(jié)數(shù)組---->" + Arrays.toString(data)); } } } }
測(cè)試通過(guò).....
可參考以下解決思路
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){ //超時(shí)錯(cuò)誤 throw new RuntimeException(EXCEPTION_TIMEOUT); } continue; } offset += is.read(messageBuffer, offset, is.available());//讀出數(shù)據(jù) TestMessage.showBytes(messageBuffer, 0, offset, "MESSAGE"); if(messageStartOffset==-1){ //未找到報(bào)文頭 if(findStartOffset<0) findStartOffset = 0; messageStartOffset = findStartOffset(messageBuffer, findStartOffset, offset);//查找報(bào)文頭 MyLog.e(TAG, "messageStartOffset="+messageStartOffset); if(messageStartOffset>=0){//找到報(bào)文頭 if(messageStartOffset<2){ //報(bào)文錯(cuò)誤 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個(gè)字節(jié),最好用以下代碼:
int count = 100; byte[] b = new byte[count]; int readCount = 0; // 已經(jīng)成功讀取的字節(jié)的個(gè)數(shù) while (readCount < count) { readCount += inStream.read(b, readCount, count - readCount); }
這樣就能保證讀取100個(gè)字節(jié),除非中途遇到IO異?;蛘叩搅藬?shù)據(jù)流的結(jié)尾情況!
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)簡(jiǎn)單圖書管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單圖書管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Java定時(shí)清理過(guò)期文件的實(shí)例代碼
這篇文章主要介紹了Java定時(shí)清理過(guò)期文件的實(shí)例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2018-12-12spring Retryable注解實(shí)現(xiàn)重試詳解
這篇文章主要介紹了spring Retryable注解實(shí)現(xiàn)重試詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09Java根據(jù)URL下載文件到本地的2種方式(大型文件與小型文件)
這篇文章主要給大家介紹了關(guān)于Java根據(jù)URL下載文件到本地的2種方式,分別是大型文件與小型文件,避免內(nèi)存溢出OOM,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01java實(shí)現(xiàn)圖片無(wú)損任意角度旋轉(zhuǎn)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)圖片無(wú)損任意角度旋轉(zhuǎn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02Java OpenCV利用KNN算法實(shí)現(xiàn)圖像背景移除
這篇文章主要為大家介紹了Java OpenCV利用K最鄰近(KNN,K-NearestNeighbor)分類算法實(shí)現(xiàn)圖像背景移除的示例代碼,需要的可以參考一下2022-01-01