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 ...
随机推荐
- HTTP长连接和短连接 + Websocket
HTTP协议与TCP/IP协议的关系 HTTP的长连接和短连接本质上是TCP长连接和短连接.HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议.IP协议主要解决网络路由和寻址问题,T ...
- CSS3 阴影模拟灯照效果
效果如下: 代码如下: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset= ...
- VsCode 使用专用编程字体FiraCode
FiraCode资料:https://github.com/tonsky/FiraCode PHP代码效果如下: VsCode 配置中添加: "editor.fontFamily" ...
- [Swift]LeetCode495. 提莫攻击 | Teemo Attacking
In LOL world, there is a hero called Teemo and his attacking can make his enemy Ashe be in poisoned ...
- JVM基础系列第7讲:JVM 类加载机制
当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析.运行等整个过程,这个过程我们叫:Java 虚拟机的类加载机制.JVM 虚拟机执行 class 字节 ...
- c#进程、定时器初步学习
首先是什么原因让我做这个小项目的呢,是因为在知乎里看到的游侠的文章才尝试着自己做的,文章地址是:https://www.zhihu.com/question/48811975 开始做的时候我是照着文章 ...
- MySQL系列--2.常用的命令
1 .创建数据库 #语法: CREATE DATABASE dbName; #创建数据库rms create database rms; 2.切换数据库 #选择数据库 USE dbName; #选择数 ...
- 不在models.py中的models
概述 如何让你定义的model不在models.py中 在app的models目录中的models 你新建一个app后这个models.py就会自动建立,里面只有几行代码.那么如果是一个中大型项目,每 ...
- 贝叶斯个性化排序(BPR)算法小结
在矩阵分解在协同过滤推荐算法中的应用中,我们讨论过像funkSVD之类的矩阵分解方法如何用于推荐.今天我们讲另一种在实际产品中用的比较多的推荐算法:贝叶斯个性化排序(Bayesian Personal ...
- 全文检索-Elasticsearch (一) 安装与基础概念
ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口 Elasticsearch由java开发,所以在搭建时,需先安 ...