TCP通信客户端-解决数据包接收不全的过程

背景:5个串口条码枪,通过MOXA Nport系列转换器,以TCPServer的形式推送扫描到的条码到客户端。5个条码枪均位于流水线上方的支架上,流水线货物流过的瞬间条码枪完成箱中12个或者4个货物的扫描(12个一箱时启动三个条码枪,4个一箱时启动两个条码枪),并推送出去。

问题:开发TCP客户端接收并解析条码

  1. 由于条码枪半自动模式下,自动识别条码时前排的货物条码总是漏掉,因此采用客户端循环发送读取命令的方式;

  2. 条码枪扫描推送的条码没有开始符和数据包长度,只有0x10,0x13两个结束符。

这是开发前连接的需求和信息。

TCP客户端版本一

本着不重复造轮子的思路,直接照搬之前局域网PLC通信的TCP实现,稍加改造,就ok了。

public void ReceiveServerTcpMsg()
{
var buffer = new byte[4096];
while (!BExit)
{
try
{
if (_socket != null && _socket.Connected)
{
if (_socket.Poll(-1, SelectMode.SelectRead))
{
if (_socket.Available > 0)
{
//接收条码
var recLength=_socket.ReceiveFrom(buffer, SocketFlags.None, ref _serverEndPoint); //解析条码
var barcode = GetBarCodeFromAscii(buffer); //后续业务逻辑处理
}
}
}
}
catch (Exception e)
{
if (EquipMessage != null)
{
EquipMessage(EquipName, _barcodeNo, 0, string.Format("ReceiveServerTcpMsg异常。ErrorCode={0}", e.Message));
}
}
}
}

写好了,拿个条码枪测试一下。

扫一个条码ok,扫一个条码ok,连扫几个条码问题出现了:

我扫的条码是6位的,但是总会出现2位或者4位的条码。

怎么会这样呢?这个实现在与PLC的通讯过程中是没有问题的呀,这里数据怎么会不全。当时手边没有资料,按照函数的说明一致在调整

_socket.Available>0

因为发送的可访问的字节数大于0就读,可能没有发送完毕,因此出现断码,于是调整为

_socket.Available>5

问题暂时解决了,但是真正使用时的条码也不是6位,而且即使这样只是断码的情况还是偶尔会出现。但是有几个疑问:

TCP一个数据包不是发送的很快吗?为什么会出现数据包不全的情况?

晚上开始查阅资料,发现了一些有用的东西:

  1. TCP底层存在着分包机制,网络传输过程中可能会出现粘包;
  2. 串口转网口通信是典型按照一定频率发送数据的;
  3. 更详细一点解释可以参照这两篇博客C# Socket Networkstream接收数据不全关于串口接收并解析数据

基于以上信息可以确认

  1. TCP数据传输是基于流的,一条完整的数据可能分包后需要发送多次,也会由于网路堵塞多个数据包粘在一起;
  2. TCP通讯数据包,客户端需要根据通讯协议约定的数据包头部、尾部、长度等信息,重新组合成一条完成的数据。

于是第二个版本的客户端就诞生了。

//接收函数
public void ReceiveServerTcpMsg()
{
var buffer = new byte[4096];
while (!BExit)
{
try
{
if (_socket != null && _socket.Connected)
{
if (_socket.Poll(-1, SelectMode.SelectRead))
{
if (_socket.Available > 5)
{
var recLength=_socket.ReceiveFrom(buffer, SocketFlags.None, ref _serverEndPoint);
if(recLength<=0)continue; //接收字节放入缓冲区
var recStr=new StringBuilder(); //因为出队列和入队列是两个线程,所以需要加锁
//接收线程只负责把数据放在缓冲队列
lock (_barcodeBuffer)
{
for (var i = 0; i < recLength; i++)
{
_barcodeBuffer.Enqueue(buffer[i]);
recStr.Append(buffer[i]);
}
}
}
}
}
}
catch (Exception e)
{
if (EquipMessage != null)
{
EquipMessage(EquipName, _barcodeNo, 0, string.Format("ReceiveServerTcpMsg异常。ErrorCode={0}", e.Message));
}
}
}
}

处理函数:

private void receiveTimer_Elapsed(object sender, ElapsedEventArgs e)
{
//没有收到数据
if (_barcodeBuffer.Count <= 0) return; var buffer = new byte[1024]; //加锁
lock (_barcodeBuffer)
{
//剔除开头空字符
while (_barcodeBuffer.Peek().Equals(10) || _barcodeBuffer.Peek().Equals(13))
{
_barcodeBuffer.Dequeue();
}
if (_barcodeBuffer.Count <= 0) return; var length = 0; //没有收到结束符不读取缓冲区
if (!_barcodeBuffer.Contains(10) || !_barcodeBuffer.Contains(13)) return;
byte item;
while (!(item = _barcodeBuffer.Dequeue()).Equals(10))
{
buffer[length] = item;
length++;
}
buffer[length] = item; //条码解析
} } //处理线程定时读取数据和处理数据
_receiveTimer = new Timer
{
Interval = 100,
Enabled = false,
AutoReset = true
};
_receiveTimer.Elapsed += receiveTimer_Elapsed; //接收线程负责接收数据
new Thread(ReceiveServerTcpMsg).Start();

ok,这个版本无论是在多个条码枪同时工作还是提高条码枪扫描频率情况下都可以正常工作。这算是基本满足现有的需求了吧。

