当前位置: 代码迷 >> 综合 >> 信号驱动,超时接收
  详细解决方案

信号驱动,超时接收

热度:54   发布时间:2024-02-28 02:59:03.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 <unistd.h>
        #include <fcntl.h>

       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 <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
 
  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;
}

  相关解决方案