select服务器的原理
select服务器实际上是对于用来标识文件描述符状态(“等”的状态结束 或正在“等”)的 fd_set变量 ret 进行检测(即select函数),对于已经完成“等”状态的文件描述符,即将开始I/O操作并不存在阻塞的问题,这样就大大提高了服务器运行效率
对于文件描述符集的操作函数有
FD_ZERO(fd_set* ret)
其功能: 初始化fd(标志位全部置0)
FD_SET(int fd, fd_set* ret)
其功能:设置描述词组ret中相关fd的位
FD_ISSET(int fd, fd_set* ret)
其功能:检测描述词组ret中相关fd的位是否被设置
FD_CLR(int fd, fd_set* ret)
其功能:清除描述词组ret中的相关fd的位
select函数
- 参数列表
select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout)
其中 nfds 是需要监视的最大的文件描述符+1
readfds、writefds、exceptfds分别代表可读文件描述符集合,可写文件描述符集合,异常文件描述符集合
timeout 用于描述一段时间长度,如果在这个时间内需要监视的文件描述符没有事件发生则函数返回0
重点:三个文件描述符集合都是输入输出型参数,作为输出时,返回对应事件就绪的文件描述符集,而select返回后会把以前加入的但并无事件发生的fd清空,基于这一点,我们必须另外创建某种数据结构来保存状态改变之前的文件描述符集的信息(代码中用一个数组来保存相应的信息)
- 返回值
执行成功返回文件描述符状态改变的个数
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
///////////////////
static void usage(const char* pro)
{
printf("%s [local_ip][local_port]\n", pro);
}
//套接字的创建 绑定 和 设置监听状态,不做解释
int startup(const char* _ip, int _port)
{
// chuangjian taojiezi
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
perror("socket");
exit(3);
}
// bangding
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip);
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){
perror("bind");
exit(4);
}
// jianting
if(listen(sock, 10) < 0){
perror("listen");
}
return sock;
}
//用于保存监视的文件描述符集
int fd_array[sizeof(fd_set)*8];
int main(int argc, char* argv[])
{
if(argc != 3)
{
usage(argv[0]);
return 1;
}
int listen_sock = startup(argv[1], atoi(argv[2]));
int maxsz = sizeof(fd_array)/sizeof(fd_array[0]);
int i = 0;
//初始化数组,-1为无效值
for(; i< maxsz; i++)
{
fd_array[i] = -1;
}
fd_array[0] = listen_sock;
while(1)
{
//创建文件描述符集,并初始化
fd_set fd;
FD_ZERO(&fd);
//用于标识nfds,即需要监视的最大的文件描述符值+1
int maxfd = -1;
//
for(i = 0; i < maxsz; ++i)
{
//set valid bit
if(fd_array[i] == -1)
continue;
else
{
//将数组中有效的文件描述符添加监视
FD_SET(fd_array[i], &fd);
//更新nfds
if(maxfd < fd_array[i])
maxfd = fd_array[i];
}
}//for结尾
// 初始化outime
struct timeval outime = {5,0};
//只监视读事件
int s = select(maxfd+1, &fd, NULL, NULL, &outime);
switch(s)
{
case 0:
printf("no client ..\n");
break;
case -1:
perror("select");
break;
default:
{
for(i=0;i<maxsz;++i)
{
if(fd_array[i] < 0)
continue;
// 有新链接申请,即listen_sock读事件就绪
if(i == 0 && FD_ISSET(listen_sock ,&fd))
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
//将new_sock添加进数组,并且寻找最小的未被使用的空间
int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
if(new_sock < 0)
{
perror("accept");
continue;
}
printf("get a new client: %s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
int j = 1;
for(; j < maxsz; ++j)
{
if(fd_array[j] < 0)
break;
}
//服务器载客量已满
if(j == maxsz)
{
printf("fd_set full!!!\n");
close(new_sock);
}else
//其他读事件就绪,开始读取数据(new_sock加入数组后,循环开始的地方被添加入需要监视的文件描述符集,然后在这里开始处理)
{
fd_array[j] = new_sock;
printf("changge of fd_array: ");
}
}
//other file_desriptor is ready
else if(i!=0 && FD_ISSET(fd_array[i], &fd))
{
char buf[1024];
ssize_t s = read(fd_array[i], buf, sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("client->%s\n", buf);
}
else if(s == 0)
{
printf("client is qute...\n");
close(fd_array[i]);
fd_array[i] = -1;
}
else
{
printf("client is error!!!\n");
close(fd_array[i]);
fd_array[i] = -1;
}
}
}//for
}//default
break;
}
}//while
return 0;
}