嵌入式linux:linux select函数与socket
2019-07-13 01:01发布
生成海报
转自:http://blog.csdn.net/lingfengtengfei/article/details/12392449
在Linux中,我们可以使用select函数实现I/O端口的复用,传递给
select函数的参数会告诉内核:
•我们所关心的文件描述符
•对每个描述符,我们所关心的状态。(我们是要想从一个文件描述符中读或者写,还是关注一个描述符中是否出现异常)
•我们要等待多长时间。(我们可以等待无限长的时间,等待固定的一段时间,或者根本就不等待)
从 select函数返回后,内核告诉我们一下信息:
•对我们的要求已经做好准备的描述符的个数
•对于三种条件哪些描述符已经做好准备.(读,写,异常)
有了这些返回信息,我们可以调用合适的I/O函数(通常是
read 或 write),并且这些函数不会再阻塞.
#include
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
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);
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("超时
"); /* 说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */
else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */
printf("ret=%d
", ret); /* ret这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的就是句柄的总和了 */
/* 这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */
if(FD_ISSET(socket, &rdfds)) { /* 先判断一下socket这外被监视的句柄是否真的变成可读的了 */
/* 读取socket句柄里的数据 */
recv(...);
}
}
注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1
下面是一个TCP服务器的例子
main.c
#include
#include
#include //open() close()
#include
#include
#include //read() wrie()
#include
#include
#include
#include
#include
#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!
");
fd=open(DEVICE_NAME,O_RDWR);
printf("SPIfd=%d
",fd);
if(fd==-1)
{
printf("open device %s error
",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;
rset = allset;//文件描述符备份,避免每次进行初始化。
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!
",SECOND,USECOND);
}
else if(nready>0)//在等待时间内socket文件描述符(SocketListen或SocketConnect)有变化 可读写或出错
{
if (FD_ISSET(SocketListen, &rset))//先判断一下SocketListen这外被监视的句柄是否真的变成可读的了
{ //检测是否有新客户端请求
printf("Accept a connection.
");
//调用accept,返回服务器与客户端连接的socket描述符
sin_size = sizeof(struct sockaddr_in);
if ((SocketConnect = accept(SocketListen, (struct sockaddr *)&client_addr, (socklen_t *) & sin_size)) == -1)
{
perror("Accept() error
");
continue;
}
FD_SET(SocketConnect, &allset); //将新socket连接放入select监听集合
if (SocketConnect > maxfd)
{
maxfd = SocketConnect; //确认maxfd是最大描述符
}
}
// 有客户连接,检测是否有数据
if (FD_ISSET(SocketConnect, &rset))
{
printf("Receive from connect SocketConnect[%d].
", SocketConnect);
if ((SocketRxlen = recv(SocketConnect, SocketRxBuf, SOCKETBUFSIZE, 0)) == 0)
{ //从客户端socket读数据,等于0表示网络中断
close(SocketConnect); //关闭socket连接
printf("SocketConnect closed.
");
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
",TxBuf[0],TxBuf[1],TxBuf[2],TxBuf[3]);
printf("APP:RX date is %d %d %d %d
",RxBuf[0],RxBuf[1],RxBuf[2],RxBuf[3]);
*/
}
close(SocketListen);
printf("********************
");
printf("Close SpiApp succeed!
");
}
return 0;
}
socket.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
",strlen(SocketRxBuf));
if(send(SocketConnect,SocketRxBuf,strlen(SocketRxBuf),0)==-1) //写功能
{
printf("socket send Error
");
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
");
exit(1);
}
printf("SocketListen fd=%d
",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
", PORT);
/*
int res2;
pid_t child;
int r_size;
socklen_t sin_size;
pthread_t thread_write; //线程id,即线程标识符
while(1)
{
printf("listen port %d
", 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
");
break;
//exit(1);
}
else
{
printf("Server get connection from %s
",inet_ntoa(client_addr.sin_addr));
res2=pthread_create(&thread_write,NULL,thrd_write,NULL); //创建线程
if(res2!=0)
{
printf("Pthread socket write create fail
");
//exit(res2);
}
while(1) //读取客户端信息
{
//char SocketBuf[100];
int r_size=0;
if((r_size=recv(SocketConnect,SocketRxBuf,SOCKETBUFSIZE,0))==-1)
{
printf("Read error
");
close(SocketConnect);
break;
//exit(1);
}
else if(r_size==0)//客户端主动断开链接
{
printf("client close succeed!
");
close(SocketConnect);
break;
}
else if(r_size>0)
{
printf("Server Received len %d::%s
",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
", recvbuf);
if(send(*socketcon,recvbuf,len,0)==-1)
{
printf("socket send Error
");
close(*socketcon);
//exit(1);
}
}
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