学习笔记Linux6_网络

#include<sys/socket.h>
#include<arpa/inet.h>    //大小端转换
#include<netdb.h>    //DNS

一、Socket套接字

为了开发网络应用,系统提供一套API函数接口,用于网络应用开发,这些接口称为套接字函数

struct sockaddr_int{
    sin_family=AF_INET;
    sin_port=8080;
    sin_addr.s_addr=127.0.0.1;
}addr;

int sockfd=socket(AF_INET,SOCK_STREAM|SOCK_DRAM,0);    //socket创建
//成功返回sockfd,失败返回-1

bind(int sockfd,struct sockaddr* addr/*使用旧的网络信息结构体,向前兼容*/,socklen_t addrlen);
//成功返回0,失败返回-1。对socket设置自定义信息,保持信息不变

listen(sockfd,backlog/*等待连接队列大小,默认128*/);//监听连接过程以及对应的链接事件(TCPServer)
//成功返回0,失败返回-1

htons();    //本机到网络16位,小端转大端端口号
htonl();    //小端转大端p
ntohs();
ntohl();
inet_ntop();    //大端序转字符串
inet_pton();
inet_addr();
connect(int sockfd,struct sockaddr* destaddr,sockelen_t addrlen);    //请求连接函数(发起握手请求)
//成功返回0,失败返回-1,如果网络异常可能引发阻塞

int clientsock = accept(int serversocket,struct sockaddr* clientaddr,socklen_t* addrlen);    //阻塞等待并建立连接函数(完成三次握手),连接成功后立即返回
//成功返回sock,失败返回-1,如果网络异常可能引发阻塞

send(int sockfd,char* msg,int len,MSG_NOSIGNAL/*写忽略信号*/);    //向目标发送网络信息

recv(int sockfd,char* buffer,int size,MSG_DONTWAIT/*非阻塞读*/);    //读取接收网络信息

TCP连接方式:keep-alive长链接,close短链接

//TcpServer.h
#include<mysock.h>

#define SHUTDOWN 1

/*支持 tcp连接 及连接反馈的模型*/

int main(){
	//close 循环持续连接
	int server_sock,client_sock;
	struct sockaddr_in addrClient;
	socklen_t addrlen;
	server_sock=net_initializer();
	printf("Test tcp server version 1.0\n");
	char client_ip[16];
	while(SHUTDOWN){
		addrlen=sizeof(addrClient);
		client_sock=ACCEPT(server_sock,(struct sockaddr*)&addrClient,&addrlen);
		bzero(client_ip,16);
		inet_ntop(AF_INET,&addrClient.sin_addr.s_addr,client_ip,16);//大端序转字符串
		printf("client port %d,client ip %s\n",ntohs(addrClient.sin_port),client_ip);
		first_response(client_sock,client_ip);
		business(client_sock);//读取请求,处理请求,反馈响应
        close(client_sock);
	}
	close(server_sock);
	printf("server tis done\n");
	return 0;
}
//TcpClient.h
#include<mysock.h>

