我正在(资源受限的)嵌入式Linux平台(C语言)中使用Termios来发送命令并从各种tty外设(CP2102 USB-UART)接收数据。显然有多种方法可以做到这一点,而且许多方法都不正确。我尝试了一些缓解成功的方法,遇到了一些可行的方法,但是我不确定它是最佳方法还是正确方法:
#define BAUDRATE 115200
#define DEVICE "/dev/ttyUSB0"
#define DATA "BLK\r"
int handler(void){
char buf[255];
struct pollfd fds[1];
int fd, ret, res, retry = 0;
connect:
fd = open(DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == 0){
perror(DEVICE);
printf("Failed to open %s\n",DEVICE);
sleepms(2000);
if(retry++<5) goto connect;
//exit(-1);
}
set_interface_attribs (fd, BAUDRATE, 0); // 8n1 no parity
set_blocking (fd, 0); // not blocking
fds[0].fd = fd; // streams
fds[0].events = POLLRDNORM;
for (;;)
{
int count = write(fd, DATA, strlen(DATA));
ret = poll(fds, 1, 1000);
if (ret > 0){
if (fds[0].revents & POLLHUP){ printf("Hangup\n"); close(fd); goto connect;}
if (fds[0].revents & POLLRDNORM){
res = read(fd,buf,255);
if(!res){close(fd); goto connect;}
buf[res-2]=0;
printf("Received %d bytes : %s",res,buf);
}
}
}
}
基本上发送命令,然后对其进行轮询,直到出现某些数据或发生超时。 这项工作超过24小时,显示没有问题,但是有一个问题:如果外围设备已断开连接,那么我会收到“环聊”通知,但它从未重新连接,因为我希望关闭文件并重试与接口的连接,因此
此外,民意调查是最好的方法吗?我不想将CPU时间完全浪费在轮询上(除非轮询具有某种机制,可以将CPU时间释放到其他线程上),但我仍然希望呼叫被阻塞,直到发生超时或响应到达为止(即,我不想发送命令,并稍后通过回调获取响应)。响应时间不到几毫秒就来自外围设备。
n.b。我知道有些眼睛因goto语句而流血,不用担心,这里使用goto来快速测试“重新连接”方法(该方法不起作用),但最后如果必须重新启动连接,它将进入一个单独的函数goto将永远不会在最终实现中使用。
编辑:
重写后。它可以工作,但仍然存在问题,主要是,当我断开外围设备的连接时,Linux将随机保留端口名称或对其进行更改。有时它将保留多个连续断开连接的端口名称,有时它将重命名该端口并使用新名称一段时间。因此,我必须找到一种从USB识别外围设备并获取当前端口名称的方法。
在断开连接时,它会从tcgetattr引发错误9(我认为是来自set_interface_attribs或set_blocking),但是一旦重新连接,并且如果Linux不更改端口名,则立即重新连接并重新开始发送和接收,但是,当Linux重命名端口时,它将失败。
int fd=-1,retry=0;
struct pollfd fds[1];
int connect(void){
if(fd==-1) fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
else return 0; // already opened
printf("Connecting to fd #%d\n",fd);
if (fd==0){
perror(MODEMDEVICE);
printf("Failed to open %s\n",MODEMDEVICE);
retry++;
sleepms(2000);
return -1;
}
set_interface_attribs (fd, BAUDRATE, 0); // 8n1 no parity
set_blocking (fd, 0); // not blocking
fds[0].fd = fd; // streams
fds[0].events = POLLERR|POLLHUP|POLLRDNORM;
return 0;
}
int handlePoll(){
int ret=0,res=0;
char buf[255];
ret = poll(fds, 1, 1000); // 1000ms
if (ret > 0){
if (fds[0].revents & POLLERR){ close(fd); fd=-1; return -1; } // IO error
if (fds[0].revents & POLLHUP){ close(fd); fd=-1; return -2; } // interface closed
if (fds[0].revents & POLLRDNORM){
res = read(fd,buf,255);
if(!res){ close(fd); return -3; } // data receive error
buf[res-2]=0;
printf("Received %d bytes : %s\n",res,buf);
return 0;
}
return -4; // unknown error
}
return -5; // timeout
}
int sendCMD(char* cmd){
int res=1;
retry=0;
while(res && retry<5){ res=connect(); }
if(res) return -7;
if(retry>5) return -8;
int len = strlen(cmd);
int count = write(fd, cmd, len);
if(count<len){ return -6;}
return handlePoll();
}
int main(void){
while(1){
switch(sendCMD(DATA)){
case -1: printf("IO error\n"); break;
case -2: printf("Interface closed error\n"); break;
case -3: printf("data receive error\n"); break;
case -4: printf("Unknown error\n"); break;
case -5: printf("Timeout\n"); break;
case -6: printf("Command send error\n"); sleepms(200); break;
case -7: printf("Interface open error\n"); break;
case -8: printf("Cannot open interface after 5 try\n"); break;
default: break;
}
}
}
我认为应该有一种更好的方法来处理断开连接(Detecting if a character device has disconnected in Linux in with termios api (c++))
答案 0 :(得分:0)
connect:
fd = open(DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == 0){
perror(DEVICE);
open(2)
不会返回0
,而是返回-1
,以防发生错误。 0
是有效的文件描述符-按照惯例是标准输入。
int count = write(fd, DATA, strlen(DATA));
ret = poll(fds, 1, 1000);
if (ret > 0){
为什么不检查write()
的返回值?如果上面的open()
实际上失败并返回了-1
,那么此write()
也将失败,请返回-1
并将errno
设置为EBADF
。>
res = read(fd,buf,255);
if(!res){close(fd); goto connect;}
buf[res-2]=0;
同上,您不必担心read()
是否失败,如果上面的open()
无法打开文件,当然可以。就像open()
,write()
和所有系统调用一样,read()
将在发生错误的情况下返回-1
。
由于文件描述符是非阻塞的,因此请准备分别处理EAGAIN
之类的瞬态错误[2]。
在res == -1
的情况下,buf[res-2]=0
将通过在buf
的开始之前写入来破坏内存。
请注意,如果poll()
传递了无效的fd,将不在POLLERR
中设置revents
-如果会忽略是否为负(例如失败的fd
返回的open()
),或者,如果为正,则设置POLLNVAL
。
if (fds[0].revents & POLLHUP){ printf("Hangup\n"); close(fd); goto connect;}
if (fds[0].revents & POLLRDNORM){
POLLHUP
和POLLRDNORM
[1]可以在revents
中一起返回,表示最后一次可能的读取,您的代码将错过该读取。
我强烈建议您使用strace(1)
strace -p PID
来运行程序。
[1]顺便说一句,POLLRDNORM
等同于Linux中的POLLIN
。
[2]和EINTR
,如果您正在使用的程序或库正在设置任何信号处理程序。