嵌入式linux:linux select函数与socket

来源:互联网 发布:linux 查看内存多大 编辑:程序博客网 时间:2024/05/19 23:03

转自:http://blog.csdn.net/lingfengtengfei/article/details/12392449

Linux中,我们可以使用select函数实现I/O端口的复用,传递给 select函数的参数会告诉内核:

      •我们所关心的文件描述符

      •对每个描述符,我们所关心的状态。(我们是要想从一个文件描述符中读或者写,还是关注一个描述符中是否出现异常)

      •我们要等待多长时间。(我们可以等待无限长的时间,等待固定的一段时间,或者根本就不等待)

    select函数返回后,内核告诉我们一下信息:

      •对我们的要求已经做好准备的描述符的个数

      •对于三种条件哪些描述符已经做好准备.(读,写,异常)

   有了这些返回信息,我们可以调用合适的I/O函数(通常是 read write),并且这些函数不会再阻塞.

 

#include <sys/select.h>   

    int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

   

   返回:做好准备的文件描述符的个数,超时为0,错误为 -1.

   

   首先我们先看一下最后一个参数。它指明我们要等待的时间:

struct timeval{      

        long tv_sec;   /* */

        long tv_usec;  /*微秒 */   

    }

   

   有三种情况:

    timeout == NULL  等待无限长的时间。等待可以被一个信号中断。当有一个描述符做好准备或者是捕获到一个信号时函数会返回。如果捕获到一个信号, select函数将返回 -1,并将变量 erro设为 EINTR

    timeout->tv_sec == 0 &&timeout->tv_usec == 0不等待,直接返回。加入描述符集的描述符都会被测试,并且返回满足要求的描述符的个数。这种方法通过轮询,无阻塞地获得了多个文件描述符状态。

    timeout->tv_sec !=0 ||timeout->tv_usec!= 0 等待指定的时间。当有描述符符合条件或者超过超时时间的话,函数返回。在超时时间即将用完但又没有描述符合条件的话,返回 0。对于第一种情况,等待也会被信号所中断。

   

   中间的三个参数 readset, writset, exceptset,指向描述符集。这些参数指明了我们关心哪些描述符,和需要满足什么条件(可写,可读,异常)。一个文件描述集保存在 fd_set 类型中。fd_set类型变量每一位代表了一个描述符。我们也可以认为它只是一个由很多二进制位构成的数组。如下图所示:

   

   对于 fd_set类型的变量我们所能做的就是声明一个变量,为变量赋一个同种类型变量的值,或者使用以下几个宏来控制它:

 

#include <sys/select.h>   

int FD_ZERO(int fd, fd_set *fdset);   

int FD_CLR(int fd, fd_set *fdset);   

int FD_SET(int fd, fd_set *fd_set);   

int FD_ISSET(int fd, fd_set *fdset);

    FD_ZERO宏将一个 fd_set类型变量的所有位都设为 0,使用FD_SET将变量的某个位置位。清除某个位时可以使用 FD_CLR,我们可以使用FD_ISSET来测试某个位是否被置位。

   当声明了一个文件描述符集后,必须用FD_ZERO将所有位置零。之后将我们所感兴趣的描述符所对应的位置位,操作如下:

 

fd_set rset;   

int fd;   

FD_ZERO(&rset);   

FD_SET(fd, &rset);   

FD_SET(stdin, &rset);</span>

    

    select返回后,用FD_ISSET测试给定位是否置位:

 

if(FD_ISSET(fd, &rset)   

{ ... }

具体解释select的参数:

1intmaxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错。

说明:对于这个原理的解释可以看上边fd_set的详细解释,fd_set是以位图的形式来存储这些文件描述符。maxfdp也就是定义了位图中有效的位的个数。

2fd_set*readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读;如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

3fd_set*writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

4fd_set*errorfds同上面两个参数的意图,用来监视文件错误异常文件。

5structtimeval* timeoutselect的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为00毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即 selecttimeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

说明:

函数返回:

1)当监视的相应的文件描述符集中满足条件时,比如说读文件描述符集中有数据到来时,内核(I/O)根据状态修改文件描述符集,并返回一个大于0的数。某些文件可读写或出错

2)当没有满足条件的文件描述符,且设置的timeval监控时间超时时,select函数会返回一个为0的值。等待超时,没有可读写或错误的文件

3)当select返回负值时,发生错误。

这里说每次轮询调用select函数都要FD_ZERO(&fds)清空集合,否则不能检测描述符变化。为什么?

  while(1) 
  { 
       FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化
       FD_SET(sock,&fds); //添加描述符 
       FD_SET(fp,&fds); //同上
       maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1
       switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用 
       { 
          case -1: exit(-1);break; //select错误,退出程序 
          case 0:break; //再次轮询
          default: 
            //......
          }

select 返回后,它会修改每个fd_set结构,删除那些不存在激活状态的I/O操作的套接字句柄。所以你每次调用select之前,必须初始化一下。

这是一个输入输出参数,如果你只在循环外初始化一次,select函数会改变这个结构,最后的例子中使用了文件描述符备份,这样就不用每次都初始化一次了。

理解select模型:

理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8fd

1)执行fd_set set;FD_ZERO(&set);set用位表示是0000,0000