但是在后续查阅了解到还有几个问题有待解决:

  1. Lock锁性能太差,.net4.0提供有异步队列,是线程安全的,而且锁的性能损耗更小(本人没有使用是由于本项目基于.net3.5);
  2. 另外是一个业务问题:由于流水线速度、条码枪触发时机等问题,货物存在漏读的现象。

这是第一篇博客,拖了这么长时间终于写完了。

刚开始准备写的时候,感觉要写的东西挺多的,但是由于选择写博客的客户端和工作上的事情耽搁了。导致现在感觉怎么写都有点干巴巴的,好吧万事开头难,我这也算开头了。

TCP客户端的更多相关文章

  1. Python TCP客户端

    import socket target_host="www.baidu.com" target_port=80 # 建立一个socket对象 client=socket.sock ...

  2. Java网络编程(TCP客户端)

    TCP传输:两个端点建立连接后会有一个传输数据的通道,这个通道就称为流,而且是建立在网络基础上的流,之为socket流,该流中既可以读取也可以写入. TCP的两个端点:一个客户端:ServerSock ...

  3. 【实验 1-1】编写一个简单的 TCP 服务器和 TCP 客户端程序。程序均为控制台程序窗口。

    在新建的 C++源文件中编写如下代码. 1.TCP 服务器端#include<winsock2.h> //包含头文件#include<stdio.h>#include<w ...

  4. 【RL-TCPnet网络教程】第14章 RL-TCPnet之TCP客户端

    第14章      RL-TCPnet之TCP客户端 本章节为大家讲解RL-TCPnet的TCP客户端实现,学习本章节前,务必要优先学习第12章TCP传输控制协议基础知识.有了这些基础知识之后,再搞本 ...

  5. UDP广播 与 TCP客户端 --服务端

    随着倒计时的响声,自觉无心工作,只想为祖国庆生. 最近有遇到过这样一个问题,将摄像头识别的行人,车辆实时显示在客户端中.有提供接口,会以Json的数据的形式将实时将识别的对象进行Post提交.所以我们 ...

  6. 10-51单片机ESP8266学习-AT指令(ESP8266连接路由器,建立TCP服务器,分别和C#TCP客户端和AndroidTCP客户端通信+花生壳远程通信)

    http://www.cnblogs.com/yangfengwu/p/8871464.html 先把源码和资料链接放到这里 源码链接:https://pan.baidu.com/s/1wT8KAOI ...

  7. android 之TCP客户端编程

    补充,由于这篇文章是自己入门的时候写的,随着Android系统的升级可能有发送需要在任务 中进行,如有问题请百度 thread 或者看下面链接的文章 https://www.cnblogs.com/y ...

  8. TCP客户端图片上传服务端保存本地示例

    //TCP客户端public class TCPClient { public static void main(String[] args)throws IOException { Socket s ...

  9. tcp客户端封装

    1.头文件 #ifndef TCPCLIENT_H #define TCPCLIENT_H #include <QTcpSocket> class TcpClient : public Q ...

随机推荐

  1. [WEB]绕过安全狗与360PHP一句话的编写

    00x01安全狗的确是让人很头痛,尤其是在上传一句话或者写入一句话的时候,会被安全狗拦截从而拿不下shell.当然,安全狗是最简单的一款waf,很容易就进行一个绕过.00x02对于绕过安全狗跟360, ...

  2. [Swift]LeetCode576. 出界的路径数 | Out of Boundary Paths

    There is an m by n grid with a ball. Given the start coordinate (i,j) of the ball, you can move the ...

  3. [Swift]LeetCode591. 标签验证器 | Tag Validator

    Given a string representing a code snippet, you need to implement a tag validator to parse the code ...

  4. 阿里云服务器公网Ip外网无法访问

    拥有了自己的服务器后,发现需要各种配置,之前应用公司的服务器的时候,一般通过内网访问,或者外网访问时,很多配置其他人员都已经配置好了,但是现在在自己的服务器上发布自己的网站的时候,才发现事情并没有自己 ...

  5. python—day9 函数的定义、操作使用方法、函数的分类、函数的嵌套调用

    一.函数的定义 函数的四个组成部分: 函数名. 函数体. 函数返回值. 函数参数 1.概念:重复利用的工具,可以完成特定功能的代码块,函数是存放代码块的容器 2.定义: def:声明函数的关键词 函数 ...

  6. zookeeper实现项目初始化缓存以及同步监听

    Spring-利用InitializingBean接口和zookeeper实现项目初始化缓存以及同步监听 1.先贴出几个需要用到的工具类 ZkClientUtils import com.ithzk. ...

  7. Python获取文件夹的名字

    dir = "../data/20170308/221.176.64.146/" # root 文件夹下的所有文件夹(包括子文件夹)的路径名字../data/20170308/22 ...

  8. javascript ES6 新特性之 let

    let的作用是声明变量,和var差不多. let是ES6提出的,在了解let之前,最好先熟悉var的原理. JavaScript有一个机制叫“预解析”,也可以叫“提升(Hoisting)机制”.很多刚 ...

  9. 跳槽 & 思维导图

    个人博客原文: 跳槽 & 思维导图 今年的冬天有点"冷".给大家来点实在的东西. 不知道大家在跳槽的时候是怎么做的?直接投简历面试?还是准备了一段时间,复习一波知识点后再投 ...

  10. 【java爬虫】---爬虫+jsoup轻松爬博客

    爬虫+jsoup轻松爬博客 最近的开发任务主要是爬虫爬新闻信息,这里主要用到技术就是jsoup,jsoup 是一款 Java的HTML解析器,可直接解析某个URL地址.HTML文本内容.它提供了一套非 ...