select函数
(select使用及原理详细说明 http://blog.csdn.net/rankun1/article/details/69815522)
该函数可同时等待多个描述符状态改变,在有描述符状态改变(一个或是多个)或指定的超时时间到了之后才唤醒它。我们调用select告知内核对哪些描述符(就读、写或异常条件)感兴趣以及等待多长时间。我们感兴趣的描述字不局限于套接口,任何描述符都可以使用select来测试。
select函数原型:
#include<sys/select.h>
#include<sys/time.h>
int select (int maxfd , fd_set *readset ,fd_set *writeset, fd_set *exceptionset , const struct timeval * timeout);
返回:就绪描述字的正数目,0——超时,-1——出错
select函数的参数介绍:
maxfd表示所有监视集合(读,写,异常)中描述符的最大范围,其值应该为所有监视集合中最大的描述符+1
readset,writeset,exceptionset指定我们要让内核测试读、写、异常条件的描述符号集合,
timeout 指定等待超时时间。
timeval结构:
struct timeval {
long tv_sec; //seconds
long tv_usec ; //microseconds
}
timeval参数有三种可能值:1、NULL:代表永远等待下去,相当于完全阻塞。2、一个固定的值,代表等待一段固定的时间。3、timeval的属性值为0,表示根本不等待,检查描述字之后立即返回,也就是说事非阻塞的。
fd_set结构:
fd_set结构表示一个描述字集。它典型的应该以一个整数数组来表示,其中每个整数中的每一位对应一个描述字。关于fd_set有以下四个宏:
void FD_ZERO(fd_set *fdset); /* clear all bits in fdset */
void FD_SET(int fd, fd_set *fdset); /* turn on the bit for fd in fdset */
void FD_CLR(int fd, fd_set *fdset); /* turn off the bit for fd in fdset */
int FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset ? */
select函数修改由指针readset,writeset,exceptionset所指向的描述字集,因而这三个参数都是值-结果参数。也就是说,在select函数执行过程中,会修改其中的值。调用该函数时,我们指定关心的描述字的值,该函数返回时,结果指示哪些描述字已就绪。该函数返回后,我们使用FD_ISSET来测试fd_set数据类型中的描述字。描述字集中任何与未就绪的描述字对应的位返回时均清为0.为此,每次重新调用select函数中,我们都得再次把所有描述字集合中的所关心的位置为1。这也是在稍候的通信例子里,我们设置resset和allset两个集合的原因所在。
select函数返回某个套接口就绪的条件:
select例子:一个简单的TCP回射服务器程
本程序只是为了简单介绍select的基本用法,其中对client数组的遍历效率太低,请无视
//#include <cstdio>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#define BUF_LEN 1024
#define SERV_PORT 6000
#define FD_SIZE 100
#define MAX_BACK 100
int main(int argc, char ** argv)
{
int listenfd;//监听socket
int client[FD_SIZE];//保存select监听的套接字
fd_set allset;//select监听的集合
struct sockaddr_in servaddr, chiaddr;//服务器地址和客户端地址
//socket创建监听socket
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("Create socket Error : %d\n", errno);
exit(EXIT_FAILURE);
}
//初始化服务器地址信息
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
//bind绑定监听socket与服务器addr
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
{
printf("Bind Error : %d\n", errno);
exit(EXIT_FAILURE);
}
//监听
if (listen(listenfd, MAX_BACK) == -1)
{
printf("Listen Error : %d\n", errno);
exit(EXIT_FAILURE);
}
//初始化套接字数组
for (int i = 0; i < FD_SIZE; i++) //首先置为全-1
{
client[i] = -1;
}
while (1)
{
int nready = 0;
int nMaxFd = listenfd;
//select每次返回都会将集合中无信号的套接字清空,所以要重新加入
//初始化select监听集合
FD_ZERO(&allset);
//服务器套接字加入select监听集合
FD_SET(listenfd, &allset);
//循环加入客户端套接字
for (int i = 0; i < FD_SIZE; i++)
{
if (-1 != client[i])
{
if (nMaxFd < client[i])
{
nMaxFd = client[i];
}
FD_SET(client[i], &allset);
}
}
//开始select,select第一个参数要求为所有监听集合中最大fd+1
if ((nready = select(nMaxFd + 1, &allset, NULL, NULL, NULL)) == -1)
{
printf("Select Erorr : %d\n", errno);
exit(EXIT_FAILURE);
}
if (nready <= 0) //如果所有套接字都没有通知,继续select
{
continue;
}
//如果是服务端套接字有通知
if (FD_ISSET(listenfd, &allset))
{
int connfd;//保存连接上来的客户端
socklen_t clilen = sizeof(chiaddr);
printf("Start doing... \n");
if ((connfd = accept(listenfd, (struct sockaddr *)&chiaddr, &clilen)) == -1)
{ //!> accept 返回的还是套接字
printf("Accept Error : %d\n", errno);
continue;
}
//保存客户端套接字
bool bSave = false;//是否保存
for (int i = 0; i < FD_SIZE; i++)
{
if (-1 == client[i])
{
client[i] = connfd;
bSave = true;
break;
}
}
if (!bSave)//客户端太多了
{
printf("To many ... ");
close(connfd);
continue;
}
}
//循环检查哪个客户端套接字有通知
for (int i = 0; i < FD_SIZE; i++)
{
int sockClient = client[i];
if (0 > sockClient)
{
continue;
}
char buf[BUF_LEN] = { 0 };
if (FD_ISSET(sockClient, &allset))
{
int nRead = 0;
nRead = read(sockClient, buf, BUF_LEN);
if (nRead < 0)
{
printf("Error!\n");
close(sockClient); //说明在这个请求端口上出错了!
client[i] = -1;
continue;
}
if (nRead == 0)
{
printf("Error!\n");
close(sockClient); //客户端退出
client[i] = -1;
continue;
}
printf("Server Recv: %s\n", buf);
if (strcmp(buf, "q") == 0) //!> 客户端输入“q”退出标志
{
printf("Error!\n");
close(sockClient); //客户端退出
client[i] = -1;
continue;
}
printf("Server send : %s\n", buf);
write(sockClient, buf, nRead); //!> 读出来的写进去
}
}
}
return 0;
}
启动服务程序,新启一个终端,使用nc命令测试一下吧
nc 127.0.0.1 6000