高并发服务器—–Select模型详解(代码实例+函数详解)

概要介绍

一般情况下,处理socket通信一个线程或者进程在处理读写的时候要么阻塞在那一直等要么非阻塞然后过会查看是否可读可写,这样会浪费大量的资源,假如需要对多个套接字进行处理读写那么得开很多个线程或者进程,IO复用技术就是解决这个问题。本节详细讲解IO复用模型 select。
解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力
往期文章
Socket实现消息互发+函数详解
Socket实现会射服务器

文章目录select()函数select()函数配套使用的四个宏select模型的缺点select模型代码实例—简单的服务器回射打印(附解释)

select()函数

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

参数说明:

nfds: 被监听的文件描述符总数,它会比文件描述符表集合中的文件描述符表的最大值大1,因为文件描述符从0开始计数

readfds:需要监听的可读事件的文件描述符集合

writefds:需要监听的可写事件的文件描述符集合

exceptfds:需要监听的异常事件的文件描述符集合

timeout:即告诉内核select等待多长时间之后就放弃等待。一般设为NULL 表示无动静就阻塞等待

返回值:表示产生动静的文件描述符个数`

函数作用:
监听等待参数集合中的两类文件描述符产生动静,产生动静后集合中只会剩下产生动静的文件描述符
高并发服务器-----Select模型详解(代码实例+函数详解)
①sockfd:专门检查有没有客服端连接的监听套接字 ------数量 1
②connfd:已经连接成功的客服端通信套接字 ------数量 n

实例补充说明:

select(maxfd+1,&rset,NULL,NULL,NULL);

由于参数二放入集合,参数一填入数字,其余位置皆为NULL,
表示我们监听的是rset文件描述符集合是否有可读事件(动静),如果有则该集合只会保留产生可读事件(动静)的文件描述符,否则就阻塞等待。
可读事件分为以下三种

    新客服端连接(此时唯一sockfd放入集合)
    旧客服端发消息过来(此时对应的connfd放入集合)
    旧客户端断开连接(此时对应的connfd放入集合)

select()函数配套使用的四个宏

FD_ZERO(fd_set* set)
理解为初始化文件描述符几何 (既把所有位都设置为0)

FD_SET(fd,fd_set* set)
理解为把文件描述符加入集合(本质为对应fd位设置为1)

FD_CLR(fd,fd_set* set)
理解为把文件描述符从集合取出(本质为对应fd位设置为0)

FD_ISSET(fd,fd_set* set)
理解为检测改文件描述符是否在集合(本质为对应的fd位是否设置为

select模型的缺点

    最大并发数限制,因为一个进程所打开的 fd(文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024,并且集合描述符最大也只能为1024,因此 select 模型的最大并发数就被相应限制了。

    效率问题,采用循环的方式匹配数组内的fd是否在产生的动静集合中,如果连接的客户端数量很多,那么效率可想而知。

    内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法,在FD非常多的时候,非常的耗费时间。
    第三点参考链接
    总结:前面两点缺陷在代码中都有体现。接下来直接参考代码

select模型代码实例—简单的服务器回射打印(附解释)

客户端代码如下

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <signal.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#define SET_PORT 8000
int main(int argc, char *argv[])
{
int sockfd,connfd;//监听套接字 和连接套接字
struct sockaddr_in serveraddr;
int i;//主要用于各种for循环的i
int on = 1;//只有下方设置可重复性使用的端口用到
//1.创建监听套接字
sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) ;//设置为可重复使用的端口

//2.bind(通信需要套接字 我把我家的地址 门牌号绑上去 ip和端口)
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SET_PORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (struct sockaddr *)&serveraddr,sizeof(serveraddr));
//3.监听 和服务器连接的总和
listen(sockfd,128) ;
int maxfd = sockfd;
//初始化两个集合 一个数组
int client[FD_SETSIZE];//数组用于存放客服端fd 循环查询使用 FD_SETSIZE 是一个宏 默认已经最大1024
fd_set rset;//放在select中 集合中都是有动静的fd 一般就一个
fd_set allset;//只做添加fd
FD_ZERO(&rset);//清空的动作
FD_ZERO(&allset);//清空的动作

//先将监听套接字放入
FD_SET(sockfd,&allset);
int nready;

//初始化数组都为-1 应为标识符从0开始
for(i = 0; i < FD_SETSIZE; i++)
client[i] = -1;
while(1)
{
rset = allset;//保证每次循环 select能监听所有的文件描述符 因为rset只会剩下有动静的
nready = select(maxfd+1,&rset,NULL,NULL,NULL);//参数一

//新客户端
if(FD_ISSET(sockfd,&rset))
{
struct sockaddr_in clientaddr;
memset(&clientaddr,0,sizeof(clientaddr));
int len = sizeof(clientaddr);

connfd = accept(sockfd, (struct sockaddr*)&clientaddr,&len);

char ipstr[128];//打印用到
printf("client ip%s ,port %d\n",inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ipstr,sizeof(ipstr)),
ntohs(clientaddr.sin_port));

for(i = 0; i < FD_SETSIZE; i++)
{ if(client[i] < 0)
{
client[i] = connfd;
break;//一定要记得及时跳出 易错点
}
}
FD_SET(connfd,&allset);//放入集合
//防止超出范围//select的第一个参数必须是监视的文件描述符+1 如果不断有新的客户连接 最大值不断变大 超出就赋值
if(connfd > maxfd)
maxfd = connfd;

//下方表示 如果同一时刻只有 一个动静 就无需进入下方的else判断处理 如果不止一个 nready-1 再进入下方判断
if(--nready <= 0)
continue;

}
else
{
//已连接FD产生可读事件
for(i = 0; i < FD_SETSIZE; i++)//FD_SEISIZE 是宏 1024 //循环从数组取出元素比对
{

if(FD_ISSET(client[i],&rset))
{
connfd = client[i];
char buf[1024] = {0};
int nread ;
nread = read(connfd, buf, sizeof(buf));
if(nread == 0)//表示客服端断开链接
{
//四步处理 打印说明 从集合中删除 从数组中删除 关闭客服端
printf("client is close..\n");
FD_CLR(connfd, &allset);
client[i] = -1;
close(connfd);
}
else//正常读到处理
{
write(connfd,buf,nread);
memset(buf,0,1024);

}
//下方表示如果同意时刻如果可读事件只有一个 无需再将数组元素进行循环比对 直接跳出
//不必让循环走完 浪费时间
if(--nready <= 0)
break;
}
}
}
}
return 0;
}

服务器代码如下

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char* argv[])
{
int sockfd;
struct sockaddr_in sfdaddr; //指定要连接服务器的结构体 ip 端口
int len;
//char buf[1024];
char wbuf[1024];
char rbuf[1024];
//1.socket 通信用套接字,创建一个socket
sockfd = socket(AF_INET,SOCK_STREAM,0);

char ipstr[] = "127.0.0.1"; //本机测试ip
//初始化地址
bzero(&sfdaddr,sizeof(sfdaddr));

sfdaddr.sin_family = AF_INET;
sfdaddr.sin_port = htons(8000);

inet_pton(AF_INET,ipstr,&sfdaddr.sin_addr.s_addr); //转换ip 保存到结构体内

//2.connect 主动连接服务器
connect(sockfd,(struct sockaddr *)&sfdaddr,sizeof(sfdaddr));
//3.读写
#if 1

while(1)
{
memset(wbuf,0,1024);
memset(rbuf,0,1024);
scanf("%s",wbuf);
write(sockfd,wbuf,strlen(wbuf));
len=read(sockfd,rbuf,sizeof(rbuf));
//write(STDOUT_FILENO,buf,len);
printf("%s\n",rbuf);
}

#endif
//4.close
close(sockfd);
return 0;
}

效果图
高并发服务器-----Select模型详解(代码实例+函数详解)

结束语:凡心所向,素履所往,生如逆旅,一苇以航。
大学时光不易,且行且珍惜。
写博客只是想记录自己的学习旅程。
文章如有不足和错误的地方,希望评论指出或私信
最后希望给文章点个赞,整理不易!!!
最后希望给文章点个赞,整理不易!!!
最后希望给文章点个赞,整理不易!!!

原创:https://www.panoramacn.com
源码网提供WordPress源码,帝国CMS源码discuz源码,微信小程序,小说源码,杰奇源码,thinkphp源码,ecshop模板源码,微擎模板源码,dede源码,织梦源码等。

专业搭建小说网站,小说程序,杰奇系列,微信小说系列,app系列小说

高并发服务器-----Select模型详解(代码实例+函数详解)

免责声明,若由于商用引起版权纠纷,一切责任均由使用者承担。

您必须遵守我们的协议,如果您下载了该资源行为将被视为对《免责声明》全部内容的认可-> 联系客服 投诉资源
www.panoramacn.com资源全部来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。 敬请谅解! 侵权删帖/违法举报/投稿等事物联系邮箱:2640602276@qq.com
未经允许不得转载:书荒源码源码网每日更新网站源码模板! » 高并发服务器—–Select模型详解(代码实例+函数详解)
关注我们小说电影免费看
关注我们,获取更多的全网素材资源,有趣有料!
120000+人已关注
分享到:
赞(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

您的打赏就是我分享的动力!

支付宝扫一扫打赏

微信扫一扫打赏