有帮助记得点个赞+关注
一.多路IO转接服务器
多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主要思想,不在由应用程序自己监视客户端连接,取而代之的是由内核替应用程序监视文件。
主要实现凡是有三种,今天来介绍一下第一种方式SELECT【轮询机制】
二.SELECT【轮询机制】
1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,【这是由历史原因造成的,因为以前网络不是这么发达并发量不高,大佬设计这个select的时候觉得1024已经够用了】。单纯的改变进程打开文件描述符并不能改变select监听的文件个数。如图(我是已经通过配置文件改过了,默认是1024)
大家看open files 字段,这就是进程能打开的 文件描述符上限
2.解决1024个数以下的客户端时,使用select是比较合适的,但连接客户多了【但只有一两个客户发送消息】,select采用轮询机制检测谁发送了消息,会大大降低服务器响应的效率,不应该在select上花太多的精力【但是我还是要给大家介绍一下】
三.select相关的操作函数
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- nfds【会变化的】: 监控文件描述符集里最大文件描述符+1, 因为此参数会告诉内核检测前多少个文件描述符的状态
- readfds: 监控有读数据到文件描述符集合,传入传出参数。
- writefds:监控写数据到达文件描述符集合,传入传出参数
- exceptfds: 监控异常发生到达文件描述符集合,如外带数据到达异常,传入传出参数
- timeout: 定时阻塞监控时间,3种情况,1.NULL永远等下去,2.设置timeval,等待固定时间,3.设置timeval里的时间均为0,检查描述字后立即返回,轮询
- struct timeval{
- long tv_sec; // 秒
- long tv_usec; // 微秒
- }
void FD_CLR(int fd, fd_set *set); // 把文件描述符集合里的 fd清0
int FD_ISSET(int fd, fd_set* set); // fd 是否在 文件描述符集合中
void FD_SET(int fd, fd_set* set); // 把 fd 添加到 文件描述符集合中
void FD_ZERO(fd_set * set); // 把文件描述符集合全部清0
四.多路IO转接服务器-select【代码Demo】
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
using namespace std;
#ifndef FD_SETSIZE
#define FD_SETSIZE 1024
#endif
#define SERVER_PORT 7777
#define LISTEN_NUM 200
#define BUF_SIZE 1024
int
main(int argc, char*argv[])
{
int i, j, n, maxi;
int nready, client[FD_SETSIZE];
int maxfd, listenfd, connfd, sockfd;
struct sockaddr_in clie_addr, serv_addr;
socklen_t clie_addr_len;
fd_set r_set, all_set;
char buf[BUF_SIZE], str[INET_ADDRSTRLEN]; // 16
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(SERVER_PORT);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
listen(listenfd, LISTEN_NUM);
maxfd = listenfd; // 3
maxi = -1;
for(i=0; i<FD_SETSIZE; i++) client[i] = -1; // 我们自己维护一个 client fd 数组
FD_ZERO(&all_set);
FD_SET(listenfd, &all_set);
// printf("*******listen******\n");
while(true){
r_set = all_set; // 因为是传入传出参数所以要 赋值
nready = select(maxfd+1, &r_set, NULL, NULL, NULL);
if(nready < 0){
perror("select err");
exit(1);
}
if(FD_ISSET(listenfd, &r_set)){ // 有用户连接
clie_addr_len = sizeof(clie_addr);
connfd = accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
printf("received from %s at port %d\n",
inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
ntohs(clie_addr.sin_port));
for(i=0; i<FD_SETSIZE; i++){
if(client[i] < 0){ // 找到下标最小的 没有被占用的
client[i] = connfd;
break;
}
}
if(i == FD_SETSIZE){ // 用户已经达到 规定的上限了
fputs("too many client\n", stderr);
exit(1);
}
FD_SET(connfd, &all_set); // 加入所有用户文件描述符集合的
if(connfd > maxfd) // 设置好最大的 maxfd
maxfd = connfd;
if(i > maxi) // 下标赋值
maxi = i;
if(--nready == 0) // only has listen event
continue;
}
for(i=0; i<=maxi; ++i){
if((sockfd = client[i]) < 0)
continue;
if(FD_ISSET(sockfd, &r_set)){
if((n = read(sockfd, buf, sizeof(buf) )) == 0){// 清理资源
close(sockfd);
FD_CLR(sockfd, &all_set);
client[i] = -1;
}else if(n > 0){
for(j=0; j<n; j++)
buf[j] = toupper(buf[j]);
sleep(2);
write(sockfd, buf, n);
}
if(--nready == 0)break;
}
}
}
close(listenfd);
return 0;
}
效果:
SERVER
CLIENT
原理图