信号驱动,超时接收

╰半夏微凉° 2022-12-14 13:34 268阅读 0赞

一、信号驱动。
1、信号驱动原理是什么?
就是使用了系统编程中信号的机制,首先让程序安装SIGIO的信号处理函数,通过监听文件描述符是否产生了SIGIO信号,我们就知道文件描述符有没有数据到达。如果有数据到达(小明这个客人来了),则系统就会产生了SIGIO信号(门铃响了),我们只需要在信号处理函数读取数据即可。

模型:

void fun(int sig)
{
//读取sockfd的数据即可。
}

signal(SIGIO,fun); -> 只要将来收到SIGIO这个信号,就执行fun这个函数
sockfd -> 只要有数据到达sockfd,就会产生一个SIGIO的信号

2、 信号驱动特点以及步骤。
特点: 适用于UDP协议,不适用于TCP协议。

步骤一: 由于不知道数据什么时候会到达,所以需要提前捕捉SIGIO信号。 -> signal()
步骤二: 设置套接字的属主,其实就是告诉这个套接字对应的进程ID是谁。 -> fcntl()
步骤三: 给套接字添加信号触发模式。 -> fcntl()

1)如何设置属主? -> fcntl() -> man 2 fcntl

#include
#include

int fcntl(int fd, int cmd, … /* arg */ );

fd: 文件描述符
cmd: F_GETFL (void) -> 获取文件属性
F_SETFL (int) -> 设置文件属性
F_SETOWN (int) -> 设置属性 -> 最后一个参数要填,填进程ID号,一般都是getpid()。

返回值:
成功:文件描述符所有者
失败:-1

例如: fcntl(sockfd,F_SETOWN,getpid()); -> 让sockfd与进程ID绑定在一起。

2)如何添加信号触发模式? -> fcntl() -> man 2 fcntl

int state;
state = fcntl(sockfd,F_GETFL);
state |= O_ASYNC;
fcntl(sockfd,F_SETFL,state);

例题1: 使用信号驱动IO模型写一个UDP协议服务器,实现监听多个客户端给我发送过来的消息。

#include “head.h”

int sockfd;
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);

void func(int sig)
{
//将sockfd中的数据读取出来。
char buf[100];

bzero(&cliaddr,sizeof(cliaddr));
bzero(buf,sizeof(buf));
recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&cliaddr,&len);

printf(“from cli:%s”,buf);

}

int main(int argc,char *argv[]) // ./server 50001
{
//1. 创建UDP套接字
sockfd = socket(AF_INET,SOCK_DGRAM,0);

//2. 绑定IP地址
struct sockaddr_in srvaddr;
bzero(&srvaddr,sizeof(srvaddr));

srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(atoi(argv[1]));
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);

bind(sockfd,(struct sockaddr *)&srvaddr,len);

//3. 捕捉信号。
signal(SIGIO,func);

//4. 设置属主
fcntl(sockfd,F_SETOWN,getpid());

//5. 添加信号触发模式。
int state;
state = fcntl(sockfd,F_GETFL);
state |= O_ASYNC;
fcntl(sockfd,F_SETFL,state);

//6. 坐等各位给我写信就可以。
while(1)
pause();

return 0;
}

二、超时接收。
1、为什么会有超时接收?
一般地,我们都习惯使用阻塞IO,就是有数据就读取,没有数据就会一直阻塞等待。
因为有可能会出现一种情况,就是一直等待,都等不到结果,所以超时接收就是为了解决这个问题。

2、如何实现超时控制? —- 方法一:使用多路复用。

select(xx,xxx,xx,xx,NULL); -> 无限等待集合中的数据。
-> 如果集合中的文件描述符有数据到达,则函数就会返回。
-> 如果集合中的文件描述符没有数据到达在,则函数就会一直阻塞。