int main(){
	//创建套接字
	int server_sock=SOCKET(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	//服务器套接字信息
	struct sockaddr_in addrServer;
	bzero(&addrServer,sizeof(addrServer));
	addrServer.sin_family=AF_INET;
	addrServer.sin_port=htons(8080);
	addrServer.sin_addr.s_addr=inet_addr("82.157.31.74");

	CONNECT(server_sock,(struct sockaddr*)&addrServer,sizeof(addrServer));
	
	int nRecvNum=0;
	int nSendNum=0;
	char recvBuf[1024]="";
	char sendBuf[1024]="";
	
	nRecvNum=RECV(server_sock,recvBuf,sizeof(recvBuf),0);
	printf("server:%s\n",recvBuf);

	fgets(sendBuf,sizeof(sendBuf),stdin);
	nSendNum=SEND(server_sock,sendBuf,sizeof(sendBuf),0);
		
	nRecvNum=RECV(server_sock,recvBuf,sizeof(recvBuf),0);
	printf("server:%s\n",recvBuf);

	close(server_sock);
	return 0;
}

  套接字函数的包裹,网络功能的包裹:在系统函数的基础上,拓展函数的功能,在函数的基础上包裹一层功能更丰富的函数

//myscok.h
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<errno.h>
#include<ctype.h>
#include<time.h>

int SOCKET(int domain,int type,int protocol);
int BIND(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
int LISTEN(int sockfd,int backlog);
int CONNECT(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
int ACCEPT(int sockfd,struct sockaddr* addr,socklen_t* addrlen);
ssize_t RECV(int sockfd,void* buf,size_t len,int flags);
ssize_t SEND(int sockfd,const void* buf,size_t len,int flags);

int net_initializer();
void first_response(int sock,char* cip);

//业务处理模块
void business(int client_sock);
//mysock.c
#include<mysock.h>

int SOCKET(int domain,int type,int protocol){
	int sock;
	if((sock=socket(domain,type,protocol))==-1){
		perror("socket create failed");
		return -1;
	}
	return sock;
}

int BIND(int sockfd,const struct sockaddr* addr,socklen_t addrlen){
	if((bind(sockfd,addr,addrlen))==-1){
		perror("bind call failed");
		return -1;
	}
	return 0;
}

int LISTEN(int sockfd,int backlog){
	if((listen(sockfd,backlog))==-1){
		perror("listen call failed");
		return -1;
	}
	return 0;
}

int CONNECT(int sockfd,const struct sockaddr* addr,socklen_t addrlen){
	if((connect(sockfd,addr,addrlen))==-1){
		perror("connect call failed");
		return -1;
	}
	return 0;
}

int ACCEPT(int sockfd,struct sockaddr* addr,socklen_t* addrlen){
	int sock;
	if((sock=accept(sockfd,addr,addrlen))==-1){
		perror("accept call failed");
		return -1;
	}
	return sock;
}

ssize_t RECV(int sockfd,void* buf,size_t len,int flags){
	ssize_t size;
	if((size=recv(sockfd,buf,len,flags))==-1){
		if(errno==EAGAIN){
			printf("recv nonblock return\n");
		}
		else{
			perror("recv call failed");
		}
		return -1;
	}
	return size;
}

ssize_t SEND(int sockfd,const void* buf,size_t len,int flags){
	ssize_t size;
	if((size=send(sockfd,buf,len,flags))==-1){
		perror("send call failed");
		return -1;
	}
	return size;
}

int net_initializer(){
	//套接字信息
	struct sockaddr_in addrServer;
	bzero(&addrServer,sizeof(addrServer));
	addrServer.sin_family=AF_INET;
	addrServer.sin_port=htons(8080);
	addrServer.sin_addr.s_addr=htonl(INADDR_ANY);
	//创建套接字
	int server_sock;
	server_sock=SOCKET(AF_INET,SOCK_STREAM,0);
	BIND(server_sock,(struct sockaddr*)&addrServer,sizeof(addrServer));
	LISTEN(server_sock,128);
	return server_sock;
}

void first_response(int sock,char* cip){
	char response[1024];
	bzero(response,sizeof(response));
	sprintf(response,"hi, %s wellcome test TCP server.\n",cip);
	SEND(sock,response,strlen(response),0);
}

void business(int client_sock){
	//读取一次客户端请求,处理后,立刻断开
	ssize_t recv_size;
	char recv_buffer[1024];
	bzero(recv_buffer,sizeof(recv_buffer));
	recv_size=RECV(client_sock,recv_buffer,sizeof(recv_buffer),0);

	if((strcmp(recv_buffer,"time\n")==0) || (strcmp(recv_buffer,"time")==0)){
		//响应系统时间
		time_t tp;
		char tbuf[1024];
		bzero(tbuf,sizeof(tbuf));
		ctime_r(&tp,tbuf);
		SEND(client_sock,tbuf,strlen(tbuf),0);
		printf("server,response time success.\n");
		close(client_sock);
	}
	else{
		//数据处理,大小写转换 toupper()
		int cnt=0;
		while(cnt<recv_size){
			recv_buffer[cnt]=toupper(recv_buffer[cnt]);
			cnt++;
		}
		SEND(client_sock,recv_buffer,recv_size,0);
		printf("server,response data sucess.\n");
		close(client_sock);
	}
}

二、业务

简易业务:例子

客户端向服务端发送time关键字,服务器接收后,向客户端返回系统时间

简单数据处理,客户端向服务端发送字符串,服务端完成大小写转换,并回复

客户端向服务端发送手机号码,服务端向手机发送短信,并附带4位验证码,后续的验证流程忽略

三、服务器基础

CS(客户端服务器模型)BS(浏览器服务器模型)

客户端的主要职责:缓存用户本地数据、面向用户并与服务器进行交互

服务器的基本职责:持久化用户数据,数据中转、高并发(大连接、并发处理(大任务量处理))、安全性、测试

3.1 客户端数据包的传递过程

3.2 服务器的网络穿透

为多端建立一条比较稳定的通信场景

网络信息管理、支持数据中转

3.3 服务器操作系统

3.4 服务器集群(大型网络设备)

3.5 将服务器部署到线上(公网)

个人服务器的搭建:

1、IP 与 MAC 地址绑定,设置绑定固定的IP,MAC地址与ARP地址绑定,避免ARP攻击

2、DNZ主机配置,内网设置DNZ主机(可以对外),设置资源共享,支持外网访问,内网中的其他设备,可以通过地址访问DNZ主机中的资源

3、虚拟服务器,开发设备待定(固定)端口号,设置外部对内访问的端口

4、DDNS 服务,将域名和路由器 WAN + IP进行绑定,互联网其他设备可以通过域名直接定位访问,内网其他设备 

3.6 客户端更有效的连接

一个域名上可以绑定多个ip

3.7 负载均衡

1、轮询机制:如果处理任务的复杂程度不同,分配不均匀

2、计算处理机的负载,按负载分发,将新任务分发给负载最低的

3、绑定分发:用户与设备绑定

3.8 服务器的几个版本

3.8.1 单进程阻塞服务器(1对1):轮询排队处理

服务器主要功能:连接(Accept),请求处理与响应(RECV,SEND)

阻塞等待连接时,无法读写用户数据;阻塞读取数据时,无法建立新连接

3.8.2 单进程非阻塞服务器(1对多):连接与处理交替执行

非阻塞连接、非阻塞读写

//将sock设为非阻塞,即可以非阻塞accept
int flag=fcntl(server_sock,F_GETFL);
flag|=O_NONBLOCK;
fcntl(server_sock,F_SETFL,flag);

accpet(server_sock,addr,addrlen);
recv(sock,buffer,sizeof(buffer),MSG_DONTWAIT);

单进程非阻塞模型,可以使用,但是无法彻底解决问题,如果业务较为复杂,依然无法建立新连接。只能满足简易任务和连接数量少的模型

//mysock.c
#include<mysock.h>

int business(int flags){
	ssize_t recv_size;
	char recv_buffer[1024];
	//循环尝试读取客户端请求
	for(int i=0;i<1024;i++){
		if(client_sock_array[i]!=-1){
			int client_sock=client_sock_array[i];
			
			bzero(recv_buffer,sizeof(recv_buffer));
			recv_size=RECV(client_sock,recv_buffer,sizeof(recv_buffer),flags);
			if(recv_size>0){
				printf("client:%s\n",recv_buffer);
				if((strcmp(recv_buffer,"time\n")==0) || (strcmp(recv_buffer,"time")==0)){
					//响应系统时间
					time_t tp;
					char tbuf[1024];
					bzero(tbuf,sizeof(tbuf));
					ctime_r(&tp,tbuf);
					SEND(client_sock,tbuf,strlen(tbuf),0);
					printf("server,response time success.\n");
				}
				else{
					//数据处理,大小写转换 toupper()
					int cnt=0;
					while(cnt<recv_size){
						recv_buffer[cnt]=toupper(recv_buffer[cnt]);
						cnt++;
					}
					SEND(client_sock,recv_buffer,recv_size,0);
					printf("server,response data sucess.\n");
				}
			}
			else if(recv_size==0){//sock断开
				printf("client exit, close sock %d\n",client_sock);
				close(client_sock);
				//从数组中删除
				client_sock_array[i]=-1;
			}
			else if(recv_size==-1){
			}
		}
	}
	
}
//Tcp_NONLOCK_Server.c
#include<mysock.h>

#define SHUTDOWN 1

int main(){
	int server_sock,client_sock;
	
	struct sockaddr_in addrClient;
	for(int i=0;i<1024;i++){
		client_sock_array[i]=-1;
	}
	socklen_t addrlen;
	server_sock=net_initializer();

	//将server_sock改为非阻塞,实现accept非阻塞
	int flag=fcntl(server_sock,F_GETFL);
	flag|=O_NONBLOCK;
	fcntl(server_sock,F_SETFL,flag);
	
	printf("Test tcp server version 1.0\n");
	char client_ip[16];
	while(SHUTDOWN){
		addrlen=sizeof(addrClient);
		client_sock=ACCEPT(server_sock,(struct sockaddr*)&addrClient,&addrlen);
		if(client_sock>0){
			bzero(client_ip,16);
			inet_ntop(AF_INET,&addrClient.sin_addr.s_addr,client_ip,16);//大端序转字符串
			printf("client port %d,client ip %s\n",ntohs(addrClient.sin_port),client_ip);
			first_response(client_sock,client_ip);
			for(int i=0;i<1024;i++){
				if(client_sock_array[i]==-1){
					client_sock_array[i]=client_sock;
					break;
				}
			}
		}
		//recv非阻塞读
		business(MSG_DONTWAIT);//读取请求 处理请求 反馈响应
	}
	close(server_sock);
	printf("server tis done\n");
	return 0;
}

3.8.3 并发服务器(多进程)

#include <mysock.h>

int main(){
	pid_t pid;
	int server_sock,client_sock;
	server_sock=net_initializer();
	
	printf("process server version 1.0 acceiting.\n");
	
	struct sockaddr_in client_addr;
	socklen_t addrlen;
	
	char client_ip[16];
	while(1){
		addrlen=sizeof(client_addr);
		client_sock=ACCEPT(server_sock,(struct sockaddr*)&client_addr,&addrlen);
		pid=fork();
		if(pid>0){
			//连接成功
			bzero(client_ip,16);
			inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,client_ip,16);
			first_response(client_sock,client_ip);
		}
		else if(pid==0){
			//子进程完成客户端处理
			ssize_t recv_size;
			char recv_buffer[1024];	
			bzero(recv_buffer,sizeof(recv_buffer));
	
			while((recv_size=RECV(client_sock,recv_buffer,sizeof(recv_buffer),0))>=0){
				if(recv_size>0){
					printf("client:%s\n",recv_buffer);
					if((strcmp(recv_buffer,"time\n")==0) || (strcmp(recv_buffer,"time")==0)){
						//响应系统时间
						time_t tp;
						char tbuf[1024];
						bzero(tbuf,sizeof(tbuf));
						ctime_r(&tp,tbuf);
						SEND(client_sock,tbuf,strlen(tbuf),0);
						printf("server,response time success.\n");
					}
					else{
						//数据处理,大小写转换 toupper()
						int cnt=0;
						while(cnt<recv_size){
							recv_buffer[cnt]=toupper(recv_buffer[cnt]);
							cnt++;
						}
						SEND(client_sock,recv_buffer,recv_size,0);
						printf("server,response data sucess.\n");
					}
				}
				else if(recv_size==0){//sock断开
					printf("client exit, close sock %d\n",client_sock);
					close(client_sock);
					exit(0);
				}
			}
		}
		else{
			perror("fork failed");
			exit(0);
		}
	}

}

解决僵尸态

注意到,wait()是阻塞的,解决方法——进程分层

#include <mysock.h>

//回收的捕捉函数
void sig_wait(int n){
	//循环非阻塞回收
	pid_t zpid;
	while(((zpid=waitpid(-1,NULL,WNOHANG)))>0){
		printf("wait thread 0x%x sucess,zpid %d\n",(unsigned int)pthread_self(),zpid);
	}
}

//线程函数
void* thread_wait(void* arg){
	//设置分离态
	pthread_detach(pthread_self());
	//捕捉设定
	struct sigaction act,oact;
	act.sa_handler=sig_wait;
	act.sa_flags=0;
	sigemptyset(&act.sa_mask);
	sigaction(SIGCHLD,&act,&oact);
	printf("wait thread set sigaction sucess\n");
	//解除屏蔽
	sigprocmask(SIG_SETMASK,&act.sa_mask,NULL);
	while(1)
		sleep(1);//等待信号并完成回收
}

void p_business(int client_sock){
	ssize_t recv_size;
	char recv_buffer[1024];	
	bzero(recv_buffer,sizeof(recv_buffer));
	
	while((recv_size=RECV(client_sock,recv_buffer,sizeof(recv_buffer),0))>=0){
		if(recv_size>0){
			printf("client:%s\n",recv_buffer);
			if((strcmp(recv_buffer,"time\n")==0) || (strcmp(recv_buffer,"time")==0)){
				//响应系统时间
				time_t tp;
				char tbuf[1024];
				bzero(tbuf,sizeof(tbuf));
				ctime_r(&tp,tbuf);
				SEND(client_sock,tbuf,strlen(tbuf),0);
				printf("server,response time success.\n");
			}
			else{
				//数据处理,大小写转换 toupper()
				int cnt=0;
				while(cnt<recv_size){
					recv_buffer[cnt]=toupper(recv_buffer[cnt]);
					cnt++;
				}
				SEND(client_sock,recv_buffer,recv_size,0);
				printf("server,response data sucess.\n");
			}
		}
		else if(recv_size==0){//sock断开
			printf("client exit, close sock %d\n",client_sock);
			close(client_sock);
			exit(0);
		}
	}
}

int main(){
	pid_t pid;
	int server_sock,client_sock;
	server_sock=net_initializer();
	
	printf("process server version 1.0 acceiting.\n");
	
	struct sockaddr_in client_addr;
	socklen_t addrlen;
	
	char client_ip[16];

	//设置屏蔽
	sigset_t set,oset;
	sigemptyset(&set);
	sigaddset(&set,SIGCHLD);
	sigprocmask(SIG_SETMASK,&set,&oset);
	//创建回收线程
	pthread_t tid;
	pthread_create(&tid,NULL,thread_wait,NULL);

	while(1){
		addrlen=sizeof(client_addr);
		client_sock=ACCEPT(server_sock,(struct sockaddr*)&client_addr,&addrlen);
		pid=fork();
		if(pid>0){
			//连接成功
			bzero(client_ip,16);
			inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,client_ip,16);
			first_response(client_sock,client_ip);
		}
		else if(pid==0){
			//子进程完成客户端处理
			p_business(client_sock);
		}
		else{
			perror("fork failed");
			exit(0);
		}
	}

}

3.8.4 并发服务器(多线程)

四、多路IO复用技术/多路IO转接

各个监听模型的主要区别:监听集合和函数不同 

4.1 socket监听

问题:是否应该在连接(accept)或读取(recv)事件触发之前,执行相关函数,是否应该阻塞等待连接或数据,这种阻塞开销是否有意义和价值?

一个sock上有很多事件:

SOCK_RD读事件:socket缓冲区有数据待读、待处理,触发读事件

SOCK_WR写事件:socket缓冲中存在待写数据,触发写事件,用户通过send发送

SOCK_ER异常事件:socket出现异常,触发异常事件

上述处理方式开销大,效率低 

socket流程:

1、socket监听某种事件

2、等待事件就绪

3、事件就绪

4、处理就绪

5、处理完毕

socket应该先监听,当socket就绪后再进行处理,accept和recv都是处理流程

前置为socket监听,后置为处理

4.2 select 经典IO复用模型

轮询模型

监听集合fd_set对应文件描述符表,每一位对应一个socket。位码0表示取消监听,1表示启动监听

select()阻塞轮询监听集合中所有的socket 

#include <sys/select.h>

fd_set set;
select(maxfd+1/*当前监听的socket数*/,
       &set/*读事件*/,
       &set/*写事件*/,
       &set/*异常事件*/,
       NULL/*timeout,空表示阻塞工作*/);
//返回就绪的数量

select()传入监听集合,传出就绪集合,就绪的保留为1,未就绪的归0

以监听为主导,交替执行。大多数时为睡眠态,没有大量的非阻塞返回,实现单进程的1对多

select模型最大能监听1020个(1024 - server_sock - stidin - stdout - stderr = 1020)

FD_ZERO(&set);    //初始化集合为0
FD_SET(int sock,&set);    //对监听集合中某个sock对应位设置1,启动监听
FD_CLR(int sock,&set);    //对监听集合中某个sock对应位设置0,取消监听
FD_ISSET(int sock,&oset);    //返回值0或1,查看sock在oset中的位码

select函数支持定时阻塞,可以在最后一个参数timeout设置定时时长,到时立即返回

timeout==NULL        阻塞监听

timeout.s=0        timeout.us=0        非阻塞监听 

timeout.s=1        timeout.us=1000        定时阻塞监听

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <ctype.h>
#include <string.h>

int main(){
	int server_sock,client_sock;
	int client_sock_array[1020];
	for(int i=0;i<1020;i++)
		client_sock_array[i]=-1;

	struct sockaddr_in addrServer,addrClient;
	bzero(&addrServer,sizeof(addrServer));
	addrServer.sin_family=AF_INET;
	addrServer.sin_port=htons(8080);
	addrServer.sin_addr.s_addr=htonl(INADDR_ANY);
	server_sock=socket(AF_INET,SOCK_STREAM,0);
	bind(server_sock,(struct sockaddr*)&addrServer,sizeof(addrServer));
	listen(server_sock,128);

	socklen_t addrlen;
	char cip[16];

	int maxfd=server_sock;//记录最大sock
	
	fd_listen_set set,ready_set;
	FD_ZERO(&listen_set);
	FD_SET(server_sock,&listen_set);//设置首个监听

	char buffer[1024];
	int recv_size;
	printf("select running.\n");
	int readynum;
	while(1){
		ready_set =listen_set;
		if((readynum=select(maxfd+1,&ready_set,NULL,NULL,NULL))==-1){
			perror("select failed");
			exit(0);
		}
		//readynum=3;
		printf("readynum:%d\n",readynum);
		while(readynum){//循环处理多个就绪
			//辨别就绪
			if(FD_ISSET(server_sock,&ready_set)){
				//server_sock 就绪处理流程
				addrlen=sizeof(addrClient);
				client_sock=accept(server_sock,(struct sockaddr*)&addrClient,&addrlen);
				if(client_sock>0){
					//插入数组
					for(int i=0;i<1020;i++)
						if(client_sock_array[i]==-1){
							client_sock_array[i]=client_sock;
							break;
						}
					//设置监听
					FD_SET(client_sock,&listen_set);
					//重置最大maxfd
					if(maxfd<client_sock)
						maxfd=client_sock;
					//返回首次响应
					bzero(cip,16);
					inet_ntop(AF_INET,&addrClient.sin_addr.s_addr,cip,16);
					bzero(buffer,sizeof(buffer));
					sprintf(buffer,"hi %s connection sucess..\n",cip);
					send(client_sock,buffer,strlen(buffer),0);
                    //当对应的sock事件处理完毕,server_sock(连接成功)client_sock(读取成功),应该将就绪集合中套接字这一位的位码置为0,因为如果ready_num > 1,不做该处理服务器会一直认为是客户端发送了TCP链接请求,从而导致错误处理
					FD_CLR(server_sock,&ready_set);
				}
			}
			else{
				//查找就绪的client_sock并处理
				for(int i=0;i<1020;i++)
					if(client_sock_array[i]!=-1)
						if(FD_ISSET(client_sock_array[i],&ready_set)){
							//就绪处理流程,小写转大写
							recv_size=recv(client_sock_array[i],buffer,sizeof(buffer),0);
							if(recv_size>0){
								int flag=0;
								while(recv_size>flag){
									buffer[flag]=toupper(buffer[flag]);
									flag++;
								}
								send(client_sock_array[i],buffer,recv_size,0);
							}
							else if(recv_size==0){//断开流程
								//关闭连接
								close(client_sock_array[i]);
								//取消监听
								FD_CLR(client_sock_array[i],&listen_set);
								//数组删除
								client_sock_array[i]=-1;
							}
                            FD_CLR(client_sock_array[i],&ready_set);    //消去client_sock
							break;
						}
			}
			readynum--;
		}
	}
    close(server_sock);
    printf("server shutdown\n");
	return 0;
}

select模型的优点:

1、兼容性较强,各个平台语言都有对select的支持,方便移植

2、方便实现,代码较轻量级

3、支持微妙级别的定时阻塞,如果监听模型对时间精度有要求,select支持

select模型的缺点:

1、监听数量少(1024不可修改),可以采用并发模型通过多个进程增加总监听数量(开销大)

2、select只返回就绪的数量,用户需要自行查找就绪的socket

3、select传入监听集合,就绪后改为就绪集合,导致监听集合不可重用,需要进行传入传出分离

4、select设置sock事件监听,是以集合为单位,批处理,无法对不同的sock设置不同的监听事件,可监听的事件种类较少

5、select监听采用轮询模式,随着监听数量怎大,导致IO处理性能线性下降

为了避免轮询问题持续占用cpu资源,锁死1024

cpu的大量占用影响服务器服务、数据库访问、网络数据吞吐量,磁盘数据的访问等等IO操作

6、随着select多轮使用,会产生大量的拷贝开销与挂载开销,这些开销都是没有意义的

每轮select都会将用户层监听集合拷贝到内核层的监听集合(会出现无意义的重复拷贝),然后将sock挂载到IO设备监听序列(也会出现无意义的重复挂载),轮询监听后返回就绪集合

4.3 poll监听模型

轮询模型

poll模型支持用户自定义的结构体数组作为监听集合

#include <sys/poll.h>

struct pollfd {
    fd=sock;    //设置对应的sock表示监听,-1表示取消监听
    events=POLLIN|POLLOUT|POLLERR;    //设置监听事件
    revents=POLLIN;    //传出就绪事件,当对应的sock就绪,系统设置
}node;

struct pollfd listen_list[10000];    //poll模型的监听集合

poll(struct pollfd* listen_list/*监听数组地址*/,
     10000/*最大监听数*/,
     -1/*timeout:-1阻塞,0非阻塞,>0定时阻塞*/);    //监听函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include <sys/poll.h>

int main(){
	int server_sock,client_sock;

	struct sockaddr_in addrServer,addrClient;
	bzero(&addrServer,sizeof(addrServer));
	addrServer.sin_family=AF_INET;
	addrServer.sin_port=htons(8080);
	addrServer.sin_addr.s_addr=htonl(INADDR_ANY);
	server_sock=socket(AF_INET,SOCK_STREAM,0);
	bind(server_sock,(struct sockaddr*)&addrServer,sizeof(addrServer));
	listen(server_sock,128);

	socklen_t addrlen;
	char cip[16];

	struct pollfd client_sock_array[1024];//监听数组
	for(int i=0;i<1024;i++)//初始化监听集合
		client_sock_array[i].fd=-1;
	//设置首个监听
	client_sock_array[0].fd=server_sock;
	client_sock_array[0].events=POLLIN;

	char buffer[1024];
	int recv_size;
	printf("poll running.\n");
	int readynum;
	while(1){
		if((readynum=poll(client_sock_array,1024,-1))==-1){
			perror("poll failed");
			exit(0);
		}
		readynum=3;
		while(readynum){//循环处理多个就绪
			//辨别就绪
			if(client_sock_array[0].revents==POLLIN){
				//server_sock 就绪处理流程
				addrlen=sizeof(addrClient);
				client_sock=accept(server_sock,(struct sockaddr*)&addrClient,&addrlen);
				if(client_sock>0){
					//插入数组
					for(int i=1;i<1024;i++)
						if(client_sock_array[i].fd==-1){
							//设置监听
							client_sock_array[i].fd=client_sock;
							client_sock_array[i].events=POLLIN;
							break;
						}
					//返回首次响应
					bzero(cip,16);
					inet_ntop(AF_INET,&addrClient.sin_addr.s_addr,cip,16);
					bzero(buffer,sizeof(buffer));
					sprintf(buffer,"hi %s connection sucess..\n",cip);
					send(client_sock,buffer,strlen(buffer),0);
					client_sock_array[0].revents=0;
					//消0,防止ready_num > 1时会多次误判断就绪套接字为服务器套接字

				}
			}
			else{
				//查找就绪的client_sock并处理
				for(int i=1;i<1024;i++)
					if(client_sock_array[i].fd!=-1)
						if(client_sock_array[i].revents==POLLIN){
							//就绪处理流程,小写转大写
							recv_size=recv(client_sock_array[i].fd,buffer,sizeof(buffer),0);
							if(recv_size>0){
								int flag=0;
								while(recv_size>flag){
									buffer[flag]=toupper(buffer[flag]);
									flag++;
								}
								send(client_sock_array[i].fd,buffer,recv_size,0);
							}
							else if(recv_size==0){//断开流程
								//关闭连接
								close(client_sock_array[i].fd);
								//数组删除
								client_sock_array[i].fd=-1;
							}
							client_sock_array[i].revents=0;    //消0
							break;
						}
			}
			readynum--;
		}
	}
	close(server_sock);
	printf("server shutdown.\n");
	return 0;
}

poll模型的优点:

1、events传入监听,revents传出就绪,无需用户分离

2、可以监听的sock事件比较丰富,可以对不同的sock设置不同的监听事件,比较灵活

poll模型的缺点:

1、受到轮询限制,poll监听数量有限

2、poll只返回就绪的数量,用户需要自行查找就绪的socket

3、poll监听采用轮询模式,随着监听数量怎大,导致IO处理性能线性下降

cpu的大量占用影响服务器服务、数据库访问、网络数据吞吐量,磁盘数据的访问等等IO操作

4、随着poll多轮使用,会产生大量的拷贝开销与挂载开销,这些开销都是没有意义的

5、兼容性比较差,linux操作系统都没有完全兼容

6、poll不支持微妙级别定时,支持毫秒

4.4 Epoll模型

异步回调模型

Epoll采用红黑树作为监听集合,也称为监听树

可以支持系统中最大数量的sock监听,且没有额外开销

struct epoll_event{
    data.fd=sock;    //设置监听sock
    events=EPOLLIN|EPOLLOUT|EPOLLERR;    //设置监听事件
    ep_poll_callback();    //回调函数
}node;
//epoll监听到就绪,返回就绪数量,并且传出就绪的节点,方便用户处理

int epfd=epoll_create(max/*监听的最大值*/);    //创建监听树
//返回指向监听树的描述符,后续访问修改树都通过epfd

epoll_ctl(epfd/*监听树描述符*/,
          EPOLL_CTL_ADD|EPOLL_CTL_DEL|EPOLL_CTL|MOD/*添加|删除|修改*/,
          int index_sock/*目标套接字*/,
          struct epoll_event* node/*修改的节点*/);    
//对监听树进行增删改,此函数是线程安全的,自带互斥锁,多线程使用没用问题

int readynum=epoll_wait(epfd/*监听树描述符*/,
                        struct epoll_event* ready_array/*就绪数组*/,
                        max/*就绪最大数*/,
                        -1/*工作模式timeout:-1阻塞,0非阻塞,>0定时阻塞*/);
//监听函数,就绪数组需要用户自行定义

修改树节点只允许修改监听的事件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include <sys/epoll.h>

#define EPOLL_MAX 10000

int main(){
	int server_sock,client_sock;

	struct sockaddr_in addrServer,addrClient;
	bzero(&addrServer,sizeof(addrServer));
	addrServer.sin_family=AF_INET;
	addrServer.sin_port=htons(8080);
	addrServer.sin_addr.s_addr=htonl(INADDR_ANY);
	server_sock=socket(AF_INET,SOCK_STREAM,0);
	bind(server_sock,(struct sockaddr*)&addrServer,sizeof(addrServer));
	listen(server_sock,128);

	socklen_t addrlen;
	char cip[16];

	int epfd;//监听树描述符
	struct epoll_event node;
	struct epoll_event ready_array[EPOLL_MAX];
	//创建监听树
	epfd=epoll_create(EPOLL_MAX);
	node.data.fd=server_sock;
	node.events=EPOLLIN;
	epoll_ctl(epfd,EPOLL_CTL_ADD,server_sock,&node);//设置监听

	char buffer[1024];
	int recv_size;
	printf("epoll running.\n");
	int readynum;
	while(1){
		if((readynum=epoll_wait(epfd,ready_array,EPOLL_MAX,-1))==-1){
			perror("epoll failed");
			exit(0);
		}
		int i=0;
		while(readynum){//循环处理多个就绪
			//辨别就绪
			if(ready_array[i].data.fd==server_sock){
				//server_sock 就绪处理流程
				addrlen=sizeof(addrClient);
				client_sock=accept(server_sock,(struct sockaddr*)&addrClient,&addrlen);
				if(client_sock>0){
					node.data.fd=client_sock;
					epoll_ctl(epfd,EPOLL_CTL_ADD,client_sock,&node);
					//返回首次响应
					bzero(cip,16);
					inet_ntop(AF_INET,&addrClient.sin_addr.s_addr,cip,16);
					bzero(buffer,sizeof(buffer));
					sprintf(buffer,"hi %s connection sucess..\n",cip);
					send(client_sock,buffer,strlen(buffer),0);
					
				}
			}
			else{
				//就绪处理流程,小写转大写
				recv_size=recv(ready_array[i].data.fd,buffer,sizeof(buffer),0);
				if(recv_size>0){
					int flag=0;
					while(recv_size>flag){
						buffer[flag]=toupper(buffer[flag]);
						flag++;
					}
					send(ready_array[i].data.fd,buffer,recv_size,0);
				}
				else if(recv_size==0){//断开流程
					//关闭连接
					close(ready_array[i].data.fd);
					//删除节点
					epoll_ctl(epfd,EPOLL_CTL_DEL,ready_array[i].data.fd,NULL);//删除监听
				}
			}
			i++;
			readynum--;
		}
	}
	close(server_sock);
	printf("server shutdown.\n");
	return 0;
}

关于轮询: 当一个sock就绪时,网卡得到信息后没有把信息压入IO设备监听序列。而是通过回调函数

等待队列:控制挂起和唤醒

每一个node都绑定自己的就绪项,就绪项可以注册回调函数

Epoll的监听树在内核层创建,用户拷贝以节点为单位,避免了重复的拷贝开销与内核开销,保证每个node只拷贝、绑定一次

流程:当网卡接收到数据,socket缓冲区信息变更,触发回调函数,激活就绪项。就绪项将node加入就绪链表,唤醒等待队列,检查就绪链表是否为空,将就绪链表内容拷贝到就绪数组

一个进程有一个文件描述符表,默认情况下一个进程可以使用1024个文件描述符(包括stdin stdout stderr)

ulimit -a    //查看系统限制

电脑能使用的文件描述符最大值

cat /proc/sys/fs/file-max    //查看系统可用描述符最大数

工作模式:

水平触发模式 EPOLLT

边缘触发模式 EPOLLET

EPOLLONESHOT

IO复用技术只有监听能力的差别,与处理无关

Epoll监听能力强

处理能力是一样的,处理能力要看进程池和线程池

4.5 epoll+thread_pool版本服务器

① 协议的设计、协议封装、协议传输、协议的解析

② 服务器socket监听

③ 请求的并发处理

生产者与消费者关系(Producer,Customer)

生产者负责监听sock处理任务的分发

多进程多线程模型相比线程池的缺点:

1、进程线程随客户端持续(声明周期),如果客户端频繁的连接断开,导致大量的进程线程频繁创建销毁,无意义的开销过大

2、如果每个客户端绑定一个进程或线程,并发连接数量却取决于处理单位数量,太少了

3、处理单元(线程)无法重用

4、没有管理措施,例如根据业务调整处理单位的数量,缺乏线程管理

5、当任务抵达,才进行处理单元的创建,影响任务处理效率

线程池的处理原则(线程池的重用性):

1、预创建原则,线程池中保留备用线程,当任务抵达,直接处理

2、线程的重用性高、不允许线程与某一个特定任务绑定

3、线程管理机制,记录线程数量与状态,根据业务动态的扩容与缩减线程

 4、线程池中采用生产者消费者模型,进行任务传递与管理

管理者manager

1、扩容条件?

1.任务数量>=闲线程数量

2. 随时保留一部分活性线程备用,忙线程占存活线程的70%

3. 扩容不允许超出最大线程阈值

1) citds应该如何利用?

