多路I/O转接服务器
多路IO转接服务器也叫多任务IO服务器。该类服务器实现的主旨思想是,不再由程序自己监视客户端连接,取而代之由内核替应用程序监视文件描述符。可用select函数实现。
select函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
fd_set代表集合。
参数1:所监听的所有文件描述符中,最大的文件描述符+1.
参数2:所监听的文件描述符“可读”事件 —readfds;
参数3:所监听的文件描述符“可写”事件 —writefds;
参数4:所监听的文件描述符“异常”事件 —timeout;
参数5:时间,一般设为NULL,有监听事件才返回
返回值:
成功:所监听的所有的监听集合中,满足条件的总数。
失败:
对集合fd_set的相关操作函数
void FD_CLR(int fd, fd_set *set); 将fd从集合中清除出去
int FD_ISSET(int fd, fd_set *set); 判断fd是否在集合中
void FD_SET(int fd, fd_set *set); 将fd设置到集合中去
void FD_ZERO(fd_set *set); set清空
个人思路
服务器端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<errno.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/select.h>
#define SERV_PORT 6666
ssize_t Read(int fd,void *ptr,size_t nbytes)
{
ssize_t n;
again:
if( (n = read(fd,ptr,nbytes))==-1)
{
if(errno==EINTR)
goto again;
else
return -1;
}
return n;
}
int main(int argc,char* argv[])
{
int i,j,n,maxi,client_sum;
int ret;
int maxfd,listenfd,connfd,sockfd;
int nready,client[FD_SETSIZE];//自定义数组,防止遍历超过1024个文件描述符
char buf[BUFSIZ],str[INET_ADDRSTRLEN]; //8096 ,16
struct sockaddr_in clie_addr,serv_addr;
socklen_t clie_addr_len;
fd_set rset,allset;//定义监听集合
listenfd=socket(AF_INET,SOCK_STREAM,0);
int opt=1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
//端口地址复用,当服务器主动关闭时,就算处于wait_time状态(主动关闭一方最后需要等待2MSL时间确保最后一个ACK发送成功,保证完整的4次握手关闭连接)重启后马上可以连接新的客户端
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(SERV_PORT);
ret=bind(listenfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));//绑定ip和端口
if(ret==-1)
{
perror("bind error!");
exit(1);
}
listen(listenfd,128);
maxfd=listenfd;//记录当前最大文件描述符
maxi=-1;
client_sum=0;
for(i=0;i<FD_SETSIZE;i++)//记录客户端连接的文件描述符数组,方便轮询查找
client[i]=-1;
FD_ZERO(&allset);//清空集合
FD_SET(listenfd,&allset); //监听描述符加入到集合中,如果有客户端连接,变为“可读”
while(1)//服务器循环等待客户端连接请求
{
rset=allset;
nready=select(maxfd+1,&rset,NULL,NULL,NULL);
//成功会返回所有监听集中满足触发条件的总数
//rset是传入传出参数,传出时是带有所有可读描述符的集合
if(nready<0)
{
perror("select error");
exit(1);
}
if(FD_ISSET(listenfd,&rset))
//有可读文件描述符,优先判断其中有没有listenfd(监听描述符),如果存在说明有客户端连接请求
{
clie_addr_len=sizeof(clie_addr);
connfd=accept(listenfd,(struct sockaddr*)&clie_addr,&clie_addr_len);
printf("*******************************************************\n");
printf("connect with client:ip %s, port: %d\n",
inet_ntop(AF_INET,&clie_addr.sin_addr.s_addr,str,sizeof(str)),
ntohs(serv_addr.sin_port));
for(i=0;i < FD_SETSIZE;i++)
{
if(client[i]<0)//查找靠前且未被使用的文件描述符
{
client[i]=connfd;
client_sum++;
if(i>maxi)//maxi是client数组存有文件描述符最大编号,方便下面的轮询查找
maxi=i;
printf(" set connfd:%d in client[%d]-----maxi=%d,client_sum=%d----\n",connfd,i,maxi,client_sum);
printf("*******************************************************\n");
break;
}
}
if(i==FD_SETSIZE)//超过1024个文件描述符报错
{
fputs("too many clients\n",stderr);
exit(1);
}
FD_SET(connfd,&allset);//把新客户端连接的文件描述符加入到总集合中
if(connfd>maxfd)//保证maxfd是序号最大的文件描述符(select函数参数要求)
maxfd=connfd;
if(--nready==0)//有listenfd且可读文件描述符总数为1,说明只有新客户端连接请求,不用执行下面的回应代码
continue;
}
//select函数传出可读文件描述符数量,但我们并不知道具体是哪些文件描述符可读,所以需要轮询查找
for(i=0;i<=maxi;i++)
{
if((sockfd=client[i])<0)//只查找数组中所有存在的文件描述符
continue;
if(FD_ISSET(sockfd,&rset))//判断是否在传出的可读集合中
{
memset(buf,0,sizeof(buf));
n=Read(sockfd,buf,sizeof(buf));
if(n==0)//客户端断开连接
{
printf("*******************************************************\n");
close(sockfd);
FD_CLR(sockfd,&allset);//从监听集合中清楚
client_sum--;
if(client_sum==0)
maxi=-1;
else if(i==maxi)//退出的是描述符最大值时,maxi变为退出后数组描述符最大值
{
for(int k=i-1;k>=0;k--)
{
if(client[k]<0)
continue;
else
{
maxi=k;
break;
}
}
}
printf("client[%d] connfd : %d exit----maxi=%d, client_sum=%d\n",i,client[i],maxi,client_sum);
client[i]=-1;
printf("*******************************************************\n");
}
else if(n>0)
{
printf("read buf:%s---from client[%d] :connfd=%d \n",buf,i,client[i]);
for(j=0;j<n;j++)
buf[j]=toupper(buf[j]);
//sleep(10);//实验,处理函数时又有其他多个客户端发信号,那么会在下一次select函数中全部变为可读,依次处理
printf("write back buf:%s\n",buf);
write(sockfd,buf,n);
printf("--------------------\n");
}
if(--nready==0)//完成一次回调处理,select返回的监听集数量-1,如果为0说明没有新的需要处理的可读文件描述符
break;//跳出client[]数组轮询查找
}
}
}
close(listenfd);
return 0;
}
客户端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<ctype.h>
#include<arpa/inet.h>
#include<string.h>
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666
int main()
{
int cfd,sfd;
struct sockaddr_in serv_addr;
socklen_t serv_addr_len;
char buf[BUFSIZ],clien_ip[BUFSIZ];
int n;
char *find;
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERV_PORT);//转换为网络字节序
//serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY可以自行寻找ip
inet_pton(AF_INET,SERV_IP,&serv_addr.sin_addr.s_addr);
cfd=socket(AF_INET,SOCK_STREAM,0);//创建本地套接字
// bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));//绑定,客户端隐式绑定
connect(cfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr) );
while(1)
{
memset(buf,0,sizeof(buf));
fgets(buf,sizeof(buf),stdin);//从输入端读一行数据存入缓存区中
find=strchr(buf,'\n');
if(find)
*find='\0';//去掉fgets中的换行符
write(cfd,buf,strlen(buf));
n=read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,n);
printf("\n");
}
close(cfd);
return 0;
}
测试结果
客户端的连接和退出
大写转换