Linux中超时程序,请求、响应与超时机制详解

请求、响应与超时机制详解

在C/S软件模型中,往往都遵循这请求和响应的机制,而且往往还伴随着超时控制。即:A发送请求,然后等待B的响应,同时开始超时计时,如果在超时时间内成功接收到响应,则结束等待和计时。如果到了超时时间还没有接收到响应,则结束等待同时此次通讯失败。

我们站在A端来设计。

A发送通讯请求,然后进入等待状态。此时常见的操作就是将当前线程挂起(suspend)。让挂起线程进入就绪状态有两个条件:超时或正确响应。当线程重新运行后,根据唤醒的方式不一样,执行不同的操作。如果是超时,则可能需要中断此次操作,然后上报错误信息。如果是正确响应,则结束超时计时,同时继续执行下一次传输。

这种模式我使用三种环境来说明:

- ucos ii(freeRTOS)

- linux

- qt5

ucos适用于低端嵌入式

linux适用于高端嵌入式和服务器

qt5适用于桌面上位机和客户端

ucos ii实现

ucos实现起来很简单,使用post/pend系列函数可以轻松实现,因为这类函数本身就带有超时控制。

INT8U OSMboxPost(OS_EVENT *pevent,void *pmsg);

INT8U OSMboxPend(OS_EVENT *pevent,INT32U timeout,INT8U *perr);

所以使用信号量或者邮箱能够很轻松的实现上面的要求。

linux实现

linux的线程同步可以使用条件变量,条件变量需要配合互斥锁使用。

互斥锁:

- pthread_mutex_init

- pthread_mutex_lock

- pthread_mutex_unlock

条件变量:

- pthread_cond_init

- pthread_cond_signal

- pthread_cond_wait

- pthread_cond_timewait

pthread_cond_wait会无限制的等待,直到触发为止,而pthread_cond_timewait则带有时间参数,正好满足我们的要求。

pthread_cond_timewait函数原型是:

int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);

在使用pthread_cond_timewait时,我们关心两点:

- struct timespec *restrict abstime参数

- int返回值

linux系统中通常有四种表示时间的方法:

- time_t

- struct timeval

- struct timespec

- struct tm

time_t就是一个长整型,表示当前时间距离1970-01-01 00:00:00 +0000 (UTC)的秒数。所以精度为秒

struct timeval是一个结构体,精度为微秒

struct timeval

{

__time_t tv_sec; /* Seconds. */

__suseconds_t tv_usec; /* Microseconds. */

};

struct timespec跟struct timeval一样,只不过精度更高,为纳秒

struct timespec

{

__time_t tv_sec; /* Seconds. */

long int tv_nsec; /* Nanoseconds. */

};

struct tm 直接用秒、分、小时、天、月、年等来表示时间。

struct tm

{

int tm_sec; /* Seconds. [0-60] (1 leap second) */

int tm_min; /* Minutes. [0-59] */

int tm_hour; /* Hours. [0-23] */

int tm_mday; /* Day. [1-31] */

int tm_mon; /* Month. [0-11] */

int tm_year; /* Year - 1900. */

int tm_wday; /* Day of week. [0-6] */

int tm_yday; /* Days in year.[0-365] */

int tm_isdst; /* DST. [-1/0/1]*/

#ifdef __USE_BSD

long int tm_gmtoff; /* Seconds east of UTC. */

__const char *tm_zone; /* Timezone abbreviation. */

#else

long int __tm_gmtoff; /* Seconds east of UTC. */

__const char *__tm_zone; /* Timezone abbreviation. */

#endif

};

相关的函数:

time_t time(time_t *t);

可以获取精确到秒的当前距离1970-01-01 00:00:00 +0000 (UTC)的秒数。

#include

int gettimeofday(struct timeval *tv, struct timezone *tz);

可以获取精确到微秒当前距离1970-01-01 00:00:00 +0000 (UTC)的微秒数。

#include

int clock_gettime(clockid_t clk_id, struct timespec *tp);

可以获取精确到纳秒当前距离1970-01-01 00:00:00 +0000 (UTC)的纳秒数。

好了,我们回到pthread_cond_timedwait上来,它需要一个struct timespec表示延迟时间,我们可以这样操作:

struct timespec ts;

(void) pthread_mutex_lock(&t.mn);

clock_gettime(CLOCK_REALTIME, &ts); //获取当前时间

ts.tv_sec += 5; //在当前时间基础上增加5s,即延时5s

rc = pthread_cond_timedwait(&t.cond, &t.mn, &ts); //线程等待

if (rc == 0){

//成功响应

}

else if(rc==ETIMEDOUT){

//超时

}

else{

//其他错误

}

(void) pthread_mutex_unlock(&t.mn);

再来说说pthread_cond_timedwait的返回值:

- 0,响应成功

- ETIMEDOUT,超时

- EINVAL,说明cond, mutex,或者abstime的值是无效的

- EPERM,说明当前线程在调用pthread_cond_timedwait时不是mutex的拥有者。

qt5实现

通过上面的讲解我们可以看出ucos和linux的线程操作方式还是很相似的。但是qt就完全不同了,我们进行qt开发的时候,时刻要记住一点就是:qt是基于事件驱动的。因此我们一定要保证:当qt什么都不做的时候,一定是在进行事件轮询。要不然整个程序就死了。

下面直接进入主题

qt通讯以串口为例,串口接收通常以槽函数来实现

my_port=new QSerialPort();

...

connect(my_port,SIGNAL(readyRead()),this,SLOT(slt_spReceive()));

将串口接收绑定到slt_spReceive上。

发送数据的时候

pkg_send(sendBa);

stat=ackwait(timeouts);

if(stat==-1)

{

//超时

}

else

{

//正确响应

}

这段代码中pkg_send(sendBa)发送玩数据后,ackwait(timeouts)开始等待timeouts,这是一个伪阻塞函数,也就是说,对于当前函数而言,不会立即执行后面的if判断,而是还阻塞在此处。但是前面也说了,当qt什么都不做的时候,一定是在进行事件轮询,所以在阻塞的时候,一定要进行时间轮询式的阻塞,而且这个阻塞还要能够被其他时间打断,比如定时器超时,比如数据正确响应信号。那么我们就要想到一个东西:局部时间循环,即QEventLoop。

比如我们的ackwait函数可以这么写

int SendFile::ackwait(int to) //响应等待 { QEventLoop loop;

QTimer timeout_t;

timeout_t.setSingleShot(true);

connect(&timeout_t,SIGNAL(timeout()),&loop,SLOT(quit())); //设置超时信号

connect(this,SIGNAL(sgn_ackRight()),&loop,SLOT(quit())); //设置响应正确信号

timeout_t.start(to); //启动超时定时器

loop.exec();

if(timeout_t.isActive())

{

qDebug()<

return 1;

}

else

{

qDebug()<

return -1;

}

}

首先定义了一个事件循环QEventLoop loop;

然后定义了一个定时器QTimer timeout_t作为超时计时

然后绑定两个信号来打断局部事件循环

connect(&timeout_t,SIGNAL(timeout()),&loop,SLOT(quit())); //设置超时信号

connect(this,SIGNAL(sgn_ackRight()),&loop,SLOT(quit())); //设置响应正确信号

然后开启定时器计时

再开启局部事件循环

然后等待事件循环被打断

然后判断是什么事件打断的,做出响应的处理。

当然在接收槽函数中,当接收到等待的数据时,需要发送响应信号。

void SendFile::slt_spReceive()

{

...

//接收到等待的数据

sgn_ackRight();

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值