2、缩减条件?

1. 闲线程是忙线程的倍数,启动缩减

2. 缩减后不允许小于阈值

1)如何缩减?

线程数量?

IO密集型 和 CPU密集型

大约为1/8 * 可执行的最大线程(381)

//epoll_thread_server.h
#include<mysock.h>
#include<sys/epoll.h>

//红黑树
int epfd;

//任务类型
typedef struct{
	void* (*business)(void*);//任务地址
	void* arg;//任务参数
}task_t;

//线程池类型
typedef struct{
	int thread_shutdown;//线程池开关
	int thread_max;//线程最大值
	int thread_min;//线程最小值
	int thread_alive;//有效数量
	int thread_busy;//繁忙线程
	int thread_exitcode;//缩减码
	task_t* list;//任务队列
	int list_front;//队列头索引
	int list_rear;//队列尾索引
	int list_max;//队列最大值
	int list_cur;//当前任务数
	pthread_mutex_t lock;//互斥锁
	pthread_cond_t not_full;//生产者条件变量
	pthread_cond_t not_empty;//消费者条件变量
	pthread_t* ctids;//存储消费者tid
	pthread_t mid;//存储管理者id
}pool_t;

void* data_business(void*);//数据处理业务(client_sock就绪)
void* accept_business(void*);//连接业务(server_sock就绪)
pool_t* thread_pool_create(int tmax,int tmin,int list_max);//线程池创建初始化,对线程进行预创建(消费者管理者)
int producer_add_task(pool_t*,task_t);//生产者调用此模块,添加一次任务
int epoll_initializer(int sock);//创建初始化epoll模型,并且设置第一次监听server_scok
int epoll_listen(int sock,pool_t*);//监听模块,负责监听sock是否就绪并按规则添加任务
void* customer_thread(void*);//消费者线程工作,等待于任务队列,持续获取任务并执行
void* manager_thread(void*);//管理者线程工作,等待于阈值,根据条件判断扩容缩减 管理线程数量
int if_thread_alive(pthread_t tid);//线程失效返回0,存活返回1
//main.c
#include<epoll_thread_server.h>

