TCP客户端
TCP通信客户端-解决数据包接收不全的过程
背景:5个串口条码枪,通过MOXA Nport系列转换器,以TCPServer的形式推送扫描到的条码到客户端。5个条码枪均位于流水线上方的支架上,流水线货物流过的瞬间条码枪完成箱中12个或者4个货物的扫描(12个一箱时启动三个条码枪,4个一箱时启动两个条码枪),并推送出去。
问题:开发TCP客户端接收并解析条码
由于条码枪半自动模式下,自动识别条码时前排的货物条码总是漏掉,因此采用客户端循环发送读取命令的方式;
条码枪扫描推送的条码没有开始符和数据包长度,只有
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一个数据包不是发送的很快吗?为什么会出现数据包不全的情况?
晚上开始查阅资料,发现了一些有用的东西:
- TCP底层存在着分包机制,网络传输过程中可能会出现粘包;
- 串口转网口通信是典型按照一定频率发送数据的;
- 更详细一点解释可以参照这两篇博客C# Socket Networkstream接收数据不全和关于串口接收并解析数据
基于以上信息可以确认
- TCP数据传输是基于流的,一条完整的数据可能分包后需要发送多次,也会由于网路堵塞多个数据包粘在一起;
- 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,这个版本无论是在多个条码枪同时工作还是提高条码枪扫描频率情况下都可以正常工作。这算是基本满足现有的需求了吧。
但是在后续查阅了解到还有几个问题有待解决:
- Lock锁性能太差,.net4.0提供有异步队列,是线程安全的,而且锁的性能损耗更小(本人没有使用是由于本项目基于.net3.5);
- 另外是一个业务问题:由于流水线速度、条码枪触发时机等问题,货物存在漏读的现象。
这是第一篇博客,拖了这么长时间终于写完了。
刚开始准备写的时候,感觉要写的东西挺多的,但是由于选择写博客的客户端和工作上的事情耽搁了。导致现在感觉怎么写都有点干巴巴的,好吧万事开头难,我这也算开头了。
TCP客户端的更多相关文章
- Python TCP客户端
import socket target_host="www.baidu.com" target_port=80 # 建立一个socket对象 client=socket.sock ...
- Java网络编程(TCP客户端)
TCP传输:两个端点建立连接后会有一个传输数据的通道,这个通道就称为流,而且是建立在网络基础上的流,之为socket流,该流中既可以读取也可以写入. TCP的两个端点:一个客户端:ServerSock ...
- 【实验 1-1】编写一个简单的 TCP 服务器和 TCP 客户端程序。程序均为控制台程序窗口。
在新建的 C++源文件中编写如下代码. 1.TCP 服务器端#include<winsock2.h> //包含头文件#include<stdio.h>#include<w ...
- 【RL-TCPnet网络教程】第14章 RL-TCPnet之TCP客户端
第14章 RL-TCPnet之TCP客户端 本章节为大家讲解RL-TCPnet的TCP客户端实现,学习本章节前,务必要优先学习第12章TCP传输控制协议基础知识.有了这些基础知识之后,再搞本 ...
- UDP广播 与 TCP客户端 --服务端
随着倒计时的响声,自觉无心工作,只想为祖国庆生. 最近有遇到过这样一个问题,将摄像头识别的行人,车辆实时显示在客户端中.有提供接口,会以Json的数据的形式将实时将识别的对象进行Post提交.所以我们 ...
- 10-51单片机ESP8266学习-AT指令(ESP8266连接路由器,建立TCP服务器,分别和C#TCP客户端和AndroidTCP客户端通信+花生壳远程通信)
http://www.cnblogs.com/yangfengwu/p/8871464.html 先把源码和资料链接放到这里 源码链接:https://pan.baidu.com/s/1wT8KAOI ...
- android 之TCP客户端编程
补充,由于这篇文章是自己入门的时候写的,随着Android系统的升级可能有发送需要在任务 中进行,如有问题请百度 thread 或者看下面链接的文章 https://www.cnblogs.com/y ...
- TCP客户端图片上传服务端保存本地示例
//TCP客户端public class TCPClient { public static void main(String[] args)throws IOException { Socket s ...
- tcp客户端封装
1.头文件 #ifndef TCPCLIENT_H #define TCPCLIENT_H #include <QTcpSocket> class TcpClient : public Q ...
随机推荐
- 【DFS】n皇后问题
回溯: 递归调用代表开启一个分支,如果希望这个分支返回后某些数据恢复到分支开启前的状态以便重新开始,就要使用到回溯技巧,全排列的交换法,数独,部分和,用到了回溯.下一个状态在开始之前需要利用到之前的状 ...
- [Swift]LeetCode894. 所有可能的满二叉树 | All Possible Full Binary Trees
A full binary tree is a binary tree where each node has exactly 0 or 2 children. Return a list of al ...
- python之zipfile
1 简述 zip文件是一个常用的归档和与压缩标准. zipfile模块提供了创建.读取.写入.添加及列出zip文件的工具. zipfile里有2个非常常用的class,分别是Zipfile和ZipIn ...
- spark计算两个DataFrame的差集、交集、合集
spark 计算两个dataframe 的差集.交集.合集,只选择某一列来对比比较好.新建两个 dataframe : import org.apache.spark.{SparkConf, Spar ...
- 【Redis篇】Redis持久化方式AOF和RDB
一.前述 持久化概念:将数据从掉电易失的内存存放到能够永久存储的设备上. Redis持久化方式RDB(Redis DB) hdfs: fsimageAOF(AppendOnlyFile) ...
- slice全解析
slice全解析 昨天组内小伙伴做分享,给出了这么一段代码: package main import ( "fmt" ) func fun1(x int) { x = x + 1 ...
- PHP 编码规范
这是给小组制定的php编码规范 该 PHP 编码规范基本上是同 PSR 规范的.有一部分的编码规范 PSR 中是建议,此编码规范会强制要求. 此编码规范 是以 PSR-1 / PSR-2 / PSR- ...
- Visual Studio 2017中使用Libman管理客户端库
什么是Libman 微软在Visual Studio 2017 15.8版本中内嵌了一个新的工具Library Manager. Library Manager(简称Libman)是一个客户端库管理工 ...
- Spring Cloud Eureka基本概述
记一次Eureka的进一步学习. 一.Eureka简介 百科描述:Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡 ...
- 补习系列(4)-springboot 参数校验详解
目录 目标 一.PathVariable 校验 二.方法参数校验 三.表单对象校验 四.RequestBody 校验 五.自定义校验规则 六.异常拦截器 参考文档 目标 对于几种常见的入参方式,了解如 ...