高并发服务器
多线程/进程服务器实现思路:
server lfd cfd1 cfd2 client1 client2
但是这样做所有客户端直接连服务器占用资源太多,所以有以下另一种方式。
server select lfd client1 client2 client3
使用select来管理客户端。
当客户端有需要连接请求时,才会向select申请。此时select向sever报备,sever调用accept函数
监管read/write client select lfd server 产生cfd
select也称为,响应模式---多路IO转接
1、select参数简介:
cpp
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struvt timeval *time out);
/*
nfds:监听的所有文件描述符当中最大的文件描述符的编号 + 1
在select的内部有一个循环,循环的次数就是文件描述符的上限
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,均是传入传出参数
exceptfds时异常事件
*/
在文件描述符表中,当程序启动时,默认被占用
0 | 启动被占用 |
---|---|
1 | 启动被占用 |
2 | 启动被占用 |
3 | lfd |
4 | cfd1 |
5 | cfd2 |
6 | cfd3 |
readfds,writefds:
当客户端发送请求时,lfd需要读这个请求。同理,有时也会给客户端写数据。 fd_set中的set的意思是集合。这两个是传入传出参数,比如writefds有三个{4,5,6},但是如果在监听时只有两个满足写事件,其就会传出{4,5}。
select的返回值是指在监听的时候满足时间的个数。
传入和传出的参数可以理解为一个位图:
传入:监控写文件描述符{3,5,6}
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 0 |
传出:监控写文件描述符{5,6}
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
2、select相关参数分析
cpp
void FD_ZERO(fd_set *set); //全部置0
void FD_SET(int fd, fd_set *set);//将待监听的文件描述字加入
void FD_CLR(int fd, fd_set *set);//清楚某一个文件描述符
void DS_ISSET(int fd, fd_set *set);//添加文件描述符
int FD_ISSET(int fd, fd_set *set);//判断一个文件描述字是否在监听集合中
cpp
int select(int nfds, fd set *readfds, fd set *writefds, fd_set *exceptfds, struct timeval *timecut);
/*
nds:监听的所有文件描述符中,最大文件描述符+1
传入、传出参数
readfds:读文件描述符监听集合。
writefds,写文件描述符监听集合。传入、传出参数nulL
exceptfds,异常文件描述符监听集合NULL
timeout:
>0:设置监听超时日长
NUII:阻赛监听
0:非阻害监听,轮询
返回值:
>0:所有监听集合 (3个)中, 满对应事件的点数。0:
0:没有满足监听条件的文件描述符0
-1:errno
*/
3、select实现多路IO转接思路
思路伪代码:
cpp
lfd = socket();
bind();
listen();
fd_set allset;
fd_set rset;//创建读集合
FD_ZERO(&allrset);//将读监听集合清空
FD_SET(lfd,&allrset);//将lfd添加到读集合当中
while(1)
{
rset = allset;
ret = select(lfd+1,&rset,NULL,NULL,NULL);//监听文件描述符集合对应的事件
if(ret > 0)//有监听的描述满足对应事件
{
if(FD_ISSET(lfd,&rset))//1在,0不在,说明建立完结完成
{
cfd = accept();
FD_SET(cdf,&allset);
}
for(i=lfd+1; i < 最大文件描述符;i++)
{
FD_ISSET(i,&rset); //有read、write事件
read();
toUpper();
write();
}
}
}
代码:
cpp
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<ctype.h>
#define SERV_PORE 8086
int main(int argc, char *argv[])
{
int i,j,n,nready;
int cfd;
int maxfd = 0;
char buf[BUFSIZ];
struct sockaddr_in clie_addr,serv_addr;
socklen_t clie_addr_len;
int lfd = socket(AF_INET,SOCK_STREAM,0);
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORE);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
listen(lfd,128);
fd_set rset,allset;
maxfd = lfd;
FD_ZERO(&allset);
FD_SET(lfd,&allset);
while(1)
{
rset = allset;
nready = select(maxfd+1,&rset,NULL,NULL,NULL);
if(FD_ISSET(lfd,&rset))
{
clie_addr_len = sizeof(clie_addr);
cfd = accept(lfd,(struct sockaddr *)&clie_addr,&clie_addr_len);
FD_SET(cfd,&allset);//必须设定在拷贝集合当中
if(maxfd < cfd)
maxfd = cfd;
if(0 == --nready)//必须有这一项,保证当只有lfd是会跳过后面的for循环
continue;
}
for(i = lfd+1; i<= maxfd; i++)
{
if(FD_ISSET(i,&rset))
{
if((n = read(i,buf,sizeof(buf))) == 0)
{
close(i);
FD_CLR(i,&allset);
}
else if(n > 0)
{
for(j = 0; j < n; j++)
{
buf[j] = toupper(buf[j]);
}
write(i,buf,n);
}
}
}
}
}
4、select优缺点
- select监听是使用轮询实现的
- 监听受文件描述符限制,最大为1024
- 检测满足条件的cfd,只能自己添加业务逻辑,提高了编码难度
- 优点:跨平台