int main(){
	//网络初始化
	int sock;
	sock=net_initializer();
	epoll_initializer(sock);
	pool_t* pt;
	pt=thread_pool_create(100,10,1000);
	//启动监听
	epoll_listen(sock,pt);
	return 0;
}
//epoll_initializer.c
#include <epoll_thread_server.h>

int epoll_initializer(int server_sock){
	//epoll创建初始化
	if((epfd=epoll_create(100000))==-1){
		perror("epoll_inintializer create epoll failed");
		return -1;
	}
	//初始化监听
	struct epoll_event node;
	node.data.fd=server_sock;
	node.events=EPOLLIN|EPOLLONESHOT/*|EPOLLET*/;//边缘
	if((epoll_ctl(epfd,EPOLL_CTL_ADD,server_sock,&node))==-1){
		perror("epoll_initializer listen server_sock failed");
		return -1;
	}
	printf("epoll_init success\n");
	return 0;
}
//thread_pool_create.c
#include <epoll_thread_server.h>

pool_t* thread_pool_create(int tmax,int tmin,int list_max){
	pool_t* pool=NULL;
	if((pool=(pool_t*)malloc(sizeof(pool_t)))==NULL){
		perror("thread_poll_create pool malloc failed");
		return NULL;
	}
	pool->thread_shutdown=1;
	pool->thread_max=tmax;
	pool->thread_min=tmin;
	pool->thread_alive=0;
	pool->thread_busy=0;
	pool->thread_exitcode=0;
	if((pool->list=(task_t*)malloc(sizeof(task_t)*list_max))==NULL){
		perror("thread_poll_create list malloc failed");
		return NULL;
	}
	pool->list_front=0;
	pool->list_rear=0;
	pool->list_cur=0;
	pool->list_max=list_max;
	if(pthread_mutex_init(&pool->lock,NULL)!=0 || pthread_cond_init(&pool->not_empty,NULL)!=0 || pthread_cond_init(&pool->not_full,NULL)!=0){
		printf("thread_pool_create init lock or cond error\n");
		return NULL;
	}
	if((pool->ctids=(pthread_t*)malloc(sizeof(pthread_t)*tmax))==NULL){
		perror("thread_pool_create init ctids failed");
		return NULL;
	}
	//初始化ctids
	bzero(pool->ctids,sizeof(pthread_t)*tmax);
	//预创建线程
	int err;
	for(int i=0;i<tmin;i++){
		if((err=pthread_create(&pool->ctids[i],NULL,customer_thread,(void*)pool))>0){
			printf("thread_pool_create,create_customer failed,%s\n",strerror(err));
			return NULL;
		}
		//pthread_mutex_lock(&pool->lock);
		++pool->thread_alive;
		//pthread_mutex_unlock(&pool->lock);
	}
	//创建管理者
	if((err=pthread_create(&pool->mid,NULL,manager_thread,(void*)pool))>0){
		printf("thread_pool_create,create manager failed,%s\n",strerror(err));
		return NULL;
	}
	return pool;
}
//customer_thread.c
#include <epoll_thread_server.h>

