提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、网络编程中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限制的.