高并发服务器(一)--- select模型

目录

19.1 什么是并发

19.2 多进程并发服务器

19.3 多线程并发服务器

19.4 多路 I/O 转接服务器

(1)select
(2)epoll

19.5 线程池并发服务器

19.6 UDP局域网络服务器

19.7 其他常用函数




19.1 什么是并发

并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。———来源《百度百科》

顾名思义,高并发就是在指定时间内,系统同时能够处理大量的请求(连接数)。

19.2 多线程并发服务器

由于多进程服务器在创建进程时要消耗较大的系统资源,所以这里不做过多赘述。

流程如下:
在这里插入图片描述

服务器接收到客户端的一个请求之后,之后临时fork()一个进程,父进程等待下一个请求,子进程处理客户端请求

转接一份代码:

/*server.c*/

#include<stdio.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include<sys/wait.h>
#include<sys/type.h>

#define MAXLINE 80
#define SERV_PORT 8000

void di_sigchild(int num)
{
	waitpid(0,NULL,WNOHANG);
}

int main(void)
{
	struct sockaddr_in servaddr,cliaddr;
	socklen_t cliaddr_len;
	int listenfd,connfd;

	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i,n;
	
	pid_t pid;
	
// 套路开始
	struct sigaction newact;  //信号量,那篇我准备重写
	newact.sa_handler = di_sigchild;
	sigemptyset(&newact.sa_mask);
	newact.sa_flags = 0;
	sigaction(SIGCHLD,&newact,NULL);
	
	listenfd = socket(AF_INET,SOCK_STREAM,0);	//创建一个网络套接字
	
	bzero(&servaddr,sizeof(servaddr));	//清空结构体变量,准备开始刻画
	servaddr.sin_family = AF_INET;  //配置网络协议
	servaddr.sin_port = htonl(SERV_PORT);  //配置端口号
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  //配置网络地址
	bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));  //好了,可以绑定了  别忘了历史遗留问题

	listen(listenfd,20);  //开始监听,允许20个进程进来


//开始接收数据了
	printf("Accepting connections···  \n");  //写完一定要来检查一下这个换行,一不小心就忘记了
	while(1)
	{
		cliaddr_len = sizeof(cliaddr);  //这得实时更新
		connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);  //接收连接
		
		pid = fork();
		if(pid == 0)
		{
			close(listenfd);
			while(1)
			{
				n = read(connfd,buf,MAXLINE);  //处理事务(这里为读取内容)
				if(n == 0)
				{
					printf("The other side has been closed··· \n");
					break;
				}
				printf("Read from %s at port %d \n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
		/*将客户端的地址读取到str里面然后打印*/  /*将端口号转换成整形数输出*/

				for(i = 0;i < n; i++)
				{
					buf[i] = toupper(buf[i]);  //换大写
				}	
				write(connfd,buf,n);  //写回去
			}
			close(connfd);//用完关咯
			return 0;
		}
		else if(pid > 0)
		{
			close(connfd);
		}
		else
		{
			perror("fork:");
		}
	}
}

//客户端代码和上一章的一样,不占篇幅了。
19.3 多线程并发服务器

多线程服务器是对多进程服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建。

使用多线程并发服务器时要注意以下问题:

1、调整进程内最大文件描述符上限
2、考虑线程同步
3、服务于客户端的线程退出时的退出处理
4、系统负载。随着连接客户端的增多,导致其他线程不能及时得到CPU

上代码:


/* server.c*/

#include<stdio.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>

#define MAXLINE 80
#define SERV_PORT 8000

struct s_info
{
	struct sockaddr_in cliaddr;
	int connfd;
}

void *run(void *arg)
{
	int n,i;
	struct s_info *ts = (struct s_info *)arg;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];

	pthread_detach(pthread_self());  //在创建线程前设置线程属性为分离态

	while(1)
	{
		n = read(ts->connfd,buf,MAXLINE);  //处理事务(这里为读取内容)
		if(n == 0)
		{
			printf("The other side has been closed··· \n");
			break;
		}
		printf("Read from %s at port %d \n",inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr,str,sizeof(str)),ntohs((*ts).cliaddr.sin_port));
		/*将客户端的地址读取到str里面然后打印*/  /*将端口号转换成整形数输出*/
		
		for(i = 0;i < n; i++)
		{
			buf[i] = toupper(buf[i]);  //换大写
		}	
		write(ts->connfd,buf,n);  //写回去
		close(ts->connfd);//用完关咯is->
	}
}

int main(void)
{
	struct sockaddr_in servaddr,cliaddr;
	socklen_t cliaddr_len;
	int listenfd,connfd;
	int i = 0;
	pthread_t tid;
	struct s_info[400];
	
	listenfd = socket(AF_INET,SOCK_STREAM,0);	//创建一个网络套接字
	
	bzero(&servaddr,sizeof(servaddr));	//清空结构体变量,准备开始刻画
	servaddr.sin_family = AF_INET;  //配置网络协议
	servaddr.sin_port = htonl(SERV_PORT);  //配置端口号
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  //配置网络地址
	bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));  //好了,可以绑定了  别忘了历史遗留问题

	listen(listenfd,20);  //开始监听,允许20个进程进来