void* customer_thread(void* arg){
	pool_t* p=(pool_t*)arg;
	task_t temp;
	pthread_detach(pthread_self());
	printf("customer 0x%x waiting jobs\n",(unsigned int)pthread_self());
	while(p->thread_shutdown){
		//消费线程,持续获取任务
		pthread_mutex_lock(&p->lock);
		while(p->list_cur==0){
			pthread_cond_wait(&p->not_empty,&p->lock);
			if(!p->thread_shutdown){
				pthread_mutex_unlock(&p->lock);
				printf("customer 0x%x,shutdown its 0,closeint\n",(unsigned int)pthread_self());
				pthread_exit(NULL);
			}
			//缩减
			if(p->thread_exitcode){
				--p->thread_alive;
				--p->thread_exitcode;
				pthread_mutex_unlock(&p->lock);
				printf("customer 0x%x exit\n",(unsigned int)pthread_self());
				pthread_exit(NULL);
			}
		}
		temp.business=p->list[p->list_rear].business;
		temp.arg=p->list[p->list_rear].arg;
		--(p->list_cur);
		p->list_rear=(p->list_rear+1)%p->list_max;
		++(p->thread_busy);
		pthread_mutex_unlock(&p->lock);
		pthread_cond_signal(&p->not_full);
		//执行任务
		temp.business(temp.arg);
		pthread_mutex_lock(&p->lock);
		--(p->thread_busy);
		pthread_mutex_unlock(&p->lock);
	}
	printf("customer 0x%x,shutdown its 0,closeint\n",(unsigned int)pthread_self());
	return NULL;
}
//manager_thread.c
#include <epoll_thread_server.h>

