网络编程-多路I/O转接服务器|线程池并发服务器|UDP服务器|本地套接字【1】
【摘要】 @[toc] 多路I/O转接服务器能做出高并发服务器方法有:阻塞等待 消耗资源非阻塞忙轮询 消耗cpu多路IO多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。多路IO转接(多路IO复用): 内核监听多个文件描述符的属性(读写缓冲区)变化如果某个文件描述符的读缓冲区变化了,这个时候就是可以读了,将这个事件...
@[toc]
多路I/O转接服务器
能做出高并发服务器方法有:
阻塞等待 消耗资源
非阻塞忙轮询 消耗cpu
多路IO
多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。
多路IO转接(多路IO复用): 内核监听多个文件描述符的属性(读写缓冲区)变化如果某个文件描述符的读缓冲区变化了,这个时候就是可以读了,将这个事件告知应用层
主要使用的方法有三种
1.select
实现原理:
应用层的文件描述符集合给内核,内核去处理进行剔除,再返回给应用层,所以需要备份一个旧的,轮询
-
select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
-
解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力。
#include <sys/select.h>
/* According to earlier standards */
#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,因为此参数会告诉内核检测前多少个文件描述符的状态
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是否置1
void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
返回值: 返回的是变化的文件描述符的个数
注意: 变化的文件描述符会存在监听的集合中,未变化的文件描述符会从集合中删除
多路IO转接服务器
#include <stdio.h>
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include "wrap.h"
#include <sys/time.h>
#define PORT 8888
int main(int argc, char *argv[])
{
if(argc < 2)
{
printf("输入两个参数./a.out port\n");
return 0;
}
//创建套接字绑定
int port=atoi(argv[1]);
//创建套接字,绑定
int lfd = tcp4bind(port,NULL);//NULL代表所有的网卡ip都可以
//监听
Listen(lfd,128);
int maxfd = lfd;//最大的文件描述符
fd_set oldset,rset;
FD_ZERO(&oldset);//把文件描述符集合里所有位清0
FD_ZERO(&rset);
//将lfd添加到oldset集合中
FD_SET(lfd,&oldset);
while(1)
{ //防止下一次返回被覆盖
rset = oldset;//将oldset赋值给需要监听的集合rset
//返回监听文件描述符个数
int n = select(maxfd+1,&rset,NULL,NULL,NULL);
if(n < 0)
{
perror("");
break;
}
else if(n == 0)
{
continue;//如果没有变化,重新监听
}
else//监听到了文件描述符的变化
{
//lfd变化 代表有新的连接到来(1个)
if( FD_ISSET(lfd,&rset))//判断lfd是否置1了
struct sockaddr_in cliaddr;
socklen_t len =sizeof(cliaddr);
char ip[16]="";
//提取新的连接
int cfd = Accept(lfd,(struct sockaddr*)&cliaddr,&len);
printf("new client ip=%s port=%d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),
ntohs(cliaddr.sin_port));//打印连接客服端的ip和端口信息
//将cfd添加至oldset集合中,以下次监听
FD_SET(cfd,&oldset);
//更新maxfd
if(cfd > maxfd)
maxfd = cfd;
//如果只有lfd变化,continue
if(--n == 0)
continue;
}
//cfd(多个) 遍历lfd之后的文件描述符是否在rset集合中
for(int i = lfd+1;i<=maxfd;i++)
{
//如果i文件描述符在rset集合中,如果在则cfd变化
if(FD_ISSET(i,&rset))
{
char buf[1500]="";//栈空间的最大长度就是1500
int ret = Read(i,buf,sizeof(buf));//读文件描述符i到缓存区返回读的个数
if(ret < 0)//出错,将cfd关闭,从oldset中删除cfd
{
perror("");
close(i);
FD_CLR(i,&oldset);//把文件描述符集合里fd位清0
continue;//读取错误就不用往下执行了
}
else if(ret == 0)
{
printf("client close\n");
close(i);
FD_CLR(i,&oldset);
}
else
{
//打印
printf("%s\n",buf);
Write(i,buf,ret);
}
}
}
}
}
return 0;
}
源码分析:
select 的优缺点
优点: 跨平台
缺点:文件描述符1024的限制 由于 FD_SETSIZE的限制只是返回变化的文件描述符的个数,具体哪个那个变化需要遍历每次都需要将需要监听的文件描述符集合由应用层拷贝到内核
大量并发,少量活跃,select效率低
假设现在 4-1023个文件描述符需要监听,但是5-1000这些文件描述符关闭了?
假设现在 4-1023个文件描述符需要监听,但是只有 5,1002 发来消息- 无解
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)