前言:
select系统调用的用途是:在指定的一段时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。
select API:
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
FD_ZERO(fd_set *fd_set); //清除fdset的所有位
FD_SET(int fd, fd_set *fd_set); //设置fdset的位fd
FD_CLR(int fd, fd_set *fd_set): //清除fdset的位fd
int FD_ISSET(int fd, fd_set *fdset); //测试fdset的位fd是否被设置(文件描述符fd是否就绪)
文件描述符的就绪条件:
--可读
1、socket内核接收缓冲区中的字节数大于或等于其低水位标记SO_RCVLOWAT。此时我们可以无阻塞地读取该文件描述符,并且读操作返回的字节数大于0。
2、socket通信的对方关闭连接。此时对该socket的读操作将返回0。
3、socket上有未处理的错误。
--可写
1、socket内核发送缓冲区中的可用字节数大于或等于其低水位标记SO_SNDLOWAT,此时我们可以无阻塞地写该socket,并且写操作返回的字节数大于0。
2、socket的写操作被关闭。
3、socket使用非阻塞connect连接成功或者失败(超时)之后。
4、socket上有未处理的错误。
编码实例
服务端select_s.c
/*
*单进程IO多路复用select模型
* */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <unistd.h>
#include <sys/types.h>
#define IPADDRESS "127.0.0.1"
#define PORT 8787
#define MAXLINE 1024
#define LISTENQ 5
//创建套接字并进行绑定
static int socket_bind(const char* ip,int port);
//IO多路复用select
static void do_select(int listenfd);
//处理多个连接
static void handle_connection(int *connfds,int num,fd_set *prset,fd_set *pallset);
int main(int argc,char *argv[])
{
int listenfd,connfd,sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
listenfd = socket_bind(IPADDRESS,PORT);
listen(listenfd,LISTENQ);
do_select(listenfd);
return 0;
}
static int socket_bind(const char* ip,int port)
{
int listenfd;
struct sockaddr_in servaddr;
listenfd = socket(AF_INET,SOCK_STREAM,0);
if (listenfd == -1)
{
perror("socket error:");
exit(1);
}
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET,ip,&servaddr.sin_addr);
servaddr.sin_port = htons(port);
if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1)
{
perror("bind error: ");
exit(1);
}
return listenfd;
}
static void do_select(int listenfd)
{
int connfd,sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
fd_set rset,allset;
int maxfd,maxi;
int i;
int clientfds[FD_SETSIZE]; //保存客户连接描述符
int nready;
//初始化客户连接描述符
for (i = 0;i < FD_SETSIZE;i++)
clientfds[i] = -1;
maxi = -1;
FD_ZERO(&allset);
//添加监听描述符
FD_SET(listenfd,&allset);
maxfd = listenfd;
//循环处理
for ( ; ; )
{
rset = allset;
//获取可用描述符的个数
nready = select(maxfd+1,&rset,NULL,NULL,NULL);
if (nready == -1)
{
perror("select error:");
exit(1);
}
//测试监听描述符是否准备好,如果就绪的是listenfd,则接收新连接。
if (FD_ISSET(listenfd,&rset))
{
cliaddrlen = sizeof(cliaddr);
//接收新的连接
if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1)
{
if (errno == EINTR)
continue;
else
{
perror("accept error:");
exit(1);
}
}
fprintf(stdout,"accept a new client: %d:%d\n", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
//将新的连接描述符添加到数组中
for (i = 0;i <FD_SETSIZE;i++)
{
if (clientfds[i] < 0)
{
clientfds[i] = connfd;
break;
}
}
if (i == FD_SETSIZE)
{
fprintf(stderr,"too many clients.\n");
exit(1);
}
//将新的描述符添加到读描述符集合中
FD_SET(connfd,&allset);
//描述符个数
maxfd = (connfd > maxfd ? connfd : maxfd);
//记录客户连接套接字的个数
maxi = (i > maxi ? i : maxi);
if (--nready <= 0)
continue;
}
//如果不是连接监听套接字,则处理已连接客户请求
handle_connection(clientfds,maxi,&rset,&allset);
}
}
static void handle_connection(int *connfds,int num,fd_set *prset,fd_set *pallset)
{
int i,n;
char buf[MAXLINE];
memset(buf,0,MAXLINE);
for (i = 0;i <= num;i++)
{
if (connfds[i] < 0)
continue;
//测试客户描述符是否准备好,即是否有新的消息可读。
if (FD_ISSET(connfds[i],prset))
{
//接收客户端发送的信息
n = read(connfds[i],buf,MAXLINE);
if (n == 0)
{
close(connfds[i]);
FD_CLR(connfds[i],pallset);
connfds[i] = -1;
continue;
}
printf("read msg is: ");
write(STDOUT_FILENO,buf,n);
//向客户端发送buf
write(connfds[i],buf,n);
}
}
}
客户端select_c.c
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#define MAXLINE 1024
#define IPADDRESS "127.0.0.1"
#define SERV_PORT 8787
#define max(a,b) (a > b) ? a : b
static void handle_connection(int sockfd);
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
//处理连接描述符
handle_connection(sockfd);
return 0;
}
static void handle_connection(int sockfd)
{
char sendline[MAXLINE],recvline[MAXLINE];
int maxfdp,stdineof;
fd_set rset;
int n;
FD_ZERO(&rset);
for (; ;)
{
//添加标准输入描述符
FD_SET(STDIN_FILENO,&rset);
//添加连接描述符
FD_SET(sockfd,&rset);
maxfdp = max(STDIN_FILENO,sockfd);
//进行轮询
select(maxfdp+1,&rset,NULL,NULL,NULL);
//测试连接套接字是否准备好
if (FD_ISSET(sockfd,&rset))
{
n = read(sockfd,recvline,MAXLINE);
if (n == 0)
{
fprintf(stderr,"client: server is closed.\n");
close(sockfd);
FD_CLR(sockfd,&rset);
}
write(STDOUT_FILENO,recvline,n);
}
//测试标准输入是否准备好
if (FD_ISSET(STDIN_FILENO,&rset))
{
n = read(STDIN_FILENO,sendline,MAXLINE);
if (n == 0)
{
FD_CLR(STDIN_FILENO,&rset);
continue;
}
write(sockfd,sendline,n);
}
}
}