void* manager_thread(void* arg){
	pool_t* p=(pool_t*)arg;
	//持续扫描阈值,完成扩容缩减
	int alive,busy,cur;
	int flag;//遍历下标 ctids
	int add;//计数器(创建线程)
	int err;
	pthread_detach(pthread_self());
	printf("manager thread 0x%x Running\n",(unsigned int)pthread_self());
	while(p->thread_shutdown){
		//取值
		pthread_mutex_lock(&p->lock);
		alive=p->thread_alive;
		busy=p->thread_busy;
		cur=p->list_cur;
		pthread_mutex_unlock(&p->lock);
		//判断扩容
		printf("server status:alive %d busy %d idel %d cur %d B/A %.2f%% A/M %.2f%%\n",alive,busy,alive-busy,cur,(double)busy/alive*100,(double)alive/p->thread_max*100);
		if((alive-busy<=cur || (double)busy/alive*100>=70) && alive+p->thread_min<=p->thread_max){
			for(flag=0,add=0;flag<p->thread_max && add<p->thread_min;flag++){
				if(p->ctids[flag]==0 || !if_thread_alive(p->ctids[flag])){
					if((err=pthread_create(&p->ctids[flag],NULL,customer_thread,(void*)p))>0){
						printf("manager_thread,create cusotomer failed:%s\n",strerror(err));
					}
					++add;
					pthread_mutex_lock(&p->lock);
					++(p->thread_alive);
					pthread_mutex_unlock(&p->lock);
				}
			}		
		}
		//缩减
		if(busy*2<=alive-busy && alive-p->thread_min>=p->thread_min){
			p->thread_exitcode=p->thread_min;
			for(int i=0;i<p->thread_min;i++){
				pthread_cond_signal(&p->not_empty);
			}
		}		
		sleep(1);
	}
	printf("shutdown close,manager exiting..\n");
	pthread_exit(NULL);
}
//epoll_listen.c
#include <epoll_thread_server.h>

