Socket客户端读取信息不全
一、问题背景
我们系统调用银行核心接口,通讯方式是Socket,报文格式是XML。
业务量少时,正常,但是我们发起批量操作时,明明核心转账成功的,但是我们解析回执,认为转账失败。
这种情况首先排查接收报文,报文是类似这样的(真实报文内容不是这样的):
二、解决思路
1. 从源头出发
遇到这个问题,最初的想法是,怀疑银行的服务器问题(虽然这需要很大的勇气),但是行方排查并找到当时他们服务器的日志,显示的回执内容是正常的。
2. 查看自身代码
查看代码,看到每次先读取约定前6位,这个是报文长度。那是不是回执的报文长度不对,但是看其它正常接收的,银行返回的都有长度。
百度,尝试各种组合关键字搜索。找到一些博客,但是没有说的很具体。
3. 模拟复现
接着,想本地模拟一下。找到原来的测试程序,修修改改。服务端代码如下:
@RunWith(JUnit4.class)
public class ServerSocketTest {private static String DATA_GRAM = "<?xml version=\"1.0\" encoding=\"GBK\"?><Voucher><Id>2411190</Id><AdmDivCode>441600</AdmDivCode><StYear>2019</StYear><VtCode>8207</VtCode><VouDate>20191031</VouDate><VoucherNo>SZ2230010001041</VoucherNo><OriginalVtCode>8202</OriginalVtCode><OriginalVoucherNo>SZ2230010001041</OriginalVoucherNo><ChildPackNum>1</ChildPackNum><CurPackNo>1</CurPackNo><SumAmt>220.00</SumAmt><AgencyCode>223001</AgencyCode><AgencyName>guangdongheyuan</AgencyName><PayAcctNo>80020000009829508</PayAcctNo><PayAcctName>guangdongheyuan</PayAcctName><PayAcctBankName>guangdongheyuan</PayAcctBankName><PayBankCode>020</PayBankCode><PayBankName>guangdongheyuan</PayBankName><BusinessTypeCode>2</BusinessTypeCode><BusinessTypeName>guangdongheyuan</BusinessTypeName><XPayDate></XPayDate><XSumAmt></XSumAmt><Hold1></Hold1><Hold2></Hold2><PayAmt>220.00</PayAmt><DetailList><Detail><Id>2411189</Id><PayeeAcctNo>6217282013100008792</PayeeAcctNo><PayeeAcctName>guangdongheyuan</PayeeAcctName><PayeeAcctBankName>guangdongheyuan</PayeeAcctBankName><PayAmt>100.00</PayAmt><Remark></Remark><XPayDate></XPayDate><XAgentBusinessNo></XAgentBusinessNo><XpayAmt></XpayAmt><XPayeeAcctBankName></XPayeeAcctBankName><XPayeeAcctNo></XPayeeAcctNo><XPayeeAcctName></XPayeeAcctName><XAddWord></XAddWord><Hold1></Hold1><Hold2></Hold2><Hold3></Hold3><Hold4></Hold4><CONSUMEINFO></CONSUMEINFO><PROVIDEADDRESS>guangdongheyuan</PROVIDEADDRESS><PROVIDEINFO>guangdongheyuan</PROVIDEINFO><XRETURNFLAG>1</XRETURNFLAG><PKCARDDETAIL>252267</PKCARDDETAIL><PayeeAccountCardName>guangdongheyuan</PayeeAccountCardName><PayeeAccountCardBank>guangdongheyuan</PayeeAccountCardBank><PayeeAccountCardNo>6217282013100008792</PayeeAccountCardNo><ConsumeDate>20190807</ConsumeDate><VoucherBillNo>SZ2230010001041</VoucherBillNo><AgentBusinessNo>005041333</AgentBusinessNo></Detail><Detail><Id>2411189</Id><PayeeAcctNo>6217282013100008792</PayeeAcctNo><PayeeAcctName>guangdongheyuan</PayeeAcctName><PayeeAcctBankName>guangdongheyuan</PayeeAcctBankName><PayAmt>120.00</PayAmt><Remark></Remark><XPayDate></XPayDate><XAgentBusinessNo></XAgentBusinessNo><XpayAmt></XpayAmt><XPayeeAcctBankName></XPayeeAcctBankName><XPayeeAcctNo></XPayeeAcctNo><XPayeeAcctName></XPayeeAcctName><XAddWord></XAddWord><Hold1></Hold1><Hold2></Hold2><Hold3></Hold3><Hold4></Hold4><CONSUMEINFO></CONSUMEINFO><PROVIDEADDRESS>guangdongheyuan</PROVIDEADDRESS><PROVIDEINFO>guangdongheyuan</PROVIDEINFO><XRETURNFLAG>1</XRETURNFLAG><PKCARDDETAIL>252266</PKCARDDETAIL><PayeeAccountCardName>guangdongheyuan</PayeeAccountCardName><PayeeAccountCardBank>guangdongheyuan</PayeeAccountCardBank><PayeeAccountCardNo>6217282013100008792</PayeeAccountCardNo><ConsumeDate>20190804</ConsumeDate><VoucherBillNo>SZ2230010001041</VoucherBillNo><AgentBusinessNo>019060816</AgentBusinessNo></Detail></DetailList></Voucher>";@Testpublic void testServer(){ServerSocket serverSocket = null;try {serverSocket = new ServerSocket();serverSocket.bind(new InetSocketAddress(8000));while(true){Socket socket = serverSocket.accept();PrintWriter pw = new PrintWriter(socket.getOutputStream(),true);StringBuffer sb = new StringBuffer("027900"); sb.append(DATA_GRAM);sb.append(DATA_GRAM);sb.append(DATA_GRAM);sb.append(DATA_GRAM);sb.append(DATA_GRAM);sb.append(DATA_GRAM);sb.append(DATA_GRAM);sb.append(DATA_GRAM);sb.append(DATA_GRAM);sb.append(DATA_GRAM);pw.println(sb.toString());BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));for(String msg = br.readLine();msg!=null&&msg.length()>0;msg = br.readLine()){System.out.println(msg);}pw.close();br.close();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
客户端的很简单,关键的地方如下:
InputStream in = socket.getInputStream();
logger.info("######已返回回执报文");
// 读取响应报文长度
byte[] lenbytes=new byte[6];
in.read(lenbytes);String len = new String(lenbytes, 0, 6, "GBK");
int messageLen = Integer.parseInt(len);
byte[] message = new byte[messageLen];
// 普通的读取措施
in.read(message);
最初的服务端的字符串没有那么长,只有1个sb(大家都明白我指的是什么意思吧)。测试时使用for循环发起200次请求,甚至2000次,均未出现问题。
考虑请求使用多线程,目的是为了模拟服务端忙碌的情况。也没有出现问题。而且我们原本的批量业务代码也是循环发起的。一时很无奈。
考虑网络上的问题,会不会是TCP拆包粘包导致的呢?但是前段时间看的谢希仁《计算机网络》,回忆了一下,但是目前的App层有很好的约定--前6位是报文体长度。拆包不会受到影响。想不到问题就,意淫是不是前6位被拆开了,没有读全。但是如果是这样,回执报文应该变短。
参考了一些网上的类似情况,考虑模拟的报文长度可能太短了,只有2790。因此拼了10次,总长度达到27900。先是多线程的情况,200请求出现了1次。改为for循环的,200次出现7、8次。自己也很疑惑为什么会是这样。。。
三、最终方案
更改客户端的代码:
enhancedRead(in, message);private static void enhancedRead(InputStream in, byte[] bytes) throws IOException {int remaining = bytes.length;while (remaining > 0) {int location = bytes.length - remaining;int count = in.read(bytes, location, remaining);if (count == -1) {break;}remaining -= count;}}
测试单线程循环和多线程都不会出现问题了。
分析:
in.read(int[] bytes)是尽量读取数组长度的字节并返回读取到的字节数目,读取0个字节就返回0;读取到文件的结尾EOF(End of File)会返回-1。但是纵使我英语不赖,没找到没读完的情况(目前实践说明,读不到后续的也会返回)。
至于为什么会读不全,可能是拆包的问题,可能是接收端缓存的问题(滑动窗口,只是有点印象,书上是什么都忘了)。
假期看了Netty,几个简单的例子,避免了与数据流直接交互,对于拆包、编码等问题也有对应的处理Handler。后续深入探讨。
Socket客户端读取信息不全相关推荐
- 循序渐进Java Socket网络编程(多客户端、信息共享、文件传输)
目录[-] 一.TCP/IP协议 二.TCP与UDP 三.Socket是什么 四.Java中的Socket 五.基本的Client/Server程序 六.多客户端连接服务器 七.信息共享 八.文件传输 ...
- 客户端服务器信息交互,select实现客户端和服务器端的信息交互(TCP socket)
实现功能: 客户端:<1>从标准输入(键盘)读入信息,然后通过socket发送到服务器端; <2>接收来自服务器端的信息,并显示到终端里. 服务器端:<1>从标准输 ...
- python客户端和服务端区别_Python socket 客户端和服务器端
connection, address = socket.accept() 调 用accept方法时,socket会时入"waiting"状态.客户请求连接时,方法建立连接并返回服 ...
- 31 socket客户端. 服务器 异常 语法
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口. 基于文件类型的套接字家族 套接字家族的名字:AF_UNIX unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来 ...
- java TCP 从客户端键入信息 循环接收发送 示例
当服务器端的监听在 循环里面的时候 发现 不正正常接收客户端的信息 把监听accept放在循环外面却可以,刚学到这里,不是很理解! 大神路过帮忙解答下,谢谢! public class TcpClie ...
- TCP 从客户端键入信息 循环接收发送 示例
当服务器端的监听在 循环里面的时候 发现 不正正常接收客户端的信息 把监听accept放在循环外面却可以,刚学到这里,不是很理解! 大神路过帮忙解答下,谢谢! public class TcpClie ...
- socket客户端和服务器通信协议,51. Socket服务端和客户端使用TCP协议通讯
Socket服务器是网络服务中常用的服务器.使用 go 语言实现这个业务场景是很容易的. 这样的网络通讯,需要一个服务端和至少一个客户端. 我们计划构建一个这样的通讯过程.服务端启动后等待客户端的访问 ...
- 模拟QQ聊天小项目收尾---界面展示服务端与客户端进行信息交互(用到的知识:io,线程,Swing界面,面向对象思想...... )
大家好,我是一位在java学习圈中不愿意透露姓名并苟且偷生的小学员,如果文章有错误之处,还望海涵,欢迎多多指正 如果你从本文学到有用的干货知识,那么请您尽量点赞,关注,评论,收藏 这两天我一直在设计这 ...
- TCP协议客户端读取文本文件,服务器端输出到文本文件
package cn.idcast11;import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.Fil ...
最新文章
- BZOJ 3119 Book (贪心+数学推导)
- [结构型] -- 外观模式
- java中的lombok_如何在Java中使用Lombok删除样板设置器吸气剂
- jq绑定的事件不生效
- 20160512关于mac安装caffe的记录
- 前端学习(2568):使用高级特性provide和inject
- VM安装CentOS6.8虚拟机
- Java 200+ 面试题补充③ Dubbo 模块
- (23)Vue.js组件介绍
- docker镜像是否包含操作系统
- 【MySQL】Could not initialize master info structure
- 原生Js汉语拼音首字母匹配城市名
- 【C++初阶:STL简介】
- 作品交流:数字通信同步技术资源下载
- Qt-Q_OBJECT宏及控件提升导致的类重定义问题
- Xcode 模拟器(Simulator)进行录屏,保存成mp4格式
- 【c#】文本转语音,语音转文本
- Java集合排序及java集合类详解
- 勇敢者游戏(捡石子问题)
- Java实现论文查重系统