select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型,原型:
#include <sys/time.h>
#include <unistd.h>
int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);
参数maxfd是需要监视的最大的文件描述符值+1;
参数rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合;
参数struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0;
返回值:返回对应位仍然为1的fd的总数。三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位仍然为1。
理解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被清空。
FD_ZERO(fd_set *fdset);将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化。
FD_SET(int fd, fd_set *fdset);用于在文件描述符集合中增加一个新的文件描述符。
FD_CLR(int fd, fd_set *fdset);用于在文件描述符集合中删除一个文件描述符。
FD_ISSET(int fd, fd_set *fdset);用于测试指定的文件描述符是否在该集合中。
timeout用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。有三种可能:
1.timeout=NULL(阻塞:select将一直被阻塞,直到某个文件描述符上发生了事件。
2.timeout所指向的结构设为非零时间(等待固定时间:如果在指定的时间段里有事件发生或者时间耗尽,函数均返回)
3.timeout所指向的结构,时间设为0(非阻塞:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生
#include <sys/time.h>
#include <unistd.h>
int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);
参数maxfd是需要监视的最大的文件描述符值+1;
参数rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合;
参数struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0;
返回值:返回对应位仍然为1的fd的总数。三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位仍然为1。
理解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被清空。
FD_ZERO(fd_set *fdset);将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化。
FD_SET(int fd, fd_set *fdset);用于在文件描述符集合中增加一个新的文件描述符。
FD_CLR(int fd, fd_set *fdset);用于在文件描述符集合中删除一个文件描述符。
FD_ISSET(int fd, fd_set *fdset);用于测试指定的文件描述符是否在该集合中。
timeout用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。有三种可能:
1.timeout=NULL(阻塞:select将一直被阻塞,直到某个文件描述符上发生了事件。
2.timeout所指向的结构设为非零时间(等待固定时间:如果在指定的时间段里有事件发生或者时间耗尽,函数均返回)
3.timeout所指向的结构,时间设为0(非阻塞:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生
读取键盘输入实例
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
int main()
{
char buf[10]="";
fd_set rdfds;
struct timeval tv;
int ret;
FD_ZERO(&rdfds); //清空的rdfs文件集合
FD_SET(0,&rdfds); //将标准输入加入到rdfds
while(1)
{
ret = select(1,&rdfds,NULL,NULL,NULL);//阻塞等待用户输入
if(ret<0)
printf("selcet failed\n");
else if(ret == 0)
printf("seletc timeout\n");
else
printf("ret = %d\n",ret);
if(FD_ISSET(0,&rdfds)) //测试标准输入是否可读
{
printf("reading data\n");
fread(buf,9,0,stdin); //从标准输入中读取数据
}
write(1,buf,strlen(buf));//将输入的数据在屏幕显示出来
}
return 0;
}
web服务器实例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MYPORT 88960 // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold
#define BUF_SIZE 200
int fd_A[BACKLOG]; // accepted connection fd
int conn_amount; // current connection amount
int main(void)
{
int sock_fd, new_fd; //sock_fd监听套接字, new_fd新连接的套接字
struct sockaddr_in server_addr; //服务端地址信息
struct sockaddr_in client_addr; //客户端地址信息
socklen_t sin_size;
int yes = 1;
char buf[BUF_SIZE];
int ret;
int i;
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)//新建监听套接字
{
perror("socket error!\n");
exit(1);
}
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
{
perror("setsockopt");
exit(1);
}
server_addr.sin_family = AF_INET; //host byte order
server_addr.sin_port = htons(MYPORT); //short, network byte order
server_addr.sin_addr.s_addr = INADDR_ANY; //automatically fill with my IP
memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));
if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)//绑定套接字和服务器地址
{
perror("bind");
exit(1);
}
if (listen(sock_fd, BACKLOG) == -1)//侦听监听套接字
{
perror("listen");
exit(1);
}
fd_set fdsr;
int maxsock;
struct timeval tv;
conn_amount = 0;
sin_size = sizeof(client_addr);
maxsock = sock_fd;
while (1) {
FD_ZERO(&fdsr);//清空文件描述符集合
FD_SET(sock_fd, &fdsr);//将监听套接字加入到文件描述符集合中
tv.tv_sec = 30;//设置超时时间
tv.tv_usec = 0;
for (i = 0; i < BACKLOG; i++) {
if (fd_A[i] != 0) {
FD_SET(fd_A[i], &fdsr); //将新建的连接套接字加入到文件描述符集合
}
}
ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);//开始检查文件描述符集合中所有的文件描述符
if (ret < 0) {
perror("select" failed!\n);
break;
} else if (ret == 0) {
printf("timeout\n");
continue;
}
//开始匹配文件描述符集合中所有的文件描述符
for (i = 0; i < conn_amount; i++) {
if (FD_ISSET(fd_A[i], &fdsr)) {
ret = recv(fd_A[i], buf, sizeof(buf), 0);
char str[] = "Good,very nice!\n";
send(fd_A[i],str,sizeof(str) + 1, 0);
if (ret <= 0) { // client close
printf("client[%d] close\n", i);
close(fd_A[i]);
FD_CLR(fd_A[i], &fdsr);
fd_A[i] = 0;
} else { // receive data
if (ret < BUF_SIZE)
memset(&buf[ret], '\0', 1);
printf("client[%d] send:%s\n", i, buf);
}
}
}
// 检查是否有新的连接请求
if (FD_ISSET(sock_fd, &fdsr)) {
new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);
if (new_fd <= 0) {
perror("accept");
continue;
}
//将新的连接套接字加入到fd_A中
if (conn_amount < BACKLOG) {
fd_A[conn_amount++] = new_fd;
printf("new connection client[%d] %s:%d\n", conn_amount,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
if (new_fd > maxsock)
maxsock = new_fd;
}
else {
printf("max connections arrive, exit\n");
send(new_fd, "bye", 4, 0);
close(new_fd);
break;
}
}
showclient();
}
// close other connections
for (i = 0; i < BACKLOG; i++) {
if (fd_A[i] != 0) {
close(fd_A[i]);
}
}
exit(0);
}