如何用socat伪终端实现两个C程序间的数据收发?
伪终端(pty)C程序收发数据失败问题排查与解决
问题背景
通过socat创建伪终端对用于测试,后续将对接RS232 USB适配器:
sudo socat -d -d pty,link=./ttyUSB0,echo=0,perm=0666 pty,link=./ttyUSB1,echo=0,perm=0666
运行自行编写的C发送端和接收端程序时,接收端在首次read()调用时阻塞,发送端看似正常执行但接收端无法收到数据。但使用cat ./ttyUSB1和echo abcd >> ./ttyUSB0测试时,数据能正常收发,说明问题出在C程序中。
发送端代码
#define _GNU_SOURCE #include <fcntl.h> #include <errno.h> #include <sys/types.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <termios.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> float data[100000]; int main() { int fdm = -1; fdm = open("./ttyUSB0",O_RDWR); if(fdm < 0) { perror("Error"); exit(EXIT_FAILURE); } grantpt(fdm); unlockpt(fdm); int i; for(i=0; i<100000; i++) { if(i%100 == 0) { printf("\rTransmit %d / %d",i,100000); fflush(stdout); } write(fdm,&data[i],sizeof(float)); } }
接收端代码
#define _GNU_SOURCE #include <fcntl.h> #include <errno.h> #include <sys/types.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <termios.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> float data[100000]; int main() { int fds = open("./ttyUSB1",O_RDWR); if(fds < 0) { perror("Error"); exit(EXIT_FAILURE); } grantpt(fds); unlockpt(fds); int i; for(i=0; i<100000; i++) { if(i%100 == 0) { printf("\rReceive %d / %d",i,100000); fflush(stdout); } read(fds,&data[i],sizeof(float)); } }
问题原因与修复
1. 错误调用grantpt和unlockpt
grantpt()和unlockpt()仅用于主伪终端(master pty)的初始化,而socat创建的./ttyUSB0和./ttyUSB1都是从伪终端(slave pty)。对slave pty调用这两个函数完全无效,还可能干扰终端的正常工作流程,直接删除这两行即可。
2. 未配置终端为原始模式
默认终端属性启用了行缓冲和特殊字符处理(比如换行符、退格键解析),导致二进制数据不会立即被传输,而是被缓冲直到触发换行或缓冲满。需要将终端配置为原始模式,禁用所有缓冲和特殊字符解析,确保数据能直接收发。
添加通用的终端配置函数:
void set_raw_mode(int fd) { struct termios term; if (tcgetattr(fd, &term) == -1) { perror("tcgetattr failed"); exit(EXIT_FAILURE); } cfmakeraw(&term); // 设置为原始模式,禁用缓冲和特殊字符处理 if (tcsetattr(fd, TCSANOW, &term) == -1) { perror("tcsetattr failed"); exit(EXIT_FAILURE); } }
修改后的发送端代码
#define _GNU_SOURCE #include <fcntl.h> #include <errno.h> #include <sys/types.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <termios.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> float data[100000]; void set_raw_mode(int fd) { struct termios term; if (tcgetattr(fd, &term) == -1) { perror("tcgetattr failed"); exit(EXIT_FAILURE); } cfmakeraw(&term); if (tcsetattr(fd, TCSANOW, &term) == -1) { perror("tcsetattr failed"); exit(EXIT_FAILURE); } } int main() { int fdm = -1; fdm = open("./ttyUSB0",O_RDWR); if(fdm < 0) { perror("Error opening ttyUSB0"); exit(EXIT_FAILURE); } set_raw_mode(fdm); int i; for(i=0; i<100000; i++) { if(i%100 == 0) { printf("\rTransmit %d / %d",i,100000); fflush(stdout); } ssize_t written = write(fdm,&data[i],sizeof(float)); if(written != sizeof(float)) { perror("Write failed"); break; } } close(fdm); printf("\nTransmission completed\n"); return 0; }
修改后的接收端代码
#define _GNU_SOURCE #include <fcntl.h> #include <errno.h> #include <sys/types.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <termios.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> float data[100000]; void set_raw_mode(int fd) { struct termios term; if (tcgetattr(fd, &term) == -1) { perror("tcgetattr failed"); exit(EXIT_FAILURE); } cfmakeraw(&term); if (tcsetattr(fd, TCSANOW, &term) == -1) { perror("tcsetattr failed"); exit(EXIT_FAILURE); } } int main() { int fds = open("./ttyUSB1",O_RDWR); if(fds < 0) { perror("Error opening ttyUSB1"); exit(EXIT_FAILURE); } set_raw_mode(fds); int i; for(i=0; i<100000; i++) { if(i%100 == 0) { printf("\rReceive %d / %d",i,100000); fflush(stdout); } ssize_t read_bytes = read(fds,&data[i],sizeof(float)); if(read_bytes != sizeof(float)) { perror("Read failed"); break; } } close(fds); printf("\nReception completed\n"); return 0; }
3. 额外优化建议
- 始终检查
read()和write()的返回值,确保数据完整传输,避免静默失败。 - 程序结束时关闭文件描述符,释放系统资源。
- 后续对接真实RS232设备时,需添加波特率、奇偶校验、停止位等参数配置,示例:
cfsetispeed(&term, B9600); cfsetospeed(&term, B9600); term.c_cflag |= (CS8 | CLOCAL | CREAD); // 8位数据位,忽略调制解调器状态,启用接收
内容的提问来源于stack exchange,提问作者Bertrand125
相关产品推荐
相关产品推荐