每次select之前,都需要重置好时间。
select(xx,xxx,xx,xx,5S); -> 只会在5S内阻塞等待,5S后就会返回。
-> 5S内有数据到达,则函数返回就绪的文件描述符个数。
-> 5S内没有数据到达,则5S内会阻塞。
-> 5S后,这个函数就会返回0,不会继续监听这个集合。

3、如何设置时间?
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

时间结构体:
struct timeval {
long tv_sec; //秒
long tv_usec; //微秒
};

例如:设置5S
struct timeval v;
v.tv_sec = 5;
v.tv_usec = 0;

select(xx,xx,xx,xx,&v);

例题2: 写一个TCP协议服务器,使用select函数去监听客户端的消息,如果客户端在5S内没有数据到达,则打印一句”timeout”,如果有数据,就打印客户端所说的话。

#include “head.h”

void *func(void *arg)
{
int i;
for(i=0;i<1000;i++)
{
printf(“i = %d\n”,i);
sleep(1);
}
}

int main(int argc,char *argv[]) // ./rose 50000
{
//0. 创建一个线程,用于计算事件。
pthread_t tid;
pthread_create(&tid,NULL,func,NULL);

//1. 创建TCP套接字
int sockfd;
sockfd = socket(AF_INET,SOCK_STREAM,0);
printf(“sockfd = %d\n”,sockfd);

//2. 绑定IP地址,端口号
struct sockaddr_in srvaddr;
socklen_t len = sizeof(srvaddr);
bzero(&srvaddr,len);

srvaddr.sin_family = AF_INET; //地址族
srvaddr.sin_port = htons(atoi(argv[1])); //端口号
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址 /usr/include/linux/in.h

bind(sockfd,(struct sockaddr *)&srvaddr,len);

//3. 设置监听套接字
//调用listen之前: sockfd -> 待连接的套接字
listen(sockfd,5);
//调用listen之后: sockfd -> 监听套接字

//4. 等待客户端的连接
struct sockaddr_in cliaddr;
int connfd;

connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);
if(connfd >= 0)
{
printf(“connfd = %d\n”,connfd);
printf(“new connection:%s\n”,inet_ntoa(cliaddr.sin_addr));
}

//5. 使用多路复用读取套接字上数据
fd_set set;
struct timeval v;
int ret;
char buf[100];

while(1)
{
FD_ZERO(&set);
FD_SET(connfd,&set);
v.tv_sec = 5;
v.tv_usec = 0;

ret = select(connfd+1,&set,NULL,NULL,&v);
if(ret == -1)
{
printf(“select error!\n”);
}

if(ret == 0)
{
printf(“timeout!\n”);
}

if(ret > 0)
{
bzero(buf,sizeof(buf));
recv(connfd,buf,sizeof(buf),0);
printf(“from jack:%s”,buf);

if(strncmp(buf,”fenshou”,7) == 0)
{
break;
}
}
}

//5. 阻塞读取套接字上的数据
/*
char buf[100];
while(1)
{
bzero(buf,sizeof(buf));
recv(connfd,buf,sizeof(buf),0);
printf(“from jack:%s”,buf);

if(strncmp(buf,”fenshou”,7) == 0)
{
break;
}
}
*/

//6. 回收TCP套接字的资源
close(sockfd);
close(connfd);

return 0;
}

练习3:修改test2/,如果在10S内,没有数据到,则打印timeout。

4、 如何实现超时接收? — 方法二: 设置套接字的属性为超时接收。
1)机制如何?
如果不给套接字设置属性,那么读取套接字上的数据时就会无限等待 -> 一直阻塞。
如果设置超时接收属性给套接字,那么读取套接字数据时,就会有时间的限制。 -> 前一段时间阻塞,时间过了就会返回。

2)操作步骤?
阻塞情况:
connfd = accept(sockfd); -> 一直阻塞。
recv(connfd); -> 一直阻塞等待数据的到达。

connfd = accept(sockfd); -> 一直阻塞。
设置超时接收属性给connfd
recv(connfd); -> 在规定时间内,就会阻塞读取,超过了时间,这个recv函数就会返回失败。

