Linux select开发服务端

多路IO技术:select,同时监听多个文件描述符,将监控的操作交给内核去处理。

数据类型fd_set:文件描述符集合。

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

函数介绍:委托内核监控该文件描述符对应的写,读或者错误事件的发生。

参数说明:

nfds:最大的文件描述符+1;

readfds:读集合,是一个传入传出参数

 传入:指的是告诉内核哪些文件描述符需要监控

 传出:指的是内核告诉应用程序哪些文件描述符发生了变化(是否有可读事件发生)

writefds:写集合,是一个传入传出参数

 传入:指的是告诉内核哪些文件描述符需要监控

 传出:指的是内核告诉应用程序哪些文件描述符发生了变化

exceptfds:传入传出参数,一般表示异常事件;

Timeout:

超时时间:

NULL:表示永久阻塞,直到有事件发生

0:表示不阻塞,不管有没有事件发生,都回立刻返回

>0:表示阻塞的时长,若超过时长会立刻返回

返回值:

成功返回发生变化的文件描述符个数。

void FD_CLR(int fd, fd_set *set);

说明:将fd从set集合中移除;


       int  FD_ISSET(int fd, fd_set *set);
说明:判断fd是否在set集合中       

void FD_SET(int fd, fd_set *set);

说明:将fd添加到set中


       void FD_ZERO(fd_set *set);

说明:将set清零

使用select的开发服务端大致流程:

1 创建socket,获取监听文件描述符lfd---socket();

2 设置端口复用---setsockopt();

3 将lfd与IP,PORT绑定---bind();

4 设置监听---listen();

5 fd_set readfds;//定义文件描述符集

FD_ZERO(&readfds);//清零

 将lfd加入到readfds集合中---FD_SET(lfd,&readfds);

6 fd_set tmpfds;//定义一个临时文件描述符集
maxfd = lfd;
while (1)
{
    tmpfds = readfds;//赋值
    nready = select(maxfd,&tmpfds,NULL,NULL,NULL);//传出修改的tmpfds(有哪些可读事件)
    if(nready<0)
    {
        if (errno == EINTR)//被信号中断
        {
            continue;
        }
        break;
    }
    //可读事件有两种:
    //1 客户端连接到来--->监听文件描述符lfd发生变化
    if (FD_ISSET(lfd, &tmpfds))
    {
      //接收新的客户端连接请求
        cfd = accept(lfd, NULL, NULL);
        //将cfd加入到readfds集合中
        FD_SET(cfd, &readfds);

        if (maxfd < cfd)
        {
            maxfd = cfd;//修改内核监控的文件描述符的范围
        }

        if (--nready == 0)
        {
            continue;//只有一个可读事件且已经执行完,就没必要继续执行下去
        }
    }
    //有客户端发数据过来
    for (i = lfd + 1; i <= maxfd; i++)
    {
        if (FD_ISSET(i, &tmpfds))//看那个通信描述符发生变化
        {
                //读数据
                int n = read(i, buf, sizeof(buf));//read函数不会阻塞,因为肯定有数据发过来
                if (n <= 0)
                {
                    close(i);
                    //将文件描述符i从内核中去除
                    FD_CLR(i, &readfds);
                }
                write(i, buf, strlen(buf));
        }
    }
    close(lfd);
    return 0;
}

完整代码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <errno.h>
#include<ctype.h>
int main()
{
	//int socket(int domain, int type, int protocol);
	int lfd=socket(AF_INET,SOCK_STREAM ,0);
	if(lfd<0)
	{
		perror("socket error");
		return -1;
	}
	// int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
	int opt=1;
	setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));//设置端口复用
	//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
	struct sockaddr_in sev;
	sev. sin_family=AF_INET;
	sev. sin_port=htons(8888);
	inet_pton(AF_INET,"192.168.230.130",&sev.sin_addr.s_addr);
	int ret=bind(lfd,(struct sockaddr*)&sev,sizeof(sev));
	if(ret<0)
	{
		perror("bind error");
		return -1;
	}
	ret=listen(lfd,128);
	if(ret<0)
	{
		perror("listen error");
		return -1;
	}
	//int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
	int maxfd=lfd;//定义最大fd
	fd_set readfds;//定义一个读文件描述符集
	FD_ZERO(&readfds);//清零
	FD_SET(lfd,&readfds);//加入readfds让内核监听lfd
	fd_set tmpfds;//定义一个传出读文件描述符集
	FD_ZERO(&tmpfds);
	int cfd;//通信描述符
	int i;
	int j;
	int n;
	char buf[64];
	int nready;//定义可读事件的数量
	while(1)
	{
		tmpfds=readfds;
//让tmpfds成为传入传出参数
//输入:告诉内核要监听哪些文件描述符
//输出:内核告诉你有哪些文件描述符发生变化
		nready=select(maxfd+1,&tmpfds,NULL,NULL,NULL);
		if(nready<0)
		{
			if(errno==EINTR)//被信号打断的错误不视为错误
			{
				continue;
			}
			else 
			{
				break;
			}
		}
		if(FD_ISSET(lfd,&tmpfds))//判断lfd是否发生变化,若变化说明有客户端连接
		{
			cfd=accept(lfd,NULL,NULL);//接受连接,获取一个通信文件描述符
			if(maxfd<cfd)
			{
				maxfd=cfd;//修改最大的maxfd
			}
			FD_SET(cfd,&readfds);//将cfd加入readfds,让内核监控是否变化
			if(--nready==0)//为真说明只有一个可读事件,继续循环
			{
				continue;
			}
		}
		for(i=lfd+1;i<=maxfd;i++)
		{
			if(FD_ISSET(i,&tmpfds))//判断cfd是否发生变化,若变化则说明客户端发信息
			{
				memset(buf,0x00,sizeof(buf));
				n=read(i,buf,sizeof(buf));
				if(n<=0)
				{
					printf("read error or client closer,n==[%d]\n",n);
					close(i);//关闭文件描述符
					FD_CLR(i,&readfds);//从内核中移除,取消监控
				}
            else//n>0的情况
    {
				printf("n==[%d],buf==[%s]\n",n,buf);
				for(j=0;j<n;j++)
				{
					buf[j]=toupper(buf[j]);
				}
				write(i,buf,n);
     }
                if(--nready==0)//减少循环次数
				{
					break;
				}
			}
		}

	}
	close(lfd);
	return 0;
}

思考:如果有效的通信文件描述符过少,会让循环的次数变多。

优化方法:

int fd[100];

for()

{

fd[i]=-1;//先把内容都初始化为-1,证明没被占用

}

优化后代码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <errno.h>
#include<ctype.h>
int main()
{
	//int socket(int domain, int type, int protocol);
	int lfd=socket(AF_INET,SOCK_STREAM ,0);
	if(lfd<0)
	{
		perror("socket error");
		return -1;
	}
	// int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
	int opt=1;
	setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
	//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
	struct sockaddr_in sev;
	sev. sin_family=AF_INET;
	sev. sin_port=htons(8888);
	inet_pton(AF_INET,"192.168.230.130",&sev.sin_addr.s_addr);
	int ret=bind(lfd,(struct sockaddr*)&sev,sizeof(sev));
	if(ret<0)
	{
		perror("bind error");
		return -1;
	}
	ret=listen(lfd,128);
	if(ret<0)
	{
		perror("listen error");
		return -1;
	}
	//int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
	int maxfd=lfd;
	fd_set readfds;
	FD_ZERO(&readfds);
	FD_SET(lfd,&readfds);
	fd_set tmpfds;
	FD_ZERO(&tmpfds);
	int cfd;
	int i;
	int j;
	int n;
	int a[100];//存放cfd的有效数组
	int x;
	for(x=0;x<100;x++)
	{
		a[x]=-1;//初始化为-1
	}
	int maxindex=0;
	char buf[64];
	int nready;
	while(1)
	{
		tmpfds=readfds;
		nready=select(maxfd+1,&tmpfds,NULL,NULL,NULL);
		if(nready<0)
		{
			if(errno==EINTR||errno==ECONNABORTED)
			{
				continue;
			}
			else 
			{
				perror("select error");
				break;
			}
		}
		if(FD_ISSET(lfd,&tmpfds))
		{
			cfd=accept(lfd,NULL,NULL);
			if(cfd<0)
			{
				if(errno==EINTR||errno==ECONNABORTED)
				{
					continue;
				}
				else 
				{
					perror("accept error");
					break;
				}

			}
			for(x=0;x<100;x++)
			{
				if(a[x]==-1)
				{
					a[x]=cfd;//为-1说明空闲,让cfd加入
					break;
				}
			}
			if(x==100)
			{
				close(cfd);//100则说明满了拒绝连接
				printf("there is no space\n");
				continue;//继续while循环
			}

			if(maxindex<x)
			{
				maxindex=x;//更新a数组的最大下标
			}
			if(maxfd<cfd)
			{
				maxfd=cfd;//更新最大cfd
			}
			FD_SET(cfd,&readfds);//加入监视区
			if(--nready==0)
			{
				continue;
			}
		}

		for(i=0;i<=maxindex;i++)//直接从a里面遍历
		{
			if(a[i]==-1)
			{
				continue;//为-1说明此位置没有cfd,继续for循环
			}
			if(FD_ISSET(a[i],&tmpfds))
			{
				memset(buf,0x00,sizeof(buf));
				n=read(a[i],buf,sizeof(buf));
				if(n<=0)
				{
					printf("read error or client closer,n==[%d]\n",n);
					close(a[i]);
					FD_CLR(a[i],&readfds);//这个要在a[i]=-1之前,不然会让内核监视一个-1的错误的文件描述符
					a[i]=-1;//变成-1,让此位置空闲
				}
				else//n>0的情况
				{
					printf("i am a[%d],n==[%d],buf==[%s]\n",i,n,buf);
					for(j=0;j<n;j++)
					{
						buf[j]=toupper(buf[j]);
					}
					write(a[i],buf,n);
				}
				if(--nready==0)
				{
					break;
				}

			}
		}

	}
	close(lfd);
	return 0;
}

结果:

 

可以看到0位置被关闭时,下一个又会在0位置存放cfd通信文件描述符。 

select优点:
 

1 一个进程可以支持多个客户端

2 select支持跨平台

select缺点:

1 代码编写困难

2 会涉及用户区到内核区来回拷贝

3 当客户端多个连接,但少数活跃,select效率低

4 最多支持1024个客户端连接 

  • 27
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落落落sss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值