int epoll_listen(int server_sock,pool_t* p){
	//监听接口,生产者进行持续的sock事件监听并完成任务分支
	int readynum;
	int flag;
	struct epoll_event readylist[100000];
	task_t temp_bs;
	//循环监听
	printf("epoll_listen\n");
	while(p->thread_shutdown){
		//边缘触发,忽略为处理 避免epoll_wait异常返回
		//水平模式+EPOLLONESHOT or 边缘模式
		//水平模式异常添加任务的问题,将就绪sock投递后,消费者为及时处理导致生产者多次调用epoll_wait,函数立即返回,多次添加任务(异常)
		if((readynum=epoll_wait(epfd,readylist,100000,-1))==-1){
			perror("epoll_listen,epoll_wait call failed");
			return -1;
		}
		//循环判断处理就绪
		flag=0;
		while(readynum){
			if(readylist[flag].data.fd==server_sock){
				//向任务容器中添加一次连接业务
				temp_bs.business=accept_business;
				temp_bs.arg=(void*)&server_sock;
				producer_add_task(p,temp_bs);
				printf("add accept_business %p sucess\n",accept_business);
			}
			else{
				//添加数据处理业务
				temp_bs.business=data_business;
				temp_bs.arg=(void*)&readylist[flag].data.fd;
				producer_add_task(p,temp_bs);
				printf("add data_business %p sucess\n",data_business);
			}
			--readynum;
			++flag;
		}
	}
	printf("epoll_listen exit\n");
	return 0;
}
//accept_business.c
#include<epoll_thread_server.h>

