您的位置:首页 > 运维架构 > Linux

嵌入式linux:linux select函数与socket

2016-03-01 16:58 696 查看
转自: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的参数:
(1)intmaxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错。
说明:对于这个原理的解释可以看上边fd_set的详细解释,fd_set是以位图的形式来存储这些文件描述符。maxfdp也就是定义了位图中有效的位的个数。
(2)fd_set*readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读;如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
(3)fd_set*writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
(4)fd_set*errorfds同上面两个参数的意图,用来监视文件错误异常文件。
(5)structtimeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即
select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
说明:
函数返回:
(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最大可以对应8个fd。
(1)执行fd_set set;FD_ZERO(&set);则set用位表示是0000,0000。
(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
(3)若再加入fd=2,fd=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返回后循环array(FD_ISSET判断是否有时间发生)。

基本原理



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 3333

extern 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);
    }
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: