嵌入式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的参数:
(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 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); }}
- 嵌入式linux:linux select函数与socket
- 嵌入式 select函数详解Linux
- linux socket select 函数分析
- linux socket的select函数的解析与事例
- linux socket的select函数例子
- linux socket的select函数示例
- linux socket的select函数例子
- linux socket的select函数例子
- linux socket的select函数例子
- linux socket的select函数例子
- linux socket的select函数例子
- linux socket的select函数例子
- linux socket的select函数例子
- linux socket的select函数例子
- linux socket 的select函数例子
- linux socket的select函数例子
- linux socket的select函数例子
- linux socket的select函数例子
- 十五、顺序执行动作重复动作
- 在数组中交换变量的陷阱
- 自定义对话框
- adb常用命令整理备忘录
- eclipse中jrebel安装+破解+配置
- 嵌入式linux:linux select函数与socket
- jQuery--jq对象和dom对象
- 新增字段default字段,老数据是否有默认值
- Android Context解析
- PopUpWindow使用详解(一)——基本使用
- ipvsadm 的命令参考
- java.lang.ClassNotFoundException:org.springframework.web.context.ContextLoaderListener问题解决
- Android RecyclerView 使用解析,替代ListView
- LWIP UDP socket编程 可以指定本地端口号及发送长度不能太长问题分析