一、问题背景

我们系统调用银行核心接口,通讯方式是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客户端读取信息不全相关推荐

  1. 循序渐进Java Socket网络编程(多客户端、信息共享、文件传输)

    目录[-] 一.TCP/IP协议 二.TCP与UDP 三.Socket是什么 四.Java中的Socket 五.基本的Client/Server程序 六.多客户端连接服务器 七.信息共享 八.文件传输 ...

  2. 客户端服务器信息交互,select实现客户端和服务器端的信息交互(TCP socket)

    实现功能: 客户端:<1>从标准输入(键盘)读入信息,然后通过socket发送到服务器端; <2>接收来自服务器端的信息,并显示到终端里. 服务器端:<1>从标准输 ...

  3. python客户端和服务端区别_Python socket 客户端和服务器端

    connection, address = socket.accept() 调 用accept方法时,socket会时入"waiting"状态.客户请求连接时,方法建立连接并返回服 ...

  4. 31 socket客户端. 服务器 异常 语法

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口. 基于文件类型的套接字家族 套接字家族的名字:AF_UNIX unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来 ...

  5. java TCP 从客户端键入信息 循环接收发送 示例

    当服务器端的监听在 循环里面的时候 发现 不正正常接收客户端的信息 把监听accept放在循环外面却可以,刚学到这里,不是很理解! 大神路过帮忙解答下,谢谢! public class TcpClie ...

  6. TCP 从客户端键入信息 循环接收发送 示例

    当服务器端的监听在 循环里面的时候 发现 不正正常接收客户端的信息 把监听accept放在循环外面却可以,刚学到这里,不是很理解! 大神路过帮忙解答下,谢谢! public class TcpClie ...

  7. socket客户端和服务器通信协议,51. Socket服务端和客户端使用TCP协议通讯

    Socket服务器是网络服务中常用的服务器.使用 go 语言实现这个业务场景是很容易的. 这样的网络通讯,需要一个服务端和至少一个客户端. 我们计划构建一个这样的通讯过程.服务端启动后等待客户端的访问 ...

  8. 模拟QQ聊天小项目收尾---界面展示服务端与客户端进行信息交互(用到的知识:io,线程,Swing界面,面向对象思想...... )

    大家好,我是一位在java学习圈中不愿意透露姓名并苟且偷生的小学员,如果文章有错误之处,还望海涵,欢迎多多指正 如果你从本文学到有用的干货知识,那么请您尽量点赞,关注,评论,收藏 这两天我一直在设计这 ...

  9. TCP协议客户端读取文本文件,服务器端输出到文本文件

    package cn.idcast11;import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.Fil ...

最新文章

  1. BZOJ 3119 Book (贪心+数学推导)
  2. [结构型] -- 外观模式
  3. java中的lombok_如何在Java中使用Lombok删除样板设置器吸气剂
  4. jq绑定的事件不生效
  5. 20160512关于mac安装caffe的记录
  6. 前端学习(2568):使用高级特性provide和inject
  7. VM安装CentOS6.8虚拟机
  8. Java 200+ 面试题补充③ Dubbo 模块
  9. (23)Vue.js组件介绍
  10. docker镜像是否包含操作系统
  11. 【MySQL】Could not initialize master info structure
  12. 原生Js汉语拼音首字母匹配城市名
  13. 【C++初阶:STL简介】
  14. 作品交流:数字通信同步技术资源下载
  15. Qt-Q_OBJECT宏及控件提升导致的类重定义问题
  16. Xcode 模拟器(Simulator)进行录屏,保存成mp4格式
  17. 【c#】文本转语音,语音转文本
  18. Java集合排序及java集合类详解
  19. 勇敢者游戏(捡石子问题)
  20. Java实现论文查重系统

热门文章

  1. LeetCode 1463. 摘樱桃 II(dp)
  2. C8813q 分区
  3. 建立menu后怎么调用menu
  4. android视频动态壁纸app,动态壁纸视频桌面
  5. win10间歇性闪屏_今天解读win10桌面自动刷新闪屏的完全解决手法
  6. AES加解密之C++实现
  7. 【IoT】NFC 基础之 ISO14443、ISO10536 和 ISO15693 的区别
  8. Ansible在windows的使用
  9. HDU 2851 Lode Runner(简单DP)
  10. php开发团购,php 团购倒计时 代码