void* accept_business(void* arg){
	int server_sock,client_sock;
	struct sockaddr_in client_addr;
	socklen_t addrlen;
	server_sock=*(int*)arg;
	//处理就绪sock
	addrlen=sizeof(client_addr);
	client_sock=ACCEPT(server_sock,(struct sockaddr*)&client_addr,&addrlen);
	//设置监听
	struct epoll_event node;
	node.data.fd=client_sock;
	node.events=EPOLLIN|EPOLLONESHOT/*|EPOLLET*/;
	epoll_ctl(epfd,EPOLL_CTL_ADD,client_sock,&node);
	//首次响应
	char cip[16];
	bzero(cip,16);
	inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,cip,16);
	first_response(client_sock,cip);
	//epolloneshot
	struct epoll_event nodes;
	nodes.data.fd=server_sock;
	nodes.events=EPOLLIN|EPOLLONESHOT;
	epoll_ctl(epfd,EPOLL_CTL_MOD,server_sock,&nodes);
	return NULL;
}
//data_business.c
#include <epoll_thread_server.h>

void* data_business(void* arg){
	int client_sock=*(int*)arg;
	char buffer[1024];
	bzero(buffer,sizeof(buffer));
	int len;

	time_t tp;
	char time_buf[1024];
	bzero(time_buf,sizeof(time_buf));

	//读取请求数据,非阻塞读
	while((len=RECV(client_sock,buffer,sizeof(buffer),MSG_DONTWAIT))>0){
		//判断客户端请求
		if(strcmp(buffer,"time\n")==0){
			tp=time(NULL);
			ctime_r(&tp,time_buf);
			SEND(client_sock,time_buf,strlen(time_buf),0);
		}
		else{
			SEND(client_sock,"PLease try again\n",17,0);
		}
		//epolloneshot
		struct epoll_event node;
		node.data.fd=client_sock;
		node.events=EPOLLIN|EPOLLONESHOT;
		epoll_ctl(epfd,EPOLL_CTL_MOD,client_sock,&node);
	}
	
	//协议解析器
	return NULL;
}

4.6 EPOLL工作模式

4.6.1 水平触发模式(EPOLLLT)

步骤:

1. 设置监听

2. 监听等待

3. 触发就绪

4. 处理就绪

5. 处理完毕

使用者默认使用水平触发模式监听sock,如果sock事件就绪,必须处理就绪数据,否则不允许进行下一轮监听(负责人模式)

优点:可以督促使用者尽快完整的处理sock数据

缺点:开销大

epoll_wait阻塞表示阻塞监听,如果立即返回,返回值为未处理完毕的sock就绪数量。

4.6.2 边缘触发模式(EPOLLET)

采用边缘模式监听sock,当sock就绪epoll触发一次就绪通知,但是无论用户是否处理就绪都与epoll无关,epoll边缘模式随时可以进行下一轮监听,即使上一轮的没有处理

优点:开销小

缺点:epoll无法保证及时处理就绪,导致丢失

可以针对不同的sock设置不同的监听模式

4.7 epoll安全(EPOLLONSHOT)

避免多个线程处理相同的sock,采用epollonshot方案

EPOLL_CTL_MOD

onshot?

自定义实现keep-alive心跳?(时间轮[较复杂])

五、 服务器性能指标

5.1 TPS QPS

TPS:服务器每秒钟处理事务的能力

1. 客户端发送请求,开始计时

2. 服务器处理

3. 客户端接收响应,计时完毕

QPS:每秒查询效率

服务器的数据库查询访问能力

一个T中包含三个Q?

一个事务中要查三次

多线程处理数据库,服务器性能应当用QPS评价

5.2 吞吐量

大多数时间服务器处理,IO处理(数据库访问以及磁盘或sock处理)较多

一个请求对CPU的消耗比较高,外部接口执行以及IO关联操作都会受影响——吞吐能力低

有一个较高能力的吞吐,都要减少业务处理时的cpu消耗

5.3 负载测试和压力测试

负载测试(load test):性能测试,在数据超出标准数量时,观察服务器负担。

目的:在平均处理量外加入处理量,查看服务器承受性,如何在硬件不变的前提下,优化、提高处理能力

压力测试(strest test):服务器极限测试,在系统资源比较低的情况下,持续执行程序,目的是找到系统宕机原因,失效定位

目的:压力测试持续进行,直到服务器宕机,查看宕机原因以及最大承压数据,进行硬件扩展

5.4 计算并发数

QPS每秒事务数量=事务数量/秒

平均响应时间:服务器采集样本,发送请求,处理请求,反馈响应的时长

并发数量:QPS*平均响应时间

5.5 服务器处理能力

使用服务器测试工具,测试服务器处理能力,返回并发数量

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值