LINUX网络编程中select函数的使用

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、网络编程中C/S模型的缺点

由于accept和read函数都会阻塞,如当read的时候,不能调用accept接收新的连接,当accept阻塞等待的时候不能用read读取数据。对于上述问题,可以使用多进程和多线程技术解决。那如果不想采用多线程技术呢?可以accept和read函数设置为非阻塞, 调用fcntl函数可以将文件描述符设置为非阻塞, 让后再while循环中忙轮询。
那还有其他解决方法吗?有的,可以使用多路IO技术:select,同时监听多个文件描述符,将监控的操作交给内核处理。


提示:以下是本篇文章正文内容,下面案例可供参考

二、术语解释

1.fd_set结构体

fd_set结构体在windows下的定义如下:

typedef struct fd_set {
  u_int  fd_count;//有效fd(文件描述符)的个数
  SOCKET fd_array[FD_SETSIZE];//一个fd结构体对象能存放fd的最大个数
} fd_set, FD_SET, *PFD_SET, *LPFD_SET;

fd_set结构体在linux下的定义如下:

参考链接:https://blog.csdn.net/weixin_43558951/article/details/107840453

typedef long int __fd_mask; 
#define __NFDBITS (8 * (int) sizeof (__fd_mask)) 

typedef struct {
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
} fd_set;

2.FD_CLR

int FD_CLR(int fd,fd_set *set)
//从set集合中删除fd

3.FD_ISSET

int FD_ISSET(int fd,fd_set *set)
//判断fd是否在set集合中

4.FD_SET

void FD_SET(int fd, fd_set *set)
//将fd添加到set集合中

5.FD_ZERO

void FD_ZERO(fd_set *set)
//清空set集合中的文件描述符

6.select函数

select函数原型为:

 //委托内核监控该文件描述符对应的读,写或者错误事件的发生.`
int select(int nfds, fd_set * readfds, fd_set *writefds, fd_set*exceptfds,struct timeval *timeout)

参数说明:

nfds:最大文件描述符+1

readfds:读集合,是一个传入传出参数(因为什么时候接收到信息是不确定的,所以这个参数是有必要的) 传入:告诉内核哪些文件描述符需要监控 传出:内核告诉应用程序哪些文件描述符发生了变化

writefds: 写文件描述符集合(传入传出参数,这个参数很少用,因为我们一般不需要内核监听什么时候写入信息,写入信息的时间是我们自己来决定的)

execptfds: 异常文件描述符集合(传入传出参数)

timeout:一个timeval结构体指针,选择等待的最长时间,以 TIMEVAL 结构的形式提供。将超时参数设置为 null 以阻止操作。
更具体的说,

NULL–表示永久阻塞, 直到有事件发生
0 --表示不阻塞, 立刻返回, 不管是否有监控的事件发生
>0–到指定事件或者有事件发生了就返回

其定义如下:

typedef struct timeval {
  long tv_sec;
  long tv_usec;
} TIMEVAL, *PTIMEVAL, *LPTIMEVAL;

三、使用select开发服务器流程

1.创建socket,得到监听文件描述符lfd

int lfd=Socket(AF_INET,SOCK_STREAM,0);

2.设置端口复用

int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));

3.将监听文件描述符绑定地址与端口号

struct sockaddr_in serv;
serv.sin_family=AF_INET;
serv.sin_port=htons(12345);
serv.sin_addr.s_addr=htonl(INADDR_ANY);
Bind(lfd,(struct sockaddr *)&serv,sizeof(serv));

4.开始监听

Listen(lfd,128);

5.定义文件描述符变量

fd_set readfds;
fd_set tmpfds;

6.清空两个集合

FD_ZERO(&readfds);
FD_ZERO(&tmpfds);

6.将lfd加入到readfds中,委托内核监管

FD_SET(lfd,&readfds);

7.循环监听

