非阻塞模式下libev监听ttyUSB0读取回调需满100字节才触发的问题
问题分析与解决
看起来你遇到的核心问题是:虽然用O_NONBLOCK打开了串口,但串口的终端属性设置覆盖了非阻塞行为,导致read()必须凑够100字节才返回,哪怕libev已经触发了可读回调。
为什么会出现这种情况?
串口设备默认工作在规范模式(Canonical Mode),或者被设置了VMIN和VTIME参数:
- 如果
c_cc[VMIN] = 100且c_cc[VTIME] = 0,终端驱动会一直缓存数据,直到攒够100字节才允许read()返回——这和文件描述符的非阻塞属性无关,因为这是终端设备层的行为,不是文件系统的阻塞控制。 - 你直接调用
read()时可能刚好测试场景凑够了数据,或者终端属性还没被其他逻辑修改;但用libev时,回调里的read()会严格遵循终端驱动的读取规则,导致必须等够字节才返回。
解决方法:修改串口终端属性
你需要把串口配置为原始模式(Raw Mode),或者手动调整VMIN和VTIME,让read()有数据就返回,而非等待固定字节数。
下面是具体代码示例,放在打开串口之后、初始化libev之前:
#include <termios.h> #include <unistd.h> #include <errno.h> // 假设fd是打开串口得到的文件描述符 struct termios tty_attr; // 获取当前串口终端属性 if (tcgetattr(fd, &tty_attr) != 0) { perror("Failed to get serial port attributes"); close(fd); return -1; } // 方案1:直接设置为原始模式(禁用行缓冲、回显、信号处理等,最适合串口字节流通信) cfmakeraw(&tty_attr); // 方案2:手动调整VMIN和VTIME(更灵活) // VMIN=1:只要有至少1字节数据,read就返回 // VTIME=0:不设置超时,有数据就返回(若要超时,设为10表示1秒超时) // tty_attr.c_cc[VMIN] = 1; // tty_attr.c_cc[VTIME] = 0; // 应用修改后的属性(TCSANOW表示立即生效) if (tcsetattr(fd, TCSANOW, &tty_attr) != 0) { perror("Failed to set serial port attributes"); close(fd); return -1; }
原理说明
cfmakeraw()会把串口配置成最适合字节流通信的模式:禁用行缓冲、取消回车换行转换、关闭回显等,同时默认将VMIN设为1、VTIME设为0——这样read()只要读到数据就会立刻返回,不管字节数多少。- 手动调整
VMIN和VTIME可以灵活控制读取行为:比如VMIN=0+VTIME=5表示最多等待0.5秒,有数据就返回,没数据也超时返回。
修改之后,你的serial_recv_cb回调里的read()就不会再阻塞等待100字节,而是有多少数据就读多少,libev也能正确触发回调处理每一批到达的数据。
内容的提问来源于stack exchange,提问作者jianxi sun
相关产品推荐
相关产品推荐

