You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 20:17:02