selcet
特点:
1.用来让我们的程序监视多个文件句柄的状态变化,关心文件描述符的读、写、异常。
2.I/o当中select函数只负责等,一次等待多个文件描述符。
select函数原型inc select(int nfds,fd_set *readfds,fd _set *writefds,fd _set *exceptfds, struct timeval *timeout)
返回值:执行成功返回文件描述符状态已改变个数;如果返回0代表在描述符状态改变前已超过timeout时间,没有返回;有错误发生返回-1;
int nfds最大文件描述符值+1;如果需要关心1号,2号,3号,9号,则应该填10;
后四个参数全部都是输入输出型参数。
最后一个参数struct timeval *timeout虽然是输入输出型参数,但和事件没多大关系, 如果设置为NULL,一直等下去;设置成0就不等(非阻塞轮询);设置为有效时间,隔一段时间醒来一次;
中间三个参数是真正意义上的输入输出型参数,输入指的是关心特定文件描述符的指定的事件(关心哪些事件);输出时代表指定文件描述符上的指定事件已经发生或者没发生(哪些事件发生了);
3.fd叫文件描述符,fd _set叫文件描述符集,用位图表示非常合适。
4.每次调用select,都必须对参数重新设定(因为一个变量被定义输入又是输出)。
5.select能处理的文件描述符个数是有限的。
makefile
select_server:select_server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f select_server
select_server.c
#include <stdio.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/socket.h>
#define SIZE sizeof(fd_set)*8
int fds[SIZE];//定义文件描述符集合的数组,大小为1024;
static void usage(const char *proc)//提示手册,提示你在命令行如何输入
{
printf("Usage: %s [local_ip] [local_port]\n",proc);
}
//获取文件描述符
int startup(const char *ip, int port)
{
int sock = socket(AF_INET,SOCK_STREAM, 0);
if(sock < 0)//创建套接字失败
{
perror("socket");
exit(2);
}
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)//调用bind绑定本地地址和端口(服务器)
{
perror("bind");
exit(3);
}
if(listen(sock,10)<0)
{
perror("listen");
exit(4);
}
return sock;
}
int main(int argc, char *argv[])
{
if(argc != 3)//命令行输入错误
{
usage(argv[0]);
return 1;
}
//监听套接字:获取是否有连接事件
int listen_sock = startup(argv[1], atoi(argv[2]));
//把要接收文件描述符集合的数组所有成员设置为无效
int nums = sizeof(fds)/sizeof(fds[0]);
int i = 0;
for(;i < nums; i++)
{
fds[i] = -1;//-1为无效
}
//
fds[0] = listen_sock;
while(1)
{
int max_fd = -1;//文件描述符值
fd_set rfds;//定义读文件描述符集
FD_ZERO(&rfds);//对文件描述符集初始化,放在循环内,防止select重复调用
//遍历数组,知道哪些文件描述符需要添加至文件描述符集
for(i = 0; i < nums; i++)
{
if(fds[i] == -1)
{
continue;
}
FD_SET(fds[i],&rfds);
if(max_fd < fds[i])
{
max_fd = fds[i];
}
}
struct timeval timeout = {0,0};//对timeout进行初始化,timeout参数为结构体类型,&timeout就是0;
//等(select函数)
switch(select(max_fd+1, &rfds, NULL, NULL, /*&timeout*/NULL))
{
case 0:
printf("timeout ...\n");
break;
case -1:
perror("select");
break;
default:
{
//at least one fd ready!
//至少有一个文件描述符就绪
for(i = 0; i<nums;i++)
{
if(fds[i] == -1)
{
continue;
}
//listen_sock 就绪,说明来了一个新连接
if(i == 0 && FD_ISSET(listen_sock, &rfds))
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_sock = accept(listen_sock,\
(struct sockaddr*)&client,\
&len);//调用accept从已连接队列中提取客户连接
//accept失败
if(new_sock < 0)
{
perror("accept");
continue;
}
//获得新连接
printf("get a new client:[%s:%d]\n",\
inet_ntoa(client.sin_addr),\
ntohs(client.sin_port));
//此时并不能直接读写,accept只能保证连接获得,读写事件是否就绪却并不知道(也就是说下一个连接有没有进来)
//重新让select负责,添加至读文件描述符集
int j = 1;//第一个已被占用,所以j =1;
for(; j < nums; j++)
{
if(fds[j] == -1)
{
break;
}
}
if(j == nums)//rfds已经满了
{
printf("client is full!\n");
close(new_sock);
continue;
}
else
{
fds[j] = new_sock;
}
}else if(i != 0 && FD_ISSET(fds[i], &rfds))//普通套接字,普通事件就绪
{
//normal fd ready
char buf[1024];
ssize_t s = read(fds[i],buf,\
sizeof(buf)-1);
if(s > 0)//读成功
{
buf[s] = 0;
printf("client# %s\n",buf);
}
else if(s == 0)//client关闭
{
close(fds[i]);
fds[i] = -1;
printf("client is quit!\n");
}
else//读失败
{
perror("read");
close(fds[i]);
fds[i] = -1;
}
}
else
{
//文件描述符没有就绪
}
}
}
break;
}
}
return 0;
}
select_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
static void usage(const char *proc)
{
printf("Usage: %s [server_ip] [server_port]\n",proc);
}
//./tcp_client server_ip server_port
int main(int argc, char *argv[])
{
if(argc != 3)
{
usage(argv[0]);
return 1;
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0)
{
perror("connect");
return 3;
}
char buf[1024];
while(1)
{
printf("Please Enter#");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf) - 1);
if(s > 0)
{
buf[s-1]= 0;
write(sock, buf, strlen(buf));
//ssize_t _s = read(sock, buf, sizeof(buf)-1);
//if(_s > 0)
//{
// buf[_s] = 0;
// printf("server echo# %s\n",buf);
//}
}
}
close(sock);
return 0;
}
select服务器优缺点(与多进程/多线程服务器进行对比)
与多进程/多线程服务器进行对比 它的优点在于:
1)不需要建立多个线程、进程(占资源少,单进程)就可以实现一对多的通信。
2)可以同时等待多个文件描述符,用户量较多时,效率比起多进程多线程来说要高很多。
与多进程/多线程服务器进行对比 它的缺点在于:
1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 ,循环次数有点多;
2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 。
3)select支持的文件描述符数量是有上限的,默认是1024;
4)select是输入输出型参数,输入输出是同一个变量,会频繁设定。