2)若fd5,执行FD_SET(fd,&set);set变为0001,0000(5位置为1)

3)若再加入fd2fd=1,set变为0001,0011

4)执行select(6,&set,0,0,0)阻塞等待

5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

基于上面的讨论,可以轻松得出select模型的特点:

1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。

2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环arrayFD_ISSET判断是否有时间发生)。

基本原理

Resize icon

 select()系统调用代码走读

调用顺序如下:sys_select() à core_sys_select() à do_select() à fop->poll()


特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样:fd_set rdfds; /* 先申明一个 fd_set 集合来保存我们要检测的 socket句柄 */
struct timeval tv; /* 申明一个时间变量来保存时间 */
int ret; /* 保存返回值 */
FD_ZERO(&rdfds); /* 用select函数之前先把集合清零 */
FD_SET(socket, &rdfds); /* 把要检测的句柄socket加入到集合里 */
tv.tv_sec = 1;
tv.tv_usec = 500; /* 设置select等待的最大时间为1秒加500毫秒 */
ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 检测我们上面设置到集合rdfds里的句柄是否有可读信息 */
if(ret < 0) perror("select");/* 这说明select函数出错 */
else if(ret == 0) printf("超时\n"); /* 说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */
else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */
    printf("ret=%d\n", ret); /* ret这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的就是句柄的总和了 */
    /* 这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */
    if(FD_ISSET(socket, &rdfds)) { /* 先判断一下socket这外被监视的句柄是否真的变成可读的了 */
        /* 读取socket句柄里的数据 */
        recv(...);
    }
}
   注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1


下面是一个TCP服务器的例子

