简介这份资源是面向工业自动化与上位机开发者的扫码枪网口TCP通讯示例工程针对基恩士扫码枪设计解决计算机与扫码枪之间稳定数据交互的问题。项目以计算机作为服务端、扫码枪作为客户端涵盖连接建立、指令下发触发扫码、条码数据回传等流程并封装了通用的TCP连接管理基类便于继承扩展数据解析与业务逻辑异步重连机制可在网络中断后自动恢复连接提升工业现场通讯的健壮性。压缩包共51个文件约291KB以C#源码文件为主辅以项目工程配置、资源文件及少量可执行与动态库文件结构完整、可直接运行测试。目前已有3999人学习下载适合需要快速实现扫码数据实时采集或参考TCP通讯与断线重连实现的开发者借鉴。1. 扫码枪网口TCP通讯demo从一根网线到稳定收码的落地实践产线上新到一批网口扫码枪插上网线、配好 IP电脑端却迟迟收不到条码数据——这种场景在仓储、分拣、装配线上太常见了。扫码枪走网口 TCP 通讯本质是把枪当成一个 TCP 服务端或客户端上位机连上去后持续读取它推送的条码字符串。这份 demo 及源码要解决的就是这件事把「扫码枪 → 网线 → 上位机」这条链路跑通给出可复用的连接、收码、断线重连骨架。适合正在做产线采集、WMS 对接、工位终端的开发者尤其是第一次接触网口扫码枪、被 IP 和端口绕晕的人。下面按「它是什么 → 怎么跑起来 → 核心代码怎么读 → 怎么改」的顺序拆。2. 项目定位与技术栈一个最小可用的 TCP 收码骨架2.1 这个 demo 到底在做什么网口扫码枪和 USB 扫码枪最大的区别在于数据出口。USB 枪模拟键盘输入扫一下等于敲了一串字符网口枪则把条码通过 TCP 报文发出去上位机必须主动建立连接、持续监听。这份 demo 的核心工作就三件建立 TCP 连接、循环读取字节流、把字节流还原成一条条完整条码。它不涉及数据库、不涉及界面美化是一个纯粹的通讯层样例方便你直接嵌进自己的采集程序里。从工程角度看它更像一块「通讯积木」。真实项目里你会在它上面加日志、加去重、加业务回调、加断线告警但底层那套 socket 连接和粘包处理逻辑基本就是这份源码里的样子。所以读它的正确姿势不是当成成品软件而是当成一个可以拆开改的起点。2.2 技术栈与运行环境源码走的是最朴素的 TCP Socket 路线没有引入第三方通讯框架。这种选择的理由很实际扫码枪协议简单就是纯文本或带结束符的字节流上 MQTT、上 WebSocket 反而增加部署负担。常见做法是用语言自带的 socket 库直接开干Windows 下用 .NET 的TcpClient跨平台或脚本场景用 Python 的socket模块C 则直接调 Winsock 或 POSIX socket。层面选型说明通讯协议TCP扫码枪网口模式的标准出口连接角色客户端为主上位机主动连扫码枪的监听端口数据形态字节流 结束符常见为\r\n、\r或固定长度运行环境Windows / Linux 均可取决于你选的实现语言依赖标准库 socket无额外第三方包需要提醒的是不同品牌扫码枪出厂默认角色不一样。有的枪是 TCP Server等你来连有的枪是 TCP Client配置好目标 IP 后主动往上位机推。demo 里通常两种模式都留了入口跑之前先翻一眼枪的配置手册确认它到底是「等连」还是「去连」这一步搞反了后面代码再对也收不到数据。2.3 目录结构与模块划分一份典型的网口扫码枪 demo 源码目录不会太深大致是这么组织的scanner-tcp-demo/ ├── config/ # 枪的 IP、端口、结束符配置 │ └── scanner.json ├── src/ │ ├── connection.* # TCP 连接建立与重连 │ ├── receiver.* # 字节流读取与粘包拆分 │ ├── parser.* # 条码解析与校验 │ └── main.* # 启动入口、回调注册 ├── logs/ # 运行日志输出 └── README.md # 配置与运行说明connection负责 socket 生命周期receiver负责把 recv 到的碎片拼成完整帧parser负责去掉结束符、做长度或前缀校验main把三者串起来并注册「收到一条码」的回调。这种分层的好处是换枪型只改 config换解析规则只动 parser连接逻辑几乎不用碰。二次开发时优先在这三个边界上做扩展别把业务代码塞进 receiver 里否则后面粘包一出问题排查范围会失控。3. 跑起来从配置 IP 到收到第一条码3.1 扫码枪侧的配置要点动手写代码前先把枪配明白。网口扫码枪一般通过扫描配置码或配套工具设置网络参数关键项有四个IP 地址、子网掩码、端口号、工作模式。IP 要和上位机在同一网段比如上位机是192.168.1.10枪配192.168.1.20端口常见默认9004、5000、8000不等以手册为准。工作模式选 Server 还是 Client决定了你代码里是connect还是listen。配置完先别急着写程序用系统自带工具验一下链路。Windows 下开命令行 ping 一下枪的 IP通了说明物理层和网络层没问题再用 telnet 或 nc 连一下端口能连上并看到扫码头数据说明枪已经在正常推流。这一步是血泪经验很多人代码调半天最后发现是枪根本没配好或者网线接在错误的网口上。# 先确认网络可达 ping 192.168.1.20 # 再确认端口在监听Linux/macOS 用 ncWindows 可用 telnet nc -v 192.168.1.20 9004ping通只代表 IP 层可达不代表端口开着nc能连上并打印出扫码内容才说明 TCP 服务真的在跑。如果nc连不上回头查枪的模式和端口别在代码里找原因。3.2 最小可运行代码建立连接并收码下面这段 Python 实现是 demo 里最核心的骨架负责连上扫码枪并循环读取。选 Python 是因为它把 socket 细节藏得少读起来直观换成其他语言逻辑完全一致。import socket HOST 192.168.1.20 # 扫码枪 IP PORT 9004 # 扫码枪监听端口 TERMINATOR b\r\n # 条码结束符按枪的实际配置改 def run(): # 创建 TCP socket超时设 5 秒避免永久阻塞 sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) sock.connect((HOST, PORT)) print(connected) buffer b while True: try: chunk sock.recv(1024) # 一次最多读 1024 字节 except socket.timeout: continue # 超时不算错继续等 if not chunk: print(peer closed) break # 对端关闭退出循环 buffer chunk # 按结束符切分处理一次收到多条或半条的情况 while TERMINATOR in buffer: line, buffer buffer.split(TERMINATOR, 1) if line: print(barcode:, line.decode(ascii, errorsignore)) if __name__ __main__: run()逐段说明socket.socket(AF_INET, SOCK_STREAM)创建的是 IPv4 的 TCP 套接字settimeout(5)让recv不会无限挂起产线上偶尔网络抖动时程序还能继续跑connect是客户端模式对应枪配成 Server 的情况。循环里recv(1024)的 1024 是单次读取上限条码通常几十字节够用返回空字节串代表对端关闭连接必须break否则会死循环空转。最关键的是buffer那段。TCP 是字节流协议没有消息边界一次recv可能拿到半条码也可能一次拿到三条码拼在一起这就是常说的粘包。用结束符切分是处理它的标准手法把新数据追加到缓冲区只要缓冲区里出现结束符就切一条出来剩下的留着等下次。split(TERMINATOR, 1)的第二个参数1表示只切一刀保证一次只取一条循环直到缓冲区里没有结束符为止。3.3 换成服务端模式枪主动来连如果枪被配成 Client角色就反过来了上位机要bind端口并listen等枪连进来。代码结构变化不大只是连接建立部分不同import socket LISTEN_PORT 9004 TERMINATOR b\r\n def serve(): server socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 端口复用避免重启报占用 server.bind((0.0.0.0, LISTEN_PORT)) # 监听所有网卡 server.listen(1) print(listening on, LISTEN_PORT) conn, addr server.accept() # 阻塞等待扫码枪接入 print(scanner connected:, addr) buffer b while True: chunk conn.recv(1024) if not chunk: break buffer chunk while TERMINATOR in buffer: line, buffer buffer.split(TERMINATOR, 1) if line: print(barcode:, line.decode(ascii, errorsignore)) if __name__ __main__: serve()SO_REUSEADDR这行是踩坑换来的程序重启时旧连接可能还处于 TIME_WAIT不加这行bind会报「地址已被占用」。bind((0.0.0.0, PORT))表示监听本机所有网卡如果只想收某个网段可以换成具体 IP。accept()是阻塞的枪没连进来之前程序停在这里这是正常行为不是卡死。实际产线里往往有多把枪那就把accept放进循环每来一个连接开一个线程或协程处理别用单连接硬扛。4. 核心模块拆解连接、收码、解析三段链路4.1 连接管理断线重连不能省demo 里最容易被忽略、上线后最容易翻车的就是重连。产线网络不是实验室交换机重启、枪断电、网线松动都会导致连接断开如果程序只连一次断了就彻底哑了。合格的做法是把「连接」包成一个可重试的函数外层套一个带退避的循环。import time import socket def connect_with_retry(host, port, max_backoff30): backoff 1 while True: try: sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) sock.connect((host, port)) print(connected to, host, port) return sock except (socket.timeout, ConnectionRefusedError, OSError) as e: print(connect failed:, e, retry in, backoff, s) time.sleep(backoff) backoff min(backoff * 2, max_backoff) # 指数退避封顶 30 秒backoff从 1 秒开始翻倍封顶 30 秒避免枪长时间离线时程序疯狂重连把日志刷爆。捕获的异常里ConnectionRefusedError表示端口没开socket.timeout表示网络不通或对端没响应OSError兜底其他网络错误。返回的sock交给收码循环用收码循环里一旦recv返回空或抛异常就跳出重新走这个函数。这套「连接-收码-断开-重连」的闭环是网口采集程序能长期稳定运行的基础。4.2 收码与粘包处理结束符是生命线粘包处理前面已经露了一手这里把它讲透。TCP 只保证字节顺序不保证消息边界扫码枪连续扫两下两个条码可能在网络层被合并成一个报文也可能被拆成两个。判断一条码是否完整的唯一依据就是枪配置的结束符。常见结束符有\r\nCRLF、\rCR、\nLF也有枪用固定长度或特殊前缀。结束符必须和枪的实际配置严格一致差一个字节切分就永远对不上。def feed(buffer: bytes, chunk: bytes, terminator: bytes): 把新数据喂进缓冲区返回本次切出的完整条码列表 buffer chunk frames [] while terminator in buffer: line, buffer buffer.split(terminator, 1) if line: frames.append(line) return buffer, frames这个feed函数把「追加 切分」抽成纯函数好处是可测试你可以构造bufferbABC\r\nDE、chunkbF\r\n断言切出[bABC, bDEF]、剩余为空。二次开发时如果枪的结束符不是标准换行只改terminator参数即可切分逻辑不用动。注意if line:这层过滤是为了丢掉连续结束符产生的空帧否则业务层会收到空条码。4.3 解析与业务回调把字节变成可用数据切出来的还是字节串要转成字符串、做校验、再交给业务。解析层通常做三件事编码转换、去空白、合法性校验。条码一般是 ASCII但少数场景含中文或特殊字符编码用ascii加errorsignore比较稳避免一条脏数据把整个循环搞崩。def parse_frame(raw: bytes) - str | None: text raw.decode(ascii, errorsignore).strip() if not text: return None if len(text) 4 or len(text) 128: # 长度兜底过滤明显异常 return None return text def on_barcode(code: str): # 业务回调入库、比对、打印按项目需要替换 print(got barcode:, code)strip()去掉可能残留的空白长度区间4~128是经验值太短可能是噪声太长可能是粘包没切干净。on_barcode就是业务入口真实项目里这里会写数据库、发消息、触发分拣动作。把解析和业务分开的意义在于解析规则变了不影响业务业务逻辑改了也不碰通讯层职责清晰出问题好定位。5. 避坑与排查网口扫码枪最常见的五个坑5.1 现象程序连上了但一条数据都收不到原因通常是枪的工作模式配反了。枪配成 Server你代码也listen两边都在等对方连自然没数据。或者枪配成 Client 但目标 IP 填错连到了别的机器。解决方法是先用nc或 telnet 直连枪的端口确认枪在主动推流再核对代码里是connect还是accept和枪的模式一一对应。这一步别偷懒模式对不上代码写得再漂亮也是空转。5.2 现象条码被截断或者两条码粘成一条原因几乎都是结束符不匹配或粘包没处理。枪发的是\r\n代码按\n切切出来的每条都带个\r或者代码根本没做缓冲区累积每次recv直接当一条码用遇到拆包就截断。解决办法是抓一次原始字节流用十六进制打印出来看结尾到底是什么再把terminator改成一致的值并确保走feed那种「累积 循环切分」的逻辑而不是一次recv当一帧。5.3 现象程序跑几小时就卡死不再收码原因是recv没有超时或者异常没捕获某次网络抖动后线程永久阻塞。表现是进程还在、CPU 不高、就是不进新数据。解决办法是给 socket 设settimeout把recv包在try/except里超时continue、异常跳出重连。另外检查是不是在收码循环里做了耗时操作比如同步写数据库把业务处理挪到队列或独立线程别堵住收码主循环。5.4 现象重启程序报「地址已被占用」原因是用服务端模式时没设端口复用旧连接处于 TIME_WAIT 状态占着端口。解决办法是在bind之前加setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)。这个坑在调试阶段特别烦每次改完代码重启都报错加一行就解决。注意这行要在bind之前调用顺序错了不生效。5.5 现象多把枪接进来后数据串台原因是服务端只accept了一次或者多连接共用了同一个buffer变量。每把枪是独立的 TCP 连接必须各自维护自己的缓冲区和解析状态。解决办法是把「连接 缓冲区 收码循环」封装成一个连接对象accept到新连接就新建一个实例丢进线程池。共享缓冲区会导致 A 枪的半条码和 B 枪的数据拼在一起这种串台在产线上极难排查一开始就按连接隔离状态最省心。6. 二次开发把 demo 改成能上产线的采集服务拿到这份源码多数人不会原样用而是把它嵌进自己的采集系统。改造的第一刀建议切在「配置外置」上把 IP、端口、结束符、重连间隔从代码里挪到配置文件换枪型时只改配置不改代码。第二刀切在「状态可观测」加连接状态、收码计数、最后收码时间这几个指标产线出问题时一眼能看出是断连还是没扫码。第三刀切在「业务解耦」收码回调里只做入队真正的入库、比对、告警交给独立消费者避免业务慢拖垮通讯。验证改造是否到位我一般用三个动作拔网线看是否自动重连并恢复收码连续快速扫 100 次看是否有丢码或粘包模拟枪断电再上电看程序是否无需人工干预。这三关过了基本能扛住产线日常。下面这个改造后的结构可以作为参考class ScannerClient: def __init__(self, host, port, terminator, on_code): self.host, self.port host, port self.terminator terminator self.on_code on_code self.buffer b self.count 0 def run_forever(self): while True: sock connect_with_retry(self.host, self.port) try: while True: chunk sock.recv(1024) if not chunk: break self.buffer, frames feed(self.buffer, chunk, self.terminator) for raw in frames: code parse_frame(raw) if code: self.count 1 self.on_code(code) # 业务回调建议只入队 except OSError as e: print(recv error:, e) finally: sock.close()这个类把连接、缓冲、计数、回调收拢在一起run_forever外层是重连循环内层是收码循环finally保证 socket 一定关闭。on_code传进来的是业务函数通讯层不关心它干什么。多枪场景就 new 多个实例、各跑各的线程状态天然隔离。有个细节值得单独说self.count这类计数别只放内存产线排查时经常需要知道「今天收了多少码、最后一次是什么时候」定期落盘或暴露成接口比事后翻日志快得多。另外结束符如果枪支持配置尽量在枪侧统一成\r\n别让不同批次的枪用不同结束符否则配置管理会变成噩梦。从那以后我每次接网口扫码枪都强制先跑一遍nc直连验证枪在推流再动代码——这个习惯帮我省掉了至少一半「代码没问题但收不到数据」的排查时间。希望帮到你。本文还有配套的精品资源点击获取