为什么我的缓冲区长度被忽略了?

10

我正在为一个小型硬件项目开发接收器。我正在研究一个使用UART传输数据的小型板子。
下面是完整的接收器代码,稍后我将逐个解释问题所在。

#define TTY "/dev/ttys002"

#include <stdio.h>
#include <string.h>
#include <unistd.h> //Unix standard functions
#include <fcntl.h> //File controls
#include <errno.h> //Error numbers
#include <assert.h>
#include <termios.h> //Posix terminal

int open_port(const char * tty) {
    int fd;
    fd = open(tty, (O_RDWR | O_NOCTTY | O_NDELAY));
    assert("__failed to open port__" && fd != -1);
    //Block until read
    fcntl(fd, F_SETFL, 0);
    return fd;
}

void configure_port(int fd) {
    struct termios options;
    //Get current options
    tcgetattr(fd, &options);

    //9600 Baud
    cfsetispeed(&options, B9600);
    cfsetospeed(&options, B9600);

    //Receive & local mode
    options.c_cflag |= (CLOCAL | CREAD);

    //Raw output
    options.c_oflag &= ~OPOST;

    //No hardware flow control
    options.c_cflag &= ~CRTSCTS;

    //No parity, 1 stop bit
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;

    //8 data bits
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

    //Write options back, set them immediately
    tcsetattr(fd, TCSANOW, &options);
}

int main(int argc, const char * argv[]) {
    int fd = open_port(TTY);
    const size_t count = 8;
    char buf[count + 1];
    ssize_t n;
    configure_port(fd);
    while (1) {
        n = read(fd, buf, count);
        buf[count] = '\0';
        if (n > 0) {
            printf("%s\n", buf);
        }
    }
    return 0;
}

由于我目前手头没有我的硬件,所以我决定在常规tty上测试我的接收器(#define TTY "/dev/ttys002")。为了测试它,我只需编译并运行上述代码,然后打开一个单独的终端并执行以下操作:

echo "text" >> /dev/ttys002

所有这些工作都很好,并且我获取了所有要回显到tty的数据。

然而,当我将长消息输入到tty中时,问题就出现了:

echo "this is a longer test message" >> /dev/ttys002

在我的程序输出中,我将整个消息作为一个单独的字符串接收到。为什么会这样?我本来希望文本被分成8个字符一块(const size_t count = 8;)。

如果重要的话,我正在使用这个指南进行配置。

编辑:请参见评论以进一步讨论此问题。


1
它不能正常工作,除非在某个点(可能不止一个)调用了未定义行为。例如,在打印之前,您没有将终止空值添加到读取的字符串中;此外,逻辑推断长文本必须存储在某个地方,即使它必须覆盖程序的某些部分或先前存储的数据。因此,我认为您的“它似乎可以工作,但为什么”只是这种UB的症状。 - Jongware
1
我将 printf("%s\n", buf); 改为 printf("[%s]\n", buf);,但它仅打印了原始字符串。可能是我的测试设置有问题,但请尝试一下,因为如果您得到相同的结果,则您的起始前提是错误的。 - Jongware
1
有一件事是确定的:您的文本永远不会出现在 buf 中。我刚刚删除了 while 循环内的所有内容,只留下 while (1) {} ... 这并没有产生任何影响。 - Jongware
1
我尝试了加上和不加上Serge的ICANON,得到了一些令人惊讶的结果(事后想想,这与ICANON设置无关)。添加一行printf ("%ld bytes received\n", n);并重新启动。打开另一个终端并启动man termios。现在,一些键入到man终端中的字符将被回显到您的陷阱中!这需要一个终端专家。 - Jongware
1
是的,看起来整个问题都与在ttys上进行测试有关,但我并不是很熟悉如何进一步研究它,直到我再次拥有实际硬件。 - Etheryte
显示剩余8条评论
3个回答

1

在我看来,你的信息分成了8个字符一组:n = read(fd, buf, count);每次最多只能读取count字节。

但由于你没有将tty行配置为RAW模式,因此它仍处于行缓冲模式。所以底层驱动程序会阻塞第一次读取,直到它有一个以\n(或超过缓冲区容量)结尾的完整行。

然后读取返回前8个字节,接下来的读取立即返回8个字节,因为数据在驱动程序缓冲区中可用。

如果您想使用原始输入模式,则应查看man termios中的非规范模式

options.c_cflags &= ~ICANON;
/* optionally if defaults are not appropriates */
options.c_cc[VMIN] = vmin; /* default is 1 */
options.c_cc[VTIME] = vtime; /* default is 0 */

但是,无论如何,您都会读取字符并且从不添加终止null,因此buf没有理由以null结尾。 您应该仅打印实际读取的字符数:

const size_t count = 8;
char buf[count];
ssize_t n;
configure_port(fd);
while (1) {
    n = read(fd, buf, count);
    if (n > 0) {
        printf("%.*s\n", n, buf); /* only print the n characters */
    }
}

2
好的,但是 printf("%s\n", buf); 必须将字符串分成不同行,每行长度为8个字符。这样正确吗?问题在于空终止符。 - LPs

0
你所需要的是一个伪终端(PTY)。
创建 PTY 的简单方法是使用 socat(1) 命令,如下所示:
socat pty,link=/tmp/testpty stdio

这将创建一个 PTY 并将当前终端的 stdio 附加到其主端。无论您在此处放入什么数据,都将发送到 PTY 的从端。

现在从您的程序连接到从端,它应该按照您的期望工作:

#define TTY "/tmp/testpty"

0

看起来你的问题和这个网站的名字一样 :)

你在栈上为你的buf变量分配了8个字节的数据。然后,在read函数中,你写入了长度远大于8个字节的字符串"this is a longer test message"。当你使用带有"%s"的printf函数时,printf会在遇到空字符(\0)时停止打印,因为它期望你的字符串以空字符结尾。 在你的情况下,你应该检查你的buf变量是否以空字符结尾。如果没有,你应该将buf的最后一个字节设置为空字符。


1
不是一个好的解决方案!!如果恰好有8个字符,您将丢弃最后一个字符! - Jongware
1
“read”命令的手册并没有说明如何将其与字符串一起使用,因为它本来就不应该这样使用。 - Jongware
2
在C语言中,创建一个长度为8的字符数组并写入8个字节的数据是不好的编程习惯。如果你想要打印出恰好8个字符,应该在栈上分配9个字节的空间 - 这是处理字符串的常规规则。 - floyd
2
你可以根据自己的需要选择使用长度为n字节的数组或长度为n+1字节的数组来存储n字节的文本。但是在第一种情况下,你必须(a)提前将数组清零(例如使用memset()),或者(b)在读取m个字符后清零第m个字节,其中mread()函数的返回值(读取的字符数)。而在第二种情况下,你必须只使用m个字节,不要假设字符串以NUL结尾。实际上,另一个答案正确地给出了第二种方法。 - Pavel Šimerda
1
@DavidSchwartz 正如我之前所说,我已经从代码中删除了大小问题。问题在于我的程序将整个输入字符串作为输出接收。你没有为这个讨论添加任何有价值的东西。 - Etheryte
显示剩余12条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接