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 ...
随机推荐
- 【RL-TCPnet网络教程】第32章 RL-TCPnet之Telnet服务器
第32章 RL-TCPnet之Telnet服务器 本章节为大家讲解RL-TCPnet的Telnet应用,学习本章节前,务必要优先学习第31章的Telnet基础知识.有了这些基础知识之后,再搞 ...
- FFmpeg 结构体学习(六): AVCodecContext 分析
在上文FFmpeg 结构体学习(五): AVCodec 分析我们学习了AVCodec结构体的相关内容.本文,我们将讲述一下AVCodecContext. AVCodecContext是包含变量较多的结 ...
- java中的堆,栈和方法区(转)
来源:https://www.cnblogs.com/iliuyuet/p/5603618.html https://blog.csdn.net/lin542405822/article/detail ...
- [Swift]LeetCode636. 函数的独占时间 | Exclusive Time of Functions
Given the running logs of n functions that are executed in a nonpreemptive single threaded CPU, find ...
- 如何为自己的pip包打造可以执行的系统命令
1.我们在打包我们自己的Python Package的时候.我们不仅可以在代码中使用我们的package,而且可以添加一些可执行命令来执行自己的函数. 2 .我们应该怎么办呢? 1.首先新建目录以及文 ...
- Linux查看系统、核数、CPU、位数
查看系统: cat /etc/os-release 结果为 centOS Linux 7 查看核数和CPU: lscpu 40 个核,处理器为 Intel(R) Xeon(R) CPU E7-8891 ...
- 手把手的教你安装PyCharm --Pycharm安装详细教程(一)(非常详细,非常实用)
简介 Jetbrains家族和Pycharm版本划分: pycharm是Jetbrains家族中的一个明星产品,Jetbrains开发了许多好用的编辑器,包括Java编辑器(IntelliJ IDEA ...
- js的异步和单线程
最近,同事之间做技术分享的时候提到了一个问题"js的异步是另开一个线程吗?"当时为此争论不休.会后自己查阅了一些资料,对这个问题进行一个自我的分析与总结,有不同意见的希望可以赐教, ...
- qt系统托盘显示、无主窗体
系统图盘是应用程序经常用到的一个控件,当应用程序需要长时间存在的时候,这个控件会变得非常有用,比如,窗口隐藏,显示,关于.关闭等接口都可以放在图盘中处理,今天与到一个问题,需求是这样的:只需要显示图盘 ...
- Android--MediaRecorder录音录像
前言 Android除了支持播放多媒体文件之外,还可以从对应的硬件中捕获多媒体,比如从麦克风录音.从摄像头录像等.本篇博客讲解一下Android下如何通过MediaRecorder进行录音以及录像的步 ...