采用多进程与多线程的方法来实现并发服务器时,监听的工作由server应用程序自身通过accept函数不断去监听。当客户端连接较多时,这种方法会大大降低程序执行效率,消耗CPU资源(CPU需要在不同进/线程中切换执行)。
多进程与多线程实现并发服务器方法可以参考以下两篇文章:
因为以上两种方法的局限性所以出现了采用多路IO转接的方式来设计服务器,该类服务器实现的思想为应用程序本身不在监听客户端连接,转而由内核来代替监听。主要使用的方法有三种,其一为 select()
函数。
select
函数
select
能监听的文件描述符个数受限于FD_SETSIZE
,一般为1024,单纯改变进程打开的文件描述符个数不能改变select监听文件个数- 解决1024以下个客户端时使用
select
十分合适,但是如果链接客户端过多,select
采用的轮询模型,会大大降低服务器响应效率,应采用其他方法。
函数原型
int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);
- 返回值
- 成功:返回所监听的所有监听集合中满足条件的总数。
- 失败:返回-1,并设置errno。
- 参数
nfds
:监控的文件描述符集里最大文件描述符加1,此参数会告诉内核检测前多少个文件描述符的状态readfds
:监控有读数据到达的文件描述符集合。传入传出参数writefds
:监控有写数据到达的文件描述符集合。传入传出参数exceptfds
:监控异常发生到达文件描述符集合。传入传出参数timeout
:定时阻塞监控时间,3种情况
- NULL,永远等下去
- 设置timeval,等待固定时间
- 设置timeval里时间均为0,检查描述字后立即返回,轮询
四个工具函数
想要看的更详细,请参考man手册
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
例子
服务端代码实现
特别要举例说的是rset
参数,在调用select
函数前调用了rset = allset
语句,假如此时赋值后rset
中有fd1、fd2、fd3三个需要被监听的文件描述符,但是这三个文件描述符中只有fd2文件描述符满足了读的监听条件,那么在调用完select
函数后,select
函数会将fd1、fd3文件描述符从rset
集合中剔除,rset
集合中只会存在满足读监听条件的文件描述符。
同样地,readfds
、writefds
、exceptfds
三个传入传出参数皆是如此。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<sys/types.h>
#include<sys/time.h>
#include<ctype.h>
#define PORT 8888
#define IP "127.0.0.1"
int main(){
int sfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serv_addr;
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr);
socklen_t serv_len = sizeof(serv_addr);
int ret = bind(sfd,(struct sockaddr*)&serv_addr,serv_len);
if(ret != 0){
printf("bind err:%s\n",strerror(ret));
exit(1);
}
listen(sfd,128);
int client[FD_SETSIZE];
for(int i = 0; i < FD_SETSIZE;i++){
client[i] = -1;
}
fd_set rset,allset;
FD_ZERO(&allset);
FD_SET(sfd,&allset);
int nready,i;
int index = -1;
int maxfd = sfd;
while(1){
rset = allset;
nready = select(maxfd+1,&rset,NULL,NULL,NULL);
if(nready < 0){
printf("监听的集合中没有满足条件的文件描述符\n");
}
int sockfd;
if(FD_ISSET(sfd,&rset)){
struct sockaddr_in clie_addr;
socklen_t clie_len = sizeof(clie_addr);
int cfd = accept(sfd,(struct sockaddr*)&clie_addr,&clie_len);
if(cfd == -1){
perror("accept error");
exit(2);
}
char buf[BUFSIZ];
printf("%sconnected...;port:%d\n",inet_ntop(AF_INET,&clie_addr.sin_addr.s_addr,buf,sizeof(buf)),ntohs(clie_addr.sin_port));
for(i = 0; i < FD_SETSIZE;i++){
if(client[i] < 0){
client[i] = cfd;
break;
}
}
if(i == FD_SETSIZE){
printf("too many connecting...\n");
exit(1);
}
FD_SET(cfd,&allset);
if(cfd > maxfd){
maxfd = cfd;
}
if(i > index){
index = i;
}
if(--nready == 0){
continue;
}
}
int len;
char rwbuf[BUFSIZ];
for(i = 0; i <= index;i++)
{
if((sockfd=client[i]) < 0){
continue;
}
if(FD_ISSET(sockfd,&rset)){
len = read(sockfd,rwbuf,sizeof(rwbuf));
if(len==0){
printf("------%d--disconnected\n",i);
close(sockfd);
FD_CLR(sockfd,&allset);
client[i] = -1;
}else if(len > 0){
for(int j = 0; j < len;j++){
rwbuf[j] = toupper(rwbuf[j]);
}
write(sockfd,rwbuf,len);
}
if(--nready == 0){
break;
}
}
}
}
close(sfd);
return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)