3)如何设置超时接收属性给套接字? -> setsockopt() -> man 2 setsockopt

#include /* See NOTES */
#include

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

sockfd:套接字
level: 优先级
SOL_SOCKET:套接字
IPPROTO_IP:IP优先级
IPPRO_TCP:TCP优先级
optname:选项名字
optval: 值,使能为1,不使能为0
optlen: 值类型大小

返回值:
成功:0
失败:-1

例子:
struct timeval v;
v.tv_sec = 5;
v.tv_usec = 0;

setsockopt(connfd,SOL_SOCKET,SO_RCVTIMEO,&v,sizeof(v));

接下来,再去读取connfd的数据,前5S就会阻塞,过了5S就会返回失败了。

练习4: 写一个TCP服务器,使用recv函数接收客户端的消息,如果服务器在5S内,没有数据到达,就打印timeout。

#include “head.h”

void *func(void *arg)
{
int i;
for(i=0;i<1000;i++)
{
printf(“i = %d\n”,i);
sleep(1);
}
}

int main(int argc,char *argv[]) // ./rose 50000
{
//0. 创建线程
pthread_t tid;
pthread_create(&tid,NULL,func,NULL);

//1. 创建TCP套接字
int sockfd;
sockfd = socket(AF_INET,SOCK_STREAM,0);
printf(“sockfd = %d\n”,sockfd);

//2. 绑定IP地址,端口号
struct sockaddr_in srvaddr;
socklen_t len = sizeof(srvaddr);
bzero(&srvaddr,len);

srvaddr.sin_family = AF_INET; //地址族
srvaddr.sin_port = htons(atoi(argv[1])); //端口号
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址 /usr/include/linux/in.h

bind(sockfd,(struct sockaddr *)&srvaddr,len);

//3. 设置监听套接字
//调用listen之前: sockfd -> 待连接的套接字
listen(sockfd,5);
//调用listen之后: sockfd -> 监听套接字

//4. 等待客户端的连接
struct sockaddr_in cliaddr;
int connfd;

connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);
if(connfd >= 0)
{
printf(“connfd = %d\n”,connfd);
printf(“new connection:%s\n”,inet_ntoa(cliaddr.sin_addr));
}

//5. 设置超时属性给connfd。
struct timeval v;
v.tv_sec = 5;
v.tv_usec = 0;

setsockopt(connfd,SOL_SOCKET,SO_RCVTIMEO,&v,sizeof(v));

//6. 不断读取connfd的内容。
char buf[100] = {0};
while(1)
{
bzero(buf,sizeof(buf));
if(recv(connfd,buf,sizeof(buf),0) >= 0) //有数据到达
{
printf(“from client:%s”,buf);
}
else{ //返回失败,说明5S内都没有数据到达,即超时。
printf(“timeout!\n”);
}

if(strncmp(buf,”quit”,4) == 0)
{
break;
}
}

close(connfd);
close(sockfd);
return 0;
}

发表评论

表情:
评论列表 (有 0 条评论,268人围观)

还没有评论,来说两句吧...

相关阅读

    相关 简单的串口接收超时函数

    简单的串口接收超时函数 类似于select 函数。 设置的超时时间,若在规定的时间内接收到数据,则返回0(接收成功);否则返回-1 适用场景: 给一块设备发送信息,

    相关 信号驱动超时接收

    一、信号驱动。 1、信号驱动原理是什么? 就是使用了系统编程中信号的机制,首先让程序安装SIGIO的信号处理函数,通过监听文件描述符是否产生了SIGIO信号,我们就知道

    相关 总线电路及信号驱动小结

    (1) 总线是各种信号线的集合,是嵌入式系统中各部件之间传送数据、地址和控制信息的公共通路。在同一时刻,每条通路线路上能够传输一位二进制信号。按照总线所传送的信息类型,可