main.c#include <stdio.h>#include <stdlib.h>#include<fcntl.h>        //open() close()#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>    //read()  wrie()#include<sys/ioctl.h>#include<string.h>#include <netinet/in.h>#include <sys/socket.h>#include <pthread.h>#include "MySocket.h"#define DEVICE_NAME "/dev/myspidev"#define TXRXFIFO 10 //在驱动中定义的FIFO大小是10.实际上FIFO始终是64.但是在驱动中的读取函数实现的是超过10时读取10个字节。#define SECOND 1#define USECOND 0//定义select时间长度int SocketListen;int SocketConnect;struct sockaddr_in server_addr;struct sockaddr_in client_addr;char SocketTxBuf[SOCKETBUFSIZE];char SocketRxBuf[SOCKETBUFSIZE];int main(){    int fd,maxfd;    int i=0;    fd_set rset, allset;        //select所需的文件描述符集合    struct timeval timeout;    int nready;    int sin_size;               //地址信息结构体大小    ssize_t SocketRxlen; //tcp接受到的数据长度    char TxBuf[TXRXFIFO]={1,2,3,4,5,6,7,8,9,10};    char RxBuf[TXRXFIFO];    unsigned char j=0;    printf("My2416 SPI dev test!\n");    fd=open(DEVICE_NAME,O_RDWR);    printf("SPIfd=%d\n",fd);    if(fd==-1)    {        printf("open device %s error\n",DEVICE_NAME);    }    else    {        SocketInit();        //初始化select        maxfd = SocketListen;        FD_ZERO(&allset);           //清空        FD_SET(SocketListen, &allset);  //将监听socket加入select检测的描述符集合        while(1)        {            // timeout setting            timeout.tv_sec = SECOND;            timeout.tv_usec = USECOND;            <span style="color:#FF0000;">rset = allset;</span>//文件描述符备份,避免每次进行初始化。            nready = select(maxfd + 1, &rset, NULL, NULL, &timeout);    //检测我们上面设置到集合rset里的句柄是否有可读信息            if(nready==-1)//select出错            {                perror("select");            }            else if(nready==0)//select超时            {                printf("Select outof time %ds%dus!\n",SECOND,USECOND);            }            else if(nready>0)//在等待时间内socket文件描述符(SocketListen或SocketConnect)有变化 可读写或出错            {                if (FD_ISSET(SocketListen, &rset))//先判断一下SocketListen这外被监视的句柄是否真的变成可读的了                {                       //检测是否有新客户端请求                    printf("Accept a connection.\n");                    //调用accept,返回服务器与客户端连接的socket描述符                    sin_size = sizeof(struct sockaddr_in);                    if ((SocketConnect = accept(SocketListen, (struct sockaddr *)&client_addr, (socklen_t *) & sin_size)) == -1)                    {                        perror("Accept() error\n");                        continue;                    }                    FD_SET(SocketConnect, &allset); //将新socket连接放入select监听集合                    if (SocketConnect > maxfd)                    {                        maxfd = SocketConnect;  //确认maxfd是最大描述符                    }                }                // 有客户连接,检测是否有数据                if (FD_ISSET(SocketConnect, &rset))                {                    printf("Receive from connect SocketConnect[%d].\n", SocketConnect);                    if ((SocketRxlen = recv(SocketConnect, SocketRxBuf, SOCKETBUFSIZE, 0)) == 0)                    {               //从客户端socket读数据,等于0表示网络中断                        close(SocketConnect);  //关闭socket连接                        printf("SocketConnect closed. \n");                        FD_CLR(SocketConnect, &allset);    //从监听集合中删除此socket连接                    }                    else                        process_client(&SocketConnect,SocketRxBuf, SocketRxlen); //接收到客户数据,开始处理                        memset(SocketRxBuf,0,strlen(SocketRxBuf));                }            }            /*           write(fd,TxBuf,TXRXFIFO);           read(fd,RxBuf,TXRXFIFO);           printf("APP:TX date is %d %d %d %d \n",TxBuf[0],TxBuf[1],TxBuf[2],TxBuf[3]);           printf("APP:RX date is %d %d %d %d \n",RxBuf[0],RxBuf[1],RxBuf[2],RxBuf[3]);           */        }        close(SocketListen);        printf("********************\n");        printf("Close SpiApp succeed!\n");    }    return 0;}<span style="font-family:'Times New Roman';">socket.c</span>#include <stdlib.h>#include <stdio.h>#include<unistd.h>#include<string.h>#include <netinet/in.h>#include<arpa/inet.h>#include <sys/socket.h>#include<sys/types.h>#include <errno.h>#include <pthread.h>#include "MySocket.h"#define PORT 3333extern int SocketListen;extern int SocketConnect;extern struct sockaddr_in server_addr;extern struct sockaddr_in client_addr;extern char SocketTxBuf[SOCKETBUFSIZE];extern char SocketRxBuf[SOCKETBUFSIZE];void *thrd_write(void *arg)        //写线程{    while(1)    {        if(strlen(SocketRxBuf)>0)        {            printf("SocketRxBuf len is %d\n",strlen(SocketRxBuf));            if(send(SocketConnect,SocketRxBuf,strlen(SocketRxBuf),0)==-1)            //写功能            {                printf("socket send Error\n");                close(SocketConnect);                //exit(1);            }            memset(SocketRxBuf,0,strlen(SocketRxBuf));        }    }    pthread_exit(NULL);            //线程退出}void SocketInit(void){    int yes = 1;//允许重复使用本地地址与套接字进行绑定    SocketListen = socket(AF_INET,SOCK_STREAM,0);    if(SocketListen == -1)    {        perror("socket");        //printf("Socket error\n");        exit(1);    }    printf("SocketListen fd=%d\n",SocketListen);    if (setsockopt(SocketListen, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)    {        perror("setsockopt");        exit(1);    }    bzero(&server_addr,sizeof(struct sockaddr_in));    server_addr.sin_family = AF_INET;//IPV4    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);    server_addr.sin_port = htons(PORT);    int on=1;    setsockopt(SocketListen,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));    if(bind(SocketListen,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr_in))==-1)    {        perror("bind");        exit(1);        //exit(1);    }    if(listen(SocketListen,20)==-1)    {        perror("listen");        exit(1);        //exit(1);    }    printf("listen port %d\n", PORT);    /*    int res2;    pid_t child;    int r_size;    socklen_t sin_size;    pthread_t thread_write;   //线程id,即线程标识符    while(1)    {        printf("listen port %d\n", PORT);        memset(SocketTxBuf,0,100);        sin_size = sizeof(struct sockaddr_in);        if((SocketConnect=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1)        {            printf("Accept error\n");            break;            //exit(1);        }        else        {            printf("Server get connection from %s\n",inet_ntoa(client_addr.sin_addr));            res2=pthread_create(&thread_write,NULL,thrd_write,NULL);       //创建线程            if(res2!=0)            {                printf("Pthread socket write create fail\n");                //exit(res2);            }            while(1)        //读取客户端信息            {                //char SocketBuf[100];                int r_size=0;                if((r_size=recv(SocketConnect,SocketRxBuf,SOCKETBUFSIZE,0))==-1)                {                    printf("Read error\n");                    close(SocketConnect);                    break;                    //exit(1);                }                else if(r_size==0)//客户端主动断开链接                {                    printf("client close succeed!\n");                    close(SocketConnect);                    break;                }                else if(r_size>0)                {                    printf("Server Received len %d::%s\n",r_size,SocketRxBuf);                }            }        }    }    close(sockfd);    */}/************************************************** Function    : process_client()* Description : 处理客户端连接函数* Calls       :* Called By   : main()* Input       :* Output      :* Return      :*************************************************/void process_client(int* socketcon ,char *recvbuf, int len){    //char sendbuf[SOCKETBUFSIZE];    printf("Received client message: %s\n", recvbuf);    if(send(*socketcon,recvbuf,len,0)==-1)    {        printf("socket send Error\n");        close(*socketcon);                //exit(1);    }}


0 0
原创粉丝点击