//开始接收数据了
	printf("Accepting connections···  \n");  //写完一定要来检查一下这个换行,一不小心就忘记了
	while(1)
	{
		cliaddr_len = sizeof(cliaddr);  //这得实时更新
		connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);  //接收连接
		ts[i].cliaddr = cliaddr;
		ts[i].connfd = connfd;

		pthread_create(&tid,NULL,run,(void)ts[i]);  //达到线程最大时,create做出错处理,增加线程稳定性
		i++;
	}
	return 0;
}

客户端代码同上

附上伪代码
在这里插入图片描述

19.4 多路 I/O 转接服务器

(重点来了)
三种模型,select、poll(略去)、epoll

(1)select 模型

1、select 能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数。
2、解决1024以下客户端使用select模型是很合适的,但如果连接客户端过多,由于select采用的是轮询模式,将会大大降低服务器响应效率。因此不应该在select上花过多时间。

直接来看select函数吧
#include<sys/select.h>

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

参数释义:
nfds:监控的文件描述符表里最大的文件描述符+1,此参数会告诉内核级检测前面多少个文件描述符的状态。(等待套接字的数量)
readfds:要检查读事件的容器
writefds:要检查写事件的容器
exceptfds:监控异常事件的容器
timeout:超时时间

//使用fd_set数据类型来表示这个描述字集,我们不用去关心具体的实现细节。
void FD_CLR(int fd,fd_set *set); //把文件描述符表里的某个fd清零
int FD_ISSET(int fd,fd_set *set); //测试文件描述符表中某个fd是否置1  //存在返回非0,不存在返回0
void FD_SET(int fd,.fd_set *set); //将文件描述符表中某个fd置1
void FD_ZERO(fd_set *set); //把文件描述符表中所有位清零

//表从第0位开始计数

行了,上代码

/*server.c*/

#include<stdio.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#define MAXLINE 80
#define SERV_PORT 8000

int main(void)
{
	struct sockaddr_in servaddr,cliaddr;
	socklen_t cliaddr_len;
	int i,maxi,maxfd,listenfd,connfd,sockfd;
	int nready,client[FD_SETSIZE];
	ssize_t n;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	fd_set rset,allset;
	
// 套路开始
	listenfd = socket(AF_INET,SOCK_STREAM,0);
	
	bzero(&servaddr,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htonl(SERV_PORT);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
	listen(listenfd,20);

	maxfd = listenfd;  //初始化
	maxi = -1; //client[]的下标
	for(i = 0 ; i < FD_SETSIZE ; i++ )
	{
		client[i] = -1; //用-1初始化client
	}

	FD_ZERO(&allset);
	FD_SET(listenfd,&allset);  //构建select监控文件描述符表

	while(1)
	{
		rset = allset;  /*区别于前面的服务器的关键步骤*/  
		//每次循环时都重新设置elect监控信号集

		nready = select(maxfd,&rest,NULL,NULL,NULL);  //只监控读
		if(nready < 0)
		{
			perrno("select error:");
		}
		if(FD_ISSET(listenfd,&rest)) //添加监控成功
		{
			//开始接收数据了
			printf("Accepting connections···  \n");  //写完一定要来检查一下这个换行,一不小心就忘记了
			cliaddr_len = sizeof(cliaddr);  //这得实时更新
			connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);  //接收连接

			printf("Read from %s at port %d \n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
		/*将客户端的地址读取到str里面然后打印*/  /*将端口号转换成整形数输出*/

			for(i = 0;i < FD_SETSIZE; i++)
			{
				if(client[i] < 0)
				{
					client[i] = connfd;  //保存accept返回的文件描述符到client【】里
				}

				if( i == FD_SETSIZE )
				{
					printf("Too many clients \n",stderr);
					exit(-1);
				}
	
				FD_SET(connfd,&allset);  //添加一个新的文件描述符到监控信号集里
				if(connfd>maxfd)
				{
					maxfd = connfd;  //select 第一个参数需要
				}
				if(i > maxi)
				{
					maxi = i;
				}

				if(--nready == 0)
				{
					continue;  //如果没有更多的就绪文件描述符,则继续回到上面的select阻塞监听,负责处理未处理完的就绪文件描述符
				}
			}	
		
			for(i = 0;i < maxi; i++)  //检测哪个client有数据就绪
			{
				if((sockfd = client[2])<0)
				{
					continue;
				}
				if(FD_ISSET(sockfd,&rset))
				{
					/*当client关闭连接时,服务器端也关闭对应连接*/
					close(sockfd);
					FD_CLR(sockfd,&allset); //解除select监控此文件描述符
					client[i] = -1;
				}
				else 
				{
					int j;
					for(j = 0;j < n; j++)
					{
						buf[j] = toupper(buf[j]);
					}
					write(sockfd,buf,n);
				}
				if(--nready == 0)
				{
					break;
				}
			}
		}
			close(listenfd);//用完关咯
			return 0;
	}
		
}


//8说了,我已经要倒了。。
epoll下个章节再说吧

发布了61 篇原创文章 · 获赞 3 · 访问量 1622

猜你喜欢

转载自blog.csdn.net/qq_43762191/article/details/104002165