串口不是文件,不能用fopen直接读取:linux需open+ioctl+tcsetattr配置termios(禁用icanon/echo、设波特率等),windows需createfile+setcommstate+readfile并设超时;必须按协议解析字节流,处理粘包、超时与缓冲区管理。

串口不是文件,不能用 fopen 直接读取
很多人一上来就尝试用 fopen("/dev/ttyUSB0", "r") 或 fopen("COM3", "rb") 打开串口当文件读,结果要么阻塞、要么读不到数据、要么乱码。串口是字符设备(Linux)或通信端口(Windows),需按通信协议配置后收发,不是普通文件流。
关键点在于:必须先设置波特率、数据位、停止位、校验等参数,再以字节流方式读取,且要处理粘包、超时、缓冲区溢出等问题。
- Linux 下用
open()+ioctl()+tcsetattr()配置,不能只靠read() - Windows 下用
CreateFile()+SetCommState()+ReadFile(),不能用 CRT 文件函数 - 串口没有“文件结束符”,
read()不会自动返回 EOF,必须靠超时或应用层协议界定数据边界
Linux 下用 termios 读取原始字节流
核心是正确初始化 struct termios:禁用回显、输入处理、流控,设为原始模式(ICANON 关闭、ECHO 关闭),并配置 c_cflag 中的波特率(如 B115200)、CSTOPB、PARENB 等。
常见错误是漏掉 cfmakeraw() 后又手动改了不该动的标志位,或者没调 tcflush(fd, TCIOFLUSH) 清空残留缓冲区。
- 用
read(fd, buf, sizeof(buf)-1)读取,返回值是实际字节数,可能小于请求长度 - 设
VMIN=0+VTIME=1实现非阻塞轮询(单位为 0.1 秒),避免死等 - 每次
read()前检查errno == EAGAIN或返回 0,判断是否无数据可读 - 接收大块二进制数据时,务必用循环
read()并拼接,不能假定一次调用读完
Windows 下用 ReadFile 处理串口异步读取
ReadFile() 在串口句柄上默认同步阻塞,但真实场景中必须配合超时(SetCommTimeouts())或重叠 I/O(OVERLAPPED)。否则一个丢包或断线就会卡死整个线程。
容易被忽略的是:Windows 串口驱动内部有两级缓冲——硬件 FIFO 和系统缓冲区。若不调 SetupComm() 显式设 dwReceiveBufferSize,默认可能只有 1024 字节,大数据传输时极易丢帧。
- 调
GetCommState()后修改DCB.BaudRate、DCB.ByteSize等字段,再SetCommState() -
COMMTIMEOUTS.ReadIntervalTimeout = MAXDWORD表示忽略字符间间隔,只看总超时 - 用
WaitForSingleObject(hEvent, timeout_ms)配合重叠读,比轮询更可靠 - 每次
ReadFile()返回成功后,务必检查*lpNumberOfBytesRead是否为 0 —— 这表示超时而非错误
如何安全还原“文件数据”?关键在协议层
串口本身不带文件头、长度、校验信息。所谓“读取文件数据”,实际是接收方按预定义协议解析字节流:比如开头 4 字节是文件长度(小端),接着是 CRC32 校验,再后面才是原始内容。没有协议,你拿到的只是一堆无法判定边界的二进制碎片。
别指望靠 read() 自动分包。TCP 有滑动窗口,UDP 有包界,串口没有。所有分帧逻辑必须自己写:检测起始标记(如 0x02)、等待固定长度、或识别结束标记(如 0x03)。
- 接收缓冲区建议至少 64KB,避免因处理慢导致后续数据被丢弃
- 用环形缓冲区(ring buffer)管理未解析数据,避免内存拷贝和越界
- 每次从环形缓冲区中尝试提取一个完整帧,失败则继续等待,不要强行截断
- 对每个完整帧做 CRC 或 SHA256 校验,确认后再写入磁盘,否则传错一个字节,解压/执行就失败
最麻烦的不是读,而是判断“这一段到底是不是完整的文件数据”。这一步没做好,后面所有解析都是徒劳。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











