USB 串行端口编程有 "disastrous" 个结果
USB Serial port programming has "disastrous" results
我目前正在 Raspberry Pi 3 (Linux Ubuntu) 上开发 C 程序 运行,旨在提供用于配置网络的网页界面在嵌入式系统上。
正在使用 Code::Blocks 和 GDB 调试器开发代码。我在 Web 服务器上使用 microhttpd,加上各种网页,都运行良好。我现在正在使用 "Serial Programming Guide for POSIX Operating Systems".
中的信息将 USB 串行 link 连接到嵌入式系统
下面的代码负责打开 USB 串行 link 到目标系统并且似乎工作正常 - 一次。如果我关闭程序并重新启动它(在命令行上独立运行或从 Code::Blocks 中重新启动)第二次 microhttpd 被清理 - 浏览器 windows 将不再连接。此外,在 Code::Blocks 中,调试器也被控制——一旦程序启动,它就不能暂停或停止。唯一的办法就是通过关闭项目来杀死它。
问题显然出在函数中,因为我可以注释掉对它的调用,一切都像以前一样工作。不幸的是,一旦问题发生,唯一的解决办法似乎就是重启树莓派。
我在使用脚本语言 (Tcl) 之前做过类似的事情,但这次我正在寻找一种非解释性语言的性能提升,因为 Pi 也将是 运行通过类似 USB 串行接口的高带宽数据记录程序。
代码如下:
/******************************************************************************/
/* This function scans through the list of USB Serial ports and tries to */
/* establish communication with the target system. */
/******************************************************************************/
void tapCommInit(void) {
char line[128];
char port[15]; // this is always of the form "/dev/TTYACMn"
char *ptr;
FILE *ifd;
struct termios options;
uint8_t msgOut[3], msgIn[4];
msgOut[0] = REQ_ID; // now prepare the message to send
msgOut[1] = 0; // no data so length is zero
msgOut[2] = 0;
/**************************************************************************/
/* First, get the list of USB Serial ports. */
/**************************************************************************/
system("ls -l /dev/serial/by-path > usbSerial\n"); // get current port list
ifd = fopen("usbSerial", "r");
logIt(fprintf(lfd, "serial ports: \n"));
/**************************************************************************/
/* The main loop iterates through the file looking for lines containing */
/* "tty" which should be a valid USB Serial port. The port is configured */
/* in raw mode as 8N1 and an ID request command is sent, which has no */
/* data. If a response is received it's checked to see if the returned */
/* ID is a match. If not, the port is closed and we keep looking. If a */
/* match is found, tapState is set to "UP" and the function returns. If */
/* no match is found, tapState is left in the initial "DOWN" state. */
/**************************************************************************/
while(1) {
if (fgets(line, 127, ifd) == NULL) { // end of file?
break; // yes - break out and return
}
ptr = strstr(line, "tty"); // make sure the line contains a valid entry
if (ptr == NULL) {
continue; // nothing to process on this line
}
strcpy(port, "/dev/"); // create a correct pathname
strcat(port, ptr); // append the "ttyACMn" part of the line
port[strlen(port)-1] = 0; // the last character is a newline - remove it
logIt(fprintf(lfd," %s\n", port)); // we have a port to process now
cfd = open(port, O_RDWR | O_NOCTTY | O_NDELAY); // cfd is a global int
if (cfd == -1) {
logIt(fprintf(lfd, "Could not open port: %s\n", port));
continue; // keep going with the next one (if any)
}
fcntl(cfd, F_SETFL, 0); // blocking mode
tcgetattr(cfd, &options); // get the current port settings
options.c_cflag |= (CLOCAL | CREAD); // ena receiver, ignore modem lines
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw, no echo
options.c_oflag &= ~OPOST; // no special output processing
options.c_cc[VMIN] = 0; // minimum number of raw read characters
options.c_cc[VTIME] = 10; // timeout in deciseconds (1 second timeout)
tcsetattr(cfd, TCSANOW, &options); // set options right now
cfsetispeed(&options, B115200); // input baud rate
cfsetospeed(&options, B115200); // output baud rate
options.c_cflag &= ~(CSIZE | PARENB | // clear size bits, no parity
CSTOPB | CRTSCTS); // 1 stop bit, no hw flow control
options.c_cflag |= CS8; // now set size: 8-bit characters
options.c_cflag &= ~(IXON | IXOFF | IXANY); // no sw flow control
if (write(cfd, msgOut, 3) < 3) {
logIt(fprintf(lfd, "Sending of output message failed\n"));
close(cfd);
continue;
}
if (read(cfd, msgIn, 4) != 4) {
logIt(fprintf(lfd, "Didn't get expected amount of return data\n"));
close(cfd);
continue;
}
if (msgIn[3] != HOST_ID) {
logIt(fprintf(lfd, "Got the wrong HOST_ID response\n"));
close(cfd);
continue;
}
logIt(fprintf(lfd, "Port found - communication established\n"));
tapState = UP;
break; // we're done - break out of the loop
}
fclose(ifd); // close and remove the file we created
remove("usbSerial");
}
from within Code::Blocks the debugger is also hosed - once the program is started it cannot be paused or stopped
您不了解您的工具的可能性远远大于您创建了一个无法杀死的程序。
解决这个问题很容易:分而治之。这里有一大堆不相关的组件。开始将它们分开,找出哪些部分在孤立状态下工作良好,哪些在与其他所有部分断开连接时继续表现不佳。那你就有罪魁祸首了。
具体在这里,这意味着尝试 运行 在 IDE 之外运行您的程序,然后在命令行下 gdb
而不是 GDB 通过 IDE.
此外,应该可以 运行 您的程序而不启动 Web 服务器部分,这样您就可以 运行 隔离应用程序的串行部分。这不仅有利于通过最小化混杂变量进行调试,还鼓励松散耦合的程序设计,这本身就是一件好事。
最后,您可能会发现阻止您的程序停止的是 Web 框架,Code::Blocks,或者 GDB 在 Code::Blocks 下的 Pi 上运行的方式,而不是任何东西使用 USB 转串口适配器。
once the problem happens the only solution seems to be to reboot the Pi
如果您的程序仍在后台运行 运行,那么您的下一个实例当然会在尝试打开同一个 USB 端口时失败。
不用猜,找出来:
$ sudo lsof | grep ttyACM
或:
$ lsof -p $(pidof myprogram)
(如果您的系统没有 pidof
,请替换为 pgrep
。)
I've done things like this before using a scripting language (Tcl) but this time around I'm looking for a performance boost from a non-interpreted language
您的串行端口 运行ning 为 115,200 bps。将其除以 10 以计算停止位和起始位,然后翻转分数以获得每个字节的秒数,得到每个字节 87 微秒。你只有在串行端口 运行ning 全力以赴,每秒发送或接收 11,500 字节时才能实现。想猜猜 Tcl 在 87 微秒内可以解释多少行代码? Tcl 不是超快,但即使在 Tcl 领域,87 微秒也是永恒的。
然后在连接的另一端,您有 HTTP 和 [W]LAN,每个事务可能会再增加一百毫秒左右的延迟。
你对速度的需求是一种错觉。
现在当你需要异步地与其中的 100 个对话时回来再和我谈谈,然后也许我们可以开始证明 C 优于 Tcl。
(我说这是因为他的日常工作涉及维护一个大型 C++ 程序,该程序执行大量串行和网络 I/O。)
现在让我们来看看这段代码的许多问题:
system("ls -l /dev/serial/by-path > usbSerial\n"); // get current port list
ifd = fopen("usbSerial", "r");
不要在管道就足够的地方使用临时文件;在这里使用 popen()
。
while(1) {
这是完全错误的。在此处输入 while (!feof(ifd)) {
,否则您将尝试读取文件末尾。
这个加上下一个错误,可能是您主要症状的关键。
if (fgets(line, 127, ifd) == NULL) {
break;
这里有几个问题:
您假设文档中未遵循的 return 值的含义。 The Linux fopen(3)
man page isn't super clear on this; the BSD version 更好:
The fgets() and gets() functions do not distinguish between end-of-file and error, and callers must use feof(3) and ferror(3) to determine which occurred.
因为 fgets()
是标准 C,而不是 Linux- 或 BSD 特定的,通常可以安全地查阅其他系统的手册页。更好的是,查阅一个很好的通用 C 参考资料,例如 Harbison & Steele。 (当我做纯 C 而不是 C++ 时,我发现它比 K&R 有用得多。)
最重要的是,简单地检查 NULL
并不能告诉您这里需要知道的一切。
其次,如果您缩小 line
缓冲区的大小,硬编码的 127
常量是一个等待爆炸的代码炸弹。在这里说sizeof(line)
。
(不,不是 sizeof(line) - 1
:fgets()
在阅读时留下 space 作为尾随的空字符。再一次,RTFM 仔细。)
break
也是一个问题,但我们必须进一步深入代码才能了解原因。
继续:
strcat(port, ptr); // append the "ttyACMn" part of the line
这里有两个问题:
你是在盲目地假设 strlen(ptr) <= sizeof(port) - 6
。请改用 strncat(3)
。
(前一行的 strcpy()
(与 strncpy()
相对)是合理的,因为您正在复制字符串文字,所以您可以看到您还没有结束 运行宁缓冲区,但你应该养成假装不检查长度的旧 C 字符串函数甚至不存在的习惯。如果你提高警告级别,一些编译器在你使用它们时实际上会发出警告。 )
或者,更好的是,放弃 C 字符串,开始使用 std::string
。我看得出来你在努力坚持使用 C,但 C++ 中确实有值得使用的东西,即使你主要使用 C。C++ 的自动内存管理工具(不仅是 string
,还有 auto_ptr
/unique_ptr
和更多)属于这一类。
此外,C++ 字符串的操作更像 Tcl 字符串,因此您可能会更熟悉它们。
评论中的事实断言必须始终是真实的,否则它们以后可能会误导您,这可能是危险的。您的特定 USB 转串口适配器可能使用 /dev/ttyACMx
,但并非所有人都使用。还有另一种常见的 USB device class 被某些串口转 USB 适配器使用,导致它们在 Linux 下显示为 ttyUSBx
。更一般地说,将来的更改可能会以其他方式更改设备名称;例如,您可能会移植到 BSD,现在您的 USB 转串口设备被称为 /dev/cu.usbserial
,耗尽了您的 15 字节 port
缓冲区。 不要假设。
即使不考虑 BSD 情况,您的 port
缓冲区不应小于 line
缓冲区,因为您将后者连接到前者。至少,sizeof(port)
应该是 sizeof(line) + strlen("/dev/")
,以防万一。如果这看起来过多,那只是因为 128 字节的行缓冲区不必要地大。 (并不是说我想扭转你的手臂来改变它。RAM 很便宜;程序员调试时间很昂贵。)
下一个:
fcntl(cfd, F_SETFL, 0); // blocking mode
文件句柄在 Unix 中默认是阻塞的。您必须询问 非阻塞文件句柄。无论如何,炸毁所有标志是不好的作风;您不知道要在此处更改的 other 标志是什么。正确的风格是获取、修改,然后设置,就像您使用 tcsetattr()
:
的方式一样
int flags;
fcntl(cfd, F_GETFL, &flags);
flags &= ~O_NONBLOCK;
fcntl(cfd, F_SETFL, flags);
嗯,你有点正确使用tcsetattr()
:
tcsetattr(cfd, TCSANOW, &options);
...随后对 options
进行进一步修改,而无需再次调用 tcsetattr()
。糟糕!
您不会认为对 options
结构的修改会立即影响串行端口,是吗?
if (write(cfd, msgOut, 3) < 3) {
logIt(fprintf(lfd, "Sending of output message failed\n"));
close(cfd);
continue;
}
此处错误成堆:
您正在折叠短写和错误情况。分别处理:
int bytes = write(cfd, msgOut, 3);
if (bytes == 0) {
// can't happen with USB, but you may later change to a
// serial-to-Ethernet bridge (e.g. Digi One SP), and then
// it *can* happen under TCP.
//
// complain, close, etc.
}
else if (bytes < 0) {
// plain failure case; could collapse this with the == 0 case
// close, etc
}
else if (bytes < 3) {
// short write case
}
else {
// success case
}
您没有记录 errno
或其等效字符串,因此当 (!) 出现错误时,您将不知道 哪个 错误:
logIt(fprintf(lfd, "Sending of output message failed: %s (code %d)\n",
strerror(errno), errno));
修改口味。只要意识到 write(2)
和大多数其他 Unix 系统调用一样,有一大堆可能的错误代码。您可能不想以相同的方式处理所有这些问题。 (例如 EINTR
)
关闭 FD 后,您将其设置为有效的 FD 值,因此在读取一行后的 EOF 上,您将函数保留为有效但关闭的 FD 值! (这是上面 break
的问题:它可以隐式 return 一个关闭的 FD 给它的调用者。)在每次 close(cfd)
调用后说 cfd = -1
。
上面写的关于 write()
的所有内容也适用于以下 read()
调用,而且:
if (read(cfd, msgIn, 4) != 4) {
POSIX 中没有任何内容告诉您如果串行设备发送 4 个字节,您将在单个 read()
中获得所有 4 个字节,即使是阻塞 FD。使用慢速串行端口时,每个 read()
获得的字节数尤其不可能超过一个字节,这仅仅是因为与串行端口相比,您的程序快如闪电。您需要在此处循环调用 read()
,仅在出错或完成时退出。
以防万一它不明显:
remove("usbSerial");
如果切换到上面的 popen()
,则不需要它。不要将临时工作文件散布在管道可以执行的文件系统周围。
我目前正在 Raspberry Pi 3 (Linux Ubuntu) 上开发 C 程序 运行,旨在提供用于配置网络的网页界面在嵌入式系统上。
正在使用 Code::Blocks 和 GDB 调试器开发代码。我在 Web 服务器上使用 microhttpd,加上各种网页,都运行良好。我现在正在使用 "Serial Programming Guide for POSIX Operating Systems".
中的信息将 USB 串行 link 连接到嵌入式系统下面的代码负责打开 USB 串行 link 到目标系统并且似乎工作正常 - 一次。如果我关闭程序并重新启动它(在命令行上独立运行或从 Code::Blocks 中重新启动)第二次 microhttpd 被清理 - 浏览器 windows 将不再连接。此外,在 Code::Blocks 中,调试器也被控制——一旦程序启动,它就不能暂停或停止。唯一的办法就是通过关闭项目来杀死它。
问题显然出在函数中,因为我可以注释掉对它的调用,一切都像以前一样工作。不幸的是,一旦问题发生,唯一的解决办法似乎就是重启树莓派。
我在使用脚本语言 (Tcl) 之前做过类似的事情,但这次我正在寻找一种非解释性语言的性能提升,因为 Pi 也将是 运行通过类似 USB 串行接口的高带宽数据记录程序。
代码如下:
/******************************************************************************/
/* This function scans through the list of USB Serial ports and tries to */
/* establish communication with the target system. */
/******************************************************************************/
void tapCommInit(void) {
char line[128];
char port[15]; // this is always of the form "/dev/TTYACMn"
char *ptr;
FILE *ifd;
struct termios options;
uint8_t msgOut[3], msgIn[4];
msgOut[0] = REQ_ID; // now prepare the message to send
msgOut[1] = 0; // no data so length is zero
msgOut[2] = 0;
/**************************************************************************/
/* First, get the list of USB Serial ports. */
/**************************************************************************/
system("ls -l /dev/serial/by-path > usbSerial\n"); // get current port list
ifd = fopen("usbSerial", "r");
logIt(fprintf(lfd, "serial ports: \n"));
/**************************************************************************/
/* The main loop iterates through the file looking for lines containing */
/* "tty" which should be a valid USB Serial port. The port is configured */
/* in raw mode as 8N1 and an ID request command is sent, which has no */
/* data. If a response is received it's checked to see if the returned */
/* ID is a match. If not, the port is closed and we keep looking. If a */
/* match is found, tapState is set to "UP" and the function returns. If */
/* no match is found, tapState is left in the initial "DOWN" state. */
/**************************************************************************/
while(1) {
if (fgets(line, 127, ifd) == NULL) { // end of file?
break; // yes - break out and return
}
ptr = strstr(line, "tty"); // make sure the line contains a valid entry
if (ptr == NULL) {
continue; // nothing to process on this line
}
strcpy(port, "/dev/"); // create a correct pathname
strcat(port, ptr); // append the "ttyACMn" part of the line
port[strlen(port)-1] = 0; // the last character is a newline - remove it
logIt(fprintf(lfd," %s\n", port)); // we have a port to process now
cfd = open(port, O_RDWR | O_NOCTTY | O_NDELAY); // cfd is a global int
if (cfd == -1) {
logIt(fprintf(lfd, "Could not open port: %s\n", port));
continue; // keep going with the next one (if any)
}
fcntl(cfd, F_SETFL, 0); // blocking mode
tcgetattr(cfd, &options); // get the current port settings
options.c_cflag |= (CLOCAL | CREAD); // ena receiver, ignore modem lines
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw, no echo
options.c_oflag &= ~OPOST; // no special output processing
options.c_cc[VMIN] = 0; // minimum number of raw read characters
options.c_cc[VTIME] = 10; // timeout in deciseconds (1 second timeout)
tcsetattr(cfd, TCSANOW, &options); // set options right now
cfsetispeed(&options, B115200); // input baud rate
cfsetospeed(&options, B115200); // output baud rate
options.c_cflag &= ~(CSIZE | PARENB | // clear size bits, no parity
CSTOPB | CRTSCTS); // 1 stop bit, no hw flow control
options.c_cflag |= CS8; // now set size: 8-bit characters
options.c_cflag &= ~(IXON | IXOFF | IXANY); // no sw flow control
if (write(cfd, msgOut, 3) < 3) {
logIt(fprintf(lfd, "Sending of output message failed\n"));
close(cfd);
continue;
}
if (read(cfd, msgIn, 4) != 4) {
logIt(fprintf(lfd, "Didn't get expected amount of return data\n"));
close(cfd);
continue;
}
if (msgIn[3] != HOST_ID) {
logIt(fprintf(lfd, "Got the wrong HOST_ID response\n"));
close(cfd);
continue;
}
logIt(fprintf(lfd, "Port found - communication established\n"));
tapState = UP;
break; // we're done - break out of the loop
}
fclose(ifd); // close and remove the file we created
remove("usbSerial");
}
from within Code::Blocks the debugger is also hosed - once the program is started it cannot be paused or stopped
您不了解您的工具的可能性远远大于您创建了一个无法杀死的程序。
解决这个问题很容易:分而治之。这里有一大堆不相关的组件。开始将它们分开,找出哪些部分在孤立状态下工作良好,哪些在与其他所有部分断开连接时继续表现不佳。那你就有罪魁祸首了。
具体在这里,这意味着尝试 运行 在 IDE 之外运行您的程序,然后在命令行下 gdb
而不是 GDB 通过 IDE.
此外,应该可以 运行 您的程序而不启动 Web 服务器部分,这样您就可以 运行 隔离应用程序的串行部分。这不仅有利于通过最小化混杂变量进行调试,还鼓励松散耦合的程序设计,这本身就是一件好事。
最后,您可能会发现阻止您的程序停止的是 Web 框架,Code::Blocks,或者 GDB 在 Code::Blocks 下的 Pi 上运行的方式,而不是任何东西使用 USB 转串口适配器。
once the problem happens the only solution seems to be to reboot the Pi
如果您的程序仍在后台运行 运行,那么您的下一个实例当然会在尝试打开同一个 USB 端口时失败。
不用猜,找出来:
$ sudo lsof | grep ttyACM
或:
$ lsof -p $(pidof myprogram)
(如果您的系统没有 pidof
,请替换为 pgrep
。)
I've done things like this before using a scripting language (Tcl) but this time around I'm looking for a performance boost from a non-interpreted language
您的串行端口 运行ning 为 115,200 bps。将其除以 10 以计算停止位和起始位,然后翻转分数以获得每个字节的秒数,得到每个字节 87 微秒。你只有在串行端口 运行ning 全力以赴,每秒发送或接收 11,500 字节时才能实现。想猜猜 Tcl 在 87 微秒内可以解释多少行代码? Tcl 不是超快,但即使在 Tcl 领域,87 微秒也是永恒的。
然后在连接的另一端,您有 HTTP 和 [W]LAN,每个事务可能会再增加一百毫秒左右的延迟。
你对速度的需求是一种错觉。
现在当你需要异步地与其中的 100 个对话时回来再和我谈谈,然后也许我们可以开始证明 C 优于 Tcl。
(我说这是因为他的日常工作涉及维护一个大型 C++ 程序,该程序执行大量串行和网络 I/O。)
现在让我们来看看这段代码的许多问题:
system("ls -l /dev/serial/by-path > usbSerial\n"); // get current port list ifd = fopen("usbSerial", "r");
不要在管道就足够的地方使用临时文件;在这里使用 popen()
。
while(1) {
这是完全错误的。在此处输入 while (!feof(ifd)) {
,否则您将尝试读取文件末尾。
这个加上下一个错误,可能是您主要症状的关键。
if (fgets(line, 127, ifd) == NULL) { break;
这里有几个问题:
您假设文档中未遵循的 return 值的含义。 The Linux
fopen(3)
man page isn't super clear on this; the BSD version 更好:The fgets() and gets() functions do not distinguish between end-of-file and error, and callers must use feof(3) and ferror(3) to determine which occurred.
因为
fgets()
是标准 C,而不是 Linux- 或 BSD 特定的,通常可以安全地查阅其他系统的手册页。更好的是,查阅一个很好的通用 C 参考资料,例如 Harbison & Steele。 (当我做纯 C 而不是 C++ 时,我发现它比 K&R 有用得多。)最重要的是,简单地检查
NULL
并不能告诉您这里需要知道的一切。其次,如果您缩小
line
缓冲区的大小,硬编码的127
常量是一个等待爆炸的代码炸弹。在这里说sizeof(line)
。(不,不是
sizeof(line) - 1
:fgets()
在阅读时留下 space 作为尾随的空字符。再一次,RTFM 仔细。)break
也是一个问题,但我们必须进一步深入代码才能了解原因。
继续:
strcat(port, ptr); // append the "ttyACMn" part of the line
这里有两个问题:
你是在盲目地假设
strlen(ptr) <= sizeof(port) - 6
。请改用strncat(3)
。(前一行的
strcpy()
(与strncpy()
相对)是合理的,因为您正在复制字符串文字,所以您可以看到您还没有结束 运行宁缓冲区,但你应该养成假装不检查长度的旧 C 字符串函数甚至不存在的习惯。如果你提高警告级别,一些编译器在你使用它们时实际上会发出警告。 )或者,更好的是,放弃 C 字符串,开始使用
std::string
。我看得出来你在努力坚持使用 C,但 C++ 中确实有值得使用的东西,即使你主要使用 C。C++ 的自动内存管理工具(不仅是string
,还有auto_ptr
/unique_ptr
和更多)属于这一类。此外,C++ 字符串的操作更像 Tcl 字符串,因此您可能会更熟悉它们。
评论中的事实断言必须始终是真实的,否则它们以后可能会误导您,这可能是危险的。您的特定 USB 转串口适配器可能使用
/dev/ttyACMx
,但并非所有人都使用。还有另一种常见的 USB device class 被某些串口转 USB 适配器使用,导致它们在 Linux 下显示为ttyUSBx
。更一般地说,将来的更改可能会以其他方式更改设备名称;例如,您可能会移植到 BSD,现在您的 USB 转串口设备被称为/dev/cu.usbserial
,耗尽了您的 15 字节port
缓冲区。 不要假设。即使不考虑 BSD 情况,您的
port
缓冲区不应小于line
缓冲区,因为您将后者连接到前者。至少,sizeof(port)
应该是sizeof(line) + strlen("/dev/")
,以防万一。如果这看起来过多,那只是因为 128 字节的行缓冲区不必要地大。 (并不是说我想扭转你的手臂来改变它。RAM 很便宜;程序员调试时间很昂贵。)
下一个:
fcntl(cfd, F_SETFL, 0); // blocking mode
文件句柄在 Unix 中默认是阻塞的。您必须询问 非阻塞文件句柄。无论如何,炸毁所有标志是不好的作风;您不知道要在此处更改的 other 标志是什么。正确的风格是获取、修改,然后设置,就像您使用 tcsetattr()
:
int flags;
fcntl(cfd, F_GETFL, &flags);
flags &= ~O_NONBLOCK;
fcntl(cfd, F_SETFL, flags);
嗯,你有点正确使用tcsetattr()
:
tcsetattr(cfd, TCSANOW, &options);
...随后对 options
进行进一步修改,而无需再次调用 tcsetattr()
。糟糕!
您不会认为对 options
结构的修改会立即影响串行端口,是吗?
if (write(cfd, msgOut, 3) < 3) { logIt(fprintf(lfd, "Sending of output message failed\n")); close(cfd); continue; }
此处错误成堆:
您正在折叠短写和错误情况。分别处理:
int bytes = write(cfd, msgOut, 3); if (bytes == 0) { // can't happen with USB, but you may later change to a // serial-to-Ethernet bridge (e.g. Digi One SP), and then // it *can* happen under TCP. // // complain, close, etc. } else if (bytes < 0) { // plain failure case; could collapse this with the == 0 case // close, etc } else if (bytes < 3) { // short write case } else { // success case }
您没有记录
errno
或其等效字符串,因此当 (!) 出现错误时,您将不知道 哪个 错误:logIt(fprintf(lfd, "Sending of output message failed: %s (code %d)\n", strerror(errno), errno));
修改口味。只要意识到
write(2)
和大多数其他 Unix 系统调用一样,有一大堆可能的错误代码。您可能不想以相同的方式处理所有这些问题。 (例如EINTR
)关闭 FD 后,您将其设置为有效的 FD 值,因此在读取一行后的 EOF 上,您将函数保留为有效但关闭的 FD 值! (这是上面
break
的问题:它可以隐式 return 一个关闭的 FD 给它的调用者。)在每次close(cfd)
调用后说cfd = -1
。
上面写的关于 write()
的所有内容也适用于以下 read()
调用,而且:
if (read(cfd, msgIn, 4) != 4) {
POSIX 中没有任何内容告诉您如果串行设备发送 4 个字节,您将在单个 read()
中获得所有 4 个字节,即使是阻塞 FD。使用慢速串行端口时,每个 read()
获得的字节数尤其不可能超过一个字节,这仅仅是因为与串行端口相比,您的程序快如闪电。您需要在此处循环调用 read()
,仅在出错或完成时退出。
以防万一它不明显:
remove("usbSerial");
如果切换到上面的 popen()
,则不需要它。不要将临时工作文件散布在管道可以执行的文件系统周围。