int maxfd=lfd;//最大的文件描述符
int nready;//发生变化的文件描述符个数
int cfd;//新的客户端文件描述符
int i;//循环变量
int n;//接收到的字节数
char buf[1024];//接收数据缓存
int sockfd;//客户端文件描述符
while(1){
		tmpfds=readfds;
		nready=select(maxfd+1,&tmpfds,NULL,NULL,NULL);
		//tmpfds是输入输出参数:
		//输入:告诉内核要检测哪些文件描述符
		//输出:内核告诉应用程序有哪些文件描述符发生了变化
		if(nready<0)
		{
			if(errno==EINTR)//被信号中断
			{
				continue;
			}
			break;
		}
		printf("nready==[%d]\n",nready);

		//有客户端连接请求到来
		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++)
		{
			sockfd=i;
			//判断sockfd文件描述符是否有变化
			if(FD_ISSET(sockfd,&tmpfds))
			{
				//读数据
				memset(buf,0x00,sizeof(buf));
				n=Read(sockfd,buf,sizeof(buf));
				if(n<=0)
				{
					//关闭连接
					close(sockfd);

					//将sockfd从readfds中删除
					FD_CLR(sockfd,&readfds);
				}
				else
				{
					printf("n==[%d],buf==[%s]\n",n,buf);

					int k=0;
					for(k=0;k<n;k++)
					{
						buf[k]=toupper(buf[k]);

					}
					Write(sockfd,buf,n);
				}

				if(--nready==0)
				{
					break;
				}
			}
		}
	}

2.完整代码

data = pd.read_csv(
    #include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/select.h>
#include"wrap.h"
#include<ctype.h>

int main()
{
	//创建socket
	int lfd=Socket(AF_INET,SOCK_STREAM,0);

	//设置端口复用
	int opt=1;
	setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));

	//绑定
	struct sockaddr_in serv;
	serv.sin_family=AF_INET;
	serv.sin_port=htons(8888);
	serv.sin_addr.s_addr=htonl(INADDR_ANY);
	Bind(lfd,(struct sockaddr *)&serv,sizeof(serv));

	//监听
	Listen(lfd,128);

	//定义fd_set类型的变量
	fd_set readfds;
	fd_set tmpfds;


	//清空readfds和tmpfds集合
	FD_ZERO(&readfds);
	FD_ZERO(&tmpfds);

	//将lfd加入到readfds中,委托内核监控
	FD_SET(lfd,&readfds);

	int maxfd=lfd;
	int nready;
	int cfd;
	int i;
	int n;
	char buf[1024];
	int sockfd;
	while(1)
	{
		tmpfds=readfds;
		nready=select(maxfd+1,&tmpfds,NULL,NULL,NULL);
		//tmpfds是输入输出参数:
		//输入:告诉内核要检测哪些文件描述符
		//输出:内核告诉应用程序有哪些文件描述符发生了变化
		if(nready<0)
		{
			if(errno==EINTR)//被信号中断
			{
				continue;
			}
			break;
		}
		printf("nready==[%d]\n",nready);

		//有客户端连接请求到来
		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++)
		{
			sockfd=i;
			//判断sockfd文件描述符是否有变化
			if(FD_ISSET(sockfd,&tmpfds))
			{
				//读数据
				memset(buf,0x00,sizeof(buf));
				n=Read(sockfd,buf,sizeof(buf));
				if(n<=0)
				{
					//关闭连接
					close(sockfd);

					//将sockfd从readfds中删除
					FD_CLR(sockfd,&readfds);
				}
				else
				{
					printf("n==[%d],buf==[%s]\n",n,buf);

					int k=0;
					for(k=0;k<n;k++)
					{
						buf[k]=toupper(buf[k]);

					}
					Write(sockfd,buf,n);
				}

				if(--nready==0)
				{
					break;
				}
			}
		}
	}
	close(lfd);
	return 0;
}

3.程序运行结果


总结

select优点:

解决了当read的时候,不能调用accept接收新的连接,当accept阻塞等待的时候不能用read读取数据问题。

select缺点:

1 代码编写困难
2 会涉及到用户区到内核区的来回拷贝
3 当客户端多个连接, 但少数活跃的情况, select效率较低 例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下
4 最大支持1024个客户端连接 select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的,而是由FD_SETSIZE=1024限制的.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值