select將IO擴充複用,一個IO監聽多個文件,當至少有一個文件有事件時select纔會返回。但還是受句柄限制(fd句柄索引文件,走虛擬文件系統,監聽文件的輸入輸出,文件多時會很耗時),和poll基本一致
select雖然能實現併發,但逃離不了句柄數量的限制,epoll可以解決(將文件轉化爲event)。所謂集羣就是多epoll,做QOS負載均衡
select後fd_set會被更改,所以每次都要清空fd_set再添加
這裏將標準輸入fd和新連接fd放入fd_set。tv設置超時時間
fd放入fd_set前最好設置成非阻塞(默認是非阻塞)
select之後數據並未發送,還需要遍歷fd後,read\write才發送
實現方法二:
將監聽句柄也放入文件集,一有新鏈接就把新鏈接放入select,實現多鏈接通信,增加fd_set容量的利用效率
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#define _BACKLOG_ 5 //監聽隊列裏允許等待的最大值
int fds[20];//用來存放需要處理的IO事件
int creat_sock(char *ip,char *port)//集成bind和listen的方法
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("creat_sock error");
exit(1);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(port));
local.sin_addr.s_addr = inet_addr(ip);
if( bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
perror("bind");
exit(2);
}
if(listen(sock,_BACKLOG_) < 0 ){
perror("listen");
exit(4);
}
return sock;
}
int main(int argc,char* argv[])
{
if(argc != 3){
printf("Please use : %s [ip] [port]\n",argv[0]);
exit(3);
}
int listen_sock = creat_sock(argv[1],argv[2]);
size_t fds_num = sizeof(fds)/sizeof(fds[0]);//清空數組
size_t i = 0;
for(;i < fds_num;++i)
{
fds[i] = -1;//只要是-1的位置就有空位
}
int max_fd = listen_sock;//最開始fd最大值是listen_sock
fd_set rset;
while(1)
{
FD_ZERO(&rset);
FD_SET(listen_sock,&rset);
max_fd = listen_sock;
//struct timeval timeout = {20 , 0};//超時時間20s
fds[0]=listen_sock;
size_t i = 0;
for(i=1;i < fds_num;++i)
{
if(fds[i] > 0 ){//只要大於0,就有fd
FD_SET(fds[i] ,&rset);
if(max_fd < fds[i])//冒泡得到最大值
{
max_fd = fds[i];
}
}
}
switch(select(max_fd+1,&rset,NULL,NULL,NULL))//內部實現需要值最大的fd+1來確定fd邊界,最後一個參數是超時時間結構體,不設置超時就爲NULL
{
case -1://錯誤
perror("select");
break;
case 0://超時
printf("time out..\n");
break;
default://有數據
{
size_t i = 0;
for(;i < fds_num;++i)
{
//當爲listen_socket事件就緒的時候,就表明有新的連接請求
if(FD_ISSET(fds[i],&rset) && fds[i] == listen_sock)
{
struct sockaddr_in client;
clilen=sizeof(client);
//返回一個鏈接句柄,不會阻塞
int accept_sock = accept(listen_sock,(struct sockaddr*)&client,&clilen);//原源碼在這裏出錯,沒有&clilen而是直接sizeof(client)
if(accept_sock < 0){
perror("accept");
exit(5);
}
char * paddr=NULL;
char * saddr=NULL;
paddr=inet_ntoa(client.sin_addr);
saddr=inet_ntoa(client.sin_addr);
printf("connect by a client, ip:%s port:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
size_t i = 0;
for(;i < fds_num;++i)//將新接受的描述符(鏈接句柄)存入集合中
{
if(fds[i] == -1){
fds[i] = accept_sock;
break;
}
}
if(i == fds_num)//如果沒空位就關閉此鏈接句柄(可設置數量最多1000個)
{
close(accept_sock);
}
}
//普通請求
else if(FD_ISSET(fds[i],&rset) && (fds[i] > 0))
{
char buf[1024];
memset(buf,'\0',sizeof(buf));
ssize_t size = read(fds[i],buf,sizeof(buf)-1);//接收鏈接句柄發來的數據
if(size < 0){
perror("read");
exit(6);
}else if(size == 0){
printf("client close..\n");
close(fds[i]);
fds[i] = -1;
}else{
printf("client say: %s\n",buf);
}
}
else{}
}
}
break;
}
}
return 0;
}