对epoll的重新学习【附源码】

news2025/1/11 18:33:19

目录

一、概述

二、使用

三、API

3.1 epoll_create(int size)

3.2 epoll_ctl(int epfd,int op, int fd. struct epoll_event *event)

3.3 epoll_wait(int epfd, struct peoll_event *events, int maxevents, int timeout)

3.4 *ssize_t read(int fd, void buf, size_t count);

3.5 *ssize_t write(int fd, const void buf, size_t count);

3.6writev与readv

四、通过一个对一个错误的程序进行修改学习epoll


一般学习某种技术可以白嫖的最全的途径就是百度

epoll_百度百科

不过百度的东西都比较浓缩,有时候 ”不是人话“(调侃罢了,确实全但是不好理解),

下面我来学习一下,虽然以前学过,可惜没好好学。下图是他们三个的工作流程

http://121.41.88.239/usr/uploads/2021/11/190406553.png

epoll的调用过程 

 

 

下面文章参考了以下博文:

EPOLL高并发服务器_epoll服务器_OwnResponsibility的博客-CSDN博客

IO多路复用之epoll_陈子青 - See的博客-CSDN博客_epoll多路复用

epoll详解_每天进步一奈奈的博客-CSDN博客_epoll

深入了解epoll模型(特别详细) - 知乎

一、概述

        Linux内核为处理大批量文件描述符而作了改进的poll

        epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

二、使用

epoll有两种工作状态:

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

ET (edge-triggered)是高速工作方式,只支持non-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。

        ET和LT的区别就在这里体现,LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。而ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。

三、API

3.1 epoll_create(int size)

---------------------------------------------------------------------------------------------------------------------------------
作用:创建一个红黑树模型的实例,用于管理待检测的文件描述符的集合。

size: 监听数目                                                                                                        
监听文件描述符的个数,与内存大小有关。 Linux 内核 2.6.8 版本以后,这个参数是被忽略的,只需要指定一个大于 0 的数值就可以了。
函数返回值:
        失败:返回 - 1
        成功:返回一个有效的文件描述符,通过这个文件描述符就可以访问创建的 epoll 实例了

---------------------------------------------------------------------------------------------------------------------------------

3.2 epoll_ctl(int epfd,int op, int fd. struct epoll_event *event)

---------------------------------------------------------------------------------------------------------------------------------

epfd:

为epoll_creat的句柄 --具体是那一课epoll树

epoll_create () 函数的返回值,通过这个参数找到 epoll 实例

op:

这是一个枚举值,控制通过该函数执行什么操作。表示动作,用3个宏来表示:

EPOLL_CTL_ADD (注册新的fd到epfd)
EPOLL_CTL_MOD (修改已经注册的fd的监听事件)
EPOLL_CTL_DEL (从epfd删除一个fd)

fd:文件描述符,即要添加 / 修改 / 删除的文件描述符

evnet:

epoll 事件,用来修饰第三个参数对应的文件描述符的,指定检测这个文件描述符的什么事件。

events:委托 epoll 检测的事件

EPOLLIN:读事件,接收数据,检测读缓冲区,如果有数据该文件描述符就绪

(包括对端socket 正常关闭)

EPOLLOUT:写事件,发送数据,检测写缓冲区,如果可写该文件描述符就绪

EPOLLERR:异常事件(表示对应的文件描述符发生错误

data:用户数据变量,这是一个联合体类型,通常情况下使用里边的 fd 成员,用于存储待检测的文件描述符的值,在调用 epoll_wait() 函数的时候这个值会被传出。

函数返回值:

失败:返回 - 1

成功:返回 0

---------------------------------------------------------------------------------------------------------------------------------

struct epoll_event{
	_uint32_t events; /*Epoll events*/
	epoll_data_t data; /*User data variable*/
};
typedef union epoll_data{
	void *ptr;
	int fd;
	uint32_t u32;
	uint64_t u64;
}epoll_data_t;

3.3 epoll_wait(int epfd, struct peoll_event *events, int maxevents, int timeout)

---------------------------------------------------------------------------------------------------------------------------------

events:

用来存内核得到事件的集合,传出参数,这是一个结构体数组的地址,里边存储了已就绪的文件描述符的信息。

maxevents:

告知内核这个events有多大, 这个maxevents的值不能大于创建epoll_create() 时的size,

修饰第二个参数,结构体数组的容量(元素个数)

timeout:

如果检测的 epoll 实例中没有已就绪的文件描述符,该函数阻塞的时长,单位 ms 毫秒
-1: 函数一直阻塞,直到 epoll 实例中有已就绪的文件描述符之后才解除阻塞
0:函数不阻塞,不管 epoll 实例中有没有就绪的文件描述符,函数被调用后都直接返回

大于 0:如果 epoll 实例中没有已就绪的文件描述符,函数阻塞对应的毫秒数再返回

返回值:

成功返回有多少文件描述符就绪, 事件到时返回0. 出错时返回-1;
等于 0:函数是阻塞被强制解除了,没有检测到满足条件的文件描述符
大于 0:检测到的已就绪的文件描述符的总个数
失败:返回 - 1

epfd:epoll_create () 函数的返回值,通过这个参数找到 epoll 实例

---------------------------------------------------------------------------------------------------------------------------------

3.4 *ssize_t read(int fd, void buf, size_t count);

---------------------------------------------------------------------------------------------------------------------------------

参数:

fd:文件描述符
buf: 读取数据的缓冲区
count: 缓冲区大小

返回值:

返回值>0; 实际读到的字节数 buf=1024

两种情况:
返回值==buf;
返回值<buf;

返回值==0;

数据读完(读到文件, 管道,socket 末尾—对端关闭)

返回值==-1; 异常

1. errno==EINTR 被信号中断, 重启/quit
2. errno==EAGAIN;(EWOULLDBLOCK) 非阻塞方式读取, 并没有数据
3. errno==ECONNRESET; 说明连接被重置,需要close(),移除监听队列
4. 其他值,出现错误. ------perror_exit

---------------------------------------------------------------------------------------------------------------------------------

3.5 *ssize_t write(int fd, const void buf, size_t count);

---------------------------------------------------------------------------------------------------------------------------------

参数:

fd:文件描述符
buf: 待写数据的缓冲区
count: 缓冲区大小

返回值:

成功: 写入字节数
失败: -1, 设置

返回值==buf;
返回值<buf;

相比read和write一次只能下发一个IO请求,并将数据读写到一个指定的缓冲区,readv和writev可以一次性指定多个缓冲区进行读写。

---------------------------------------------------------------------------------------------------------------------------------

3.6writev与readv

#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);


其中 const struct iovec *iov 设置多个缓冲区

struct iovec{
	void *iov_base; /*starting address of buffer*/
	size_t iov_len; //size of buffer;
}


writev和readv 都是以 iov[0],iov[1],…,iov[n-1] 的順序输出或者读入的, 返回值是输出|读入的字节总数长度之和.
 

四、通过一个对一个错误的程序进行修改学习epoll

客户端

#include "net.h"

void usage(char *s)
{
	printf("%s addr port\n",s);
	printf("addr is dotted address\n");
	printf("port should above 5000\n");
}

int main(int argc,char *argv[])
{
	//0 Check input parameter.
	if(argc!=3)
	{
		usage(argv[0]);
		exit(1);
	}
	int port=-1;
	port=atoi(argv[2]);
	if(port<5000)
	{
		usage(argv[0]);
		exit(1);
	}

	//1 Socket.
	int cfd=-1;
	cfd=socket(AF_INET,SOCK_STREAM,0);
	if(cfd<0)
	{
		perror("socket");
		exit(1);
	}
	//2 Connect.
	struct sockaddr_in sin;
	bzero(&sin,sizeof(sin));
	sin.sin_family=AF_INET;
	sin.sin_port=htons(port);
	if(inet_pton(AF_INET,argv[1],(void*)&sin.sin_addr.s_addr)!=1)
	{
		perror("inet_pton");
		exit(1);
	}

	if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin))<0)
	{
		perror("connect");
		exit(1);
	}
	printf("Client(%s:%d):Connect!\n",argv[1],port);

	//3 Write/read.
	char buf[BUFSIZ];
	int ret=-1;
	while(1)
	{	
#if 1
		bzero(buf,BUFSIZ);
		printf("2");
		do{
			ret=read(0,buf,BUFSIZ-1);
		}while(ret<0 && EINTR==errno);
		printf("2");
		if(!ret)
		{
			continue;
		}
		if(ret<0)
		{
			perror("read");
			continue;
		}
		if(write(cfd,buf,strlen(buf))<0)
		{
			perror("write to socket");
			continue;
		}

		if(!strncmp(buf,QUIT,strlen(buf)))
		{
			printf("Quit!\n");
			break;
		}
#endif

		bzero(buf,BUFSIZ);
		do{
			ret=read(cfd,buf,BUFSIZ-1);
		}while(ret<0 && EINTR==errno);
		if(!ret)
		{
			continue;
		}
		if(ret<0)
		{
			perror("read from socket");
			continue;
		}
		printf("server said:%s\n",buf);


		if(!strncmp(buf+strlen(SERVER_STR),QUIT,strlen(QUIT)))
		{
			printf("Quit!\n");
			break;
		}
	}

	//4 Close.
	close(cfd);

}

服务器: 

#include "net.h"

int main()
{
	//1 Socket.
	int lfd=-1;
	lfd=socket(AF_INET,SOCK_STREAM,0);
	if(lfd<0)
	{
		printf("perror");
		exit(1);
	}
	//2 Bind.
	struct sockaddr_in sin;
	bzero(&sin,sizeof(sin));
	sin.sin_family=AF_INET;
	sin.sin_port=htons(SERV_PORT);
	sin.sin_addr.s_addr=htonl(INADDR_ANY);

	if(bind(lfd,(struct sockaddr*)&sin,sizeof(sin))<0)
	{
		perror("bind");
		exit(1);
	}

	//3 Listen.
	if(listen(lfd,BACKLOG)<0)
	{
		perror("listen");
		exit(1);
	}

	//4 Create epoll.
	int epfd=epoll_create(1024);
	if(epfd==-1)
	{
		perror("epoll_create");
		exit(0);
	}
	struct epoll_event ev;
	ev.events=EPOLLIN;
	ev.data.fd=lfd;
	epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);

	struct epoll_event evs[1024];
	int size=sizeof(evs)/sizeof(evs[0]);
	//5 Detect.
	while(1)
	{
		int num=epoll_wait(epfd,evs,size,-1);
		printf("num=%d\n",num);
		for(int i=0;i<num;++i)
		{
			int fd=evs[i].data.fd;
			if(fd==lfd)//fd for listening
			{
				struct sockaddr_in cin;
				socklen_t addrlen=sizeof(cin);
				int cfd=-1;
				cfd=accept(lfd,(struct sockaddr*)&cin,&addrlen);
				if(cfd<0)
				{
					perror("accept");
					exit(1);
				}
				char ipv4_addr[16];
				if(inet_ntop(AF_INET,(void*)&cin.sin_addr,ipv4_addr,sizeof(cin))<0)
				{
					perror("inet_ntop");
					exit(1);
				}
				printf("The client is:%s:%d\n",ipv4_addr,ntohs(cin.sin_port));

				//struct epoll_event ev;
				ev.events=EPOLLIN;
				ev.data.fd=cfd;
				epoll_ctl(cfd,EPOLL_CTL_ADD,cfd,&ev);
			//	continue;
			}
			else//fd for communitcation
			{
				while(1)
				{
					char buf[BUFSIZ];
					char resp_buf[BUFSIZ+10];
					int ret=-1;
					bzero(buf,BUFSIZ);
					do
					{
						ret=read(fd,buf,BUFSIZ-1);
					}while(ret<0 && EINTR==errno);

					if(ret<0)
					{
						perror("read");
						continue;
					}
					if(!ret)//Client closed.
					{
						printf("Client is unconnected.\n");
						epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
						close(fd);
						break;
					}
					printf("read:%s\n",buf);
					if(!strncasecmp(buf,QUIT,strlen(QUIT)))
					{
						printf("Quit.\n");
						break;
					}

					int re=-1;
					bzero(resp_buf,BUFSIZ+10);
					strncpy(resp_buf,SERVER_STR,strlen(SERVER_STR));
					strcat(resp_buf,buf);
					do
					{
						re=write(fd,resp_buf,strlen(resp_buf));
					}while(re<0 && EINTR==errno);
				}
			}
		}
	}

	return 0;
}

共用头文件

#ifndef _NET_H_
#define _NET_H_

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/epoll.h>

#define SERV_PORT 5002
#define SERV_ADDR "192.168.112.130"
#define BACKLOG 5
#define QUIT "quit"
#define SERVER_STR "SERVER:"

#endif

 

可以连接成功但是不能发消息

 发现一个问题,没有回车就打印不到屏幕上

 

缓冲区应该是出现了问题,不加回车不能打印,这个现象 在连接后才产生,初步判断是服务器有问题。

服务器退出了客户端居然没断开,这就更神奇了。 

bind:address already in use的深刻教训以及解决办法_程序猿小泽的博客-CSDN博客

客户端现在是对的了,之前一定会阻塞在读取服务器程序那里注释掉就没问题了。这个退出程序也有问题,我改了一下现在没问题了

#include "net.h"

void usage(char *s)
{
	printf("%s addr port\n",s);
	printf("addr is dotted address\n");
	printf("port should above 5000\n");
}

int main(int argc,char *argv[])
{
	//0 Check input parameter.
	if(argc != 3)
	{
		usage(argv[0]);
		exit(1);
	}
	int port = -1;
	port = atoi(argv[2]);
	if(port < 5000)
	{
		usage(argv[0]);
		exit(1);
	}

	//1 Socket.
	int cfd=-1;
	cfd=socket(AF_INET,SOCK_STREAM,0);
	if(cfd<0)
	{
		perror("socket");
		exit(1);
	}
	//2 Connect.
	struct sockaddr_in sin;
	bzero(&sin,sizeof(sin));
	sin.sin_family=AF_INET;//TCP or UDP 
	sin.sin_port = htons(port);
	if(inet_pton(AF_INET,argv[1],(void*)&sin.sin_addr.s_addr)!=1)
	{
		perror("inet_pton");
		exit(1);
	}
	if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin))<0)
	{
		perror("connect");
		exit(1);
	}
	printf("Client(%s:%d):Connect!\n",argv[1],port);
	//3 Write/read.
	char buf[BUFSIZ];
	int ret=-1;
	fd_set rset;
	while(1)
	{	
#if 1
		
			bzero(buf,BUFSIZ);
			do{
				ret=read(0,buf,BUFSIZ-1);
			}while(ret<0 && EINTR==errno);
			if(!ret)
			{
				continue;
			}
			if(ret<0)
			{
				perror("read");
				continue;
			}
			if(write(cfd,buf,strlen(buf))<0)
			{
				perror("write to socket");
				continue;
			}
			if (!strncasecmp (buf, "quit", strlen ("quit"))) {	//用户输入了quit字符
				printf ("Client is exiting!\n");
				break;
			}
#if 0
			if(!strncmp(buf,"quit",strlen(buf)))
			{
				printf("Quit!\n");
				break;
			}
#endif		
#endif
#if 0
			bzero(buf,BUFSIZ);
			do{
				ret=read(cfd,buf,BUFSIZ-1);
			}while(ret<0 && EINTR==errno);
			if(!ret)
			{
				continue;
			}
			if(ret<0)
			{
				perror("read from socket");
				continue;
			}
			printf("server said:%s\n",buf);
		
	
			if(!strncmp(buf+strlen(SERVER_STR),QUIT,strlen(QUIT)))
			{
				printf("Quit!\n");
				break;
			}
#endif		
	}

	//4 Close.
	close(cfd);

}

好吧最后老师出马,创建事件的文件描述符写错了。(呜呜呜呜)

 客户端程序没有问题,我测试用的服务器没有回传才会卡,服务器程序把文件描述符换了,然后去掉那个死循环:

#include "net.h"

int main()
{
	//1 Socket.
	int lfd=-1;
	lfd=socket(AF_INET,SOCK_STREAM,0);
	if(lfd<0)
	{
		printf("perror");
		exit(1);
	}
	//2 Bind.
	struct sockaddr_in sin;
	bzero(&sin,sizeof(sin));
	sin.sin_family=AF_INET;
	sin.sin_port=htons(SERV_PORT);
	sin.sin_addr.s_addr=htonl(INADDR_ANY);

	if(bind(lfd,(struct sockaddr*)&sin,sizeof(sin))<0)
	{
		perror("bind");
		exit(1);
	}

	//3 Listen.
	if(listen(lfd,BACKLOG)<0)
	{
		perror("listen");
		exit(1);
	}

	//4 Create epoll.
	int epfd = epoll_create(1);
	if(epfd == -1)
	{
		perror("epoll_create");
		exit(0);
	}
	struct epoll_event ev;
	ev.events=EPOLLIN;
	ev.data.fd=lfd;
	epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);

	struct epoll_event evs[1024];
	int size = sizeof(evs)/sizeof(evs[0]);
	//5 Detect.
	while(1)
	{
		int num = epoll_wait(epfd,evs,size,-1);
		if(num == -1){
			perror("epoll_wait");
			exit(0);
		}
		printf("num=%d\n",num);
		for(int i = 0;i < num;++i)
		{
			if(!(evs[i].events & EPOLLIN))
				continue;
			int fd = evs[i].data.fd;
			if(fd == lfd)//fd for listening
			{
#if 1
				printf("1\n");
				struct sockaddr_in cin;
				socklen_t addrlen=sizeof(cin);
				int cfd=-1;
				cfd = accept(lfd,(struct sockaddr*)&cin,&addrlen);
				if(cfd<0)
				{
					perror("accept");
					exit(1);
				}
				char ipv4_addr[16];
				if(inet_ntop(AF_INET,(void*)&cin.sin_addr,ipv4_addr,sizeof(cin))<0)
				{
					perror("inet_ntop");
					exit(1);
				}
				printf("The client is:%s:%d\n",ipv4_addr,ntohs(cin.sin_port));

				//struct epoll_event ev;
				ev.events=EPOLLIN;
				ev.data.fd=cfd;
				epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
#else
				cfd = accept(events[i].data.fd,(struct sockaddr *)&clientAddr, &addrlen);
				printf("new client %s,port=%d \n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
				event.events = EPOLLIN;
	            event.data.fd = cfd;
				epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);
			//	continue;
#endif
			}
			else//fd for communitcation
			{
				//while(1)
				//{
					printf("2\n");
					char buf[BUFSIZ];
					char resp_buf[BUFSIZ+10];
					int ret=-1;
					bzero(buf,BUFSIZ);
					do
					{
						ret=read(fd,buf,BUFSIZ-1);
					}while(ret<0 && EINTR==errno);

					if(ret<0)
					{
						perror("read");
						continue;
					}
					if(!ret)//Client closed.
					{
						printf("Client is unconnected.\n");
						epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
						close(fd);
						break;
					}
					printf("read:%s\n",buf);
					if(!strncasecmp(buf,QUIT,strlen(QUIT)))
					{
						printf("Quit.\n");
						break;
					}

					int re=-1;
					bzero(resp_buf,BUFSIZ+10);
					strncpy(resp_buf,SERVER_STR,strlen(SERVER_STR));
					strcat(resp_buf,buf);
					do
					{
						re=write(fd,resp_buf,strlen(resp_buf));
					}while(re<0 && EINTR==errno);
				//}
			}
			#if 0
			else{   //否则,是连接的客户端事件,读取数据
				rdlen = read(events[i].data.fd,buf,BUFLEN);
				if(rdlen>0){
					printf("read buf=%s\n",buf);
				}else if (rdlen==0){//客户连接中断,删除epoll监听的客户端文件描述符
					event.events = EPOLLIN;
					event.data.fd = events[i].data.fd;
					epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event);	
				}
			#endif
		}
	}

	return 0;
}

 我终于成为某人不可或缺的人了,人逢喜事精神爽呀。正好拓展坞到了,开整。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/193899.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python模块之codecs

python 模块codecs python对多国语言的处理是支持的很好的&#xff0c;它可以处理现在任意编码的字符&#xff0c;这里深入的研究一下python对多种不同语言的处理。 有一点需要清楚的是&#xff0c;当python要做编码转换的时候&#xff0c;会借助于内部的编码&#xff0c;转换…

Spark读取Hive数据的两种方式与保存数据到HDFS

Spark读取Hive数据的两种方式与保存数据到HDFS Spark读取Hive数据的方式主要有两种 1、 通过访问hive metastore的方式&#xff0c;这种方式通过访问hive的metastore元数据的方式获取表结构信息和该表数据所存放的HDFS路径&#xff0c;这种方式的特点是效率高、数据吞吐量大、…

规则引擎-drools-4-动态生成drl文档

文章目录drools 引擎工作原理动态生成drl文件示例步骤模板文件 decision_rule_template.drt生成规则文件serviceDecisionNodeFact实体对象生成的drl字符串如下KieHealper 执行动态生成drl文件的原理实际应用过程中&#xff0c;很多时候&#xff0c;规则不是一成不变的&#xff…

54.Isaac教程--RealSense相机

RealSense相机 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录RealSense相机RealsenseCamera Codelet示例应用程序故障排除固件注意事项通过 USB 3.0 电缆使用 USB 3.0 端口x86_64 Linux 主机设置设置电源模型英特尔RealSense 435 摄像头…

分享159个ASP源码,总有一款适合您

ASP源码 分享159个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 159个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1EaQuRA6mxyylrNWLq8iKVA?pwdaljz 提取码&#x…

springmvc知识巩固

文章目录回顾spring知识前言什么是SpringMVCSpringMVC的优点SpringMVC的常用注解Controller注解的作用ResponseBody注解的作用SpringMVC重定向和转发SpringMVC主要组件SpringMVC的执行流程回顾spring知识 上篇整理了“spring知识巩固”常见面试题&#xff0c;有需要的伙伴请点…

Java基础:源码讲解Collection及相关实现List、Set、Queue

1 缘起 说到Java第一问&#xff0c;很多人的第一反应是三大特性&#xff0c;那么接下来&#xff0c;可能就是集合了。 Collection是Java必知必会&#xff0c;即使没有系统学习&#xff0c;在实际的开发过程中&#xff0c;Collection也是应用最广泛的。 当然&#xff0c;一般的…

ESP-IDF:归并排序测试

ESP-IDF:归并排序测试 /归并排序测试/ void printarry18 (int arr[],int length) { for(int i0;i<length;i) { cout<<arr[i]<<" "; } cout<<endl; } void merge(int arr[],int start, int end, int mid,int * temp) { int length 0;//记录te…

进程间通信之管道(匿名管道与命名管道)

进程间通信之管道进程间通信管道什么是管道管道分类——1.匿名管道匿名管道举例管道的特点管道分类——2.命名管道创建一个命名管道举例命名管道的打开规则匿名管道与命名管道的区别具体使用举例&#xff1a;例子1-用命名管道实现文件拷贝例子2-用命名管道实现server&clien…

POI介绍简介

2.1 POI介绍 Apache POI是用Java编写的免费开源的跨平台的Java API&#xff0c;Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能&#xff0c;其中使用最多的就是使用POI操作Excel文件。 jxl&#xff1a;专门操作Excel maven坐标&#xff1a; <depend…

Linux中安装MySQL及常见问题汇总

一、安装前工作 1、卸载之前的数据库 在安装前需要确定现在这个系统有没有 mysql&#xff0c;如果有那么必须卸载 &#xff08;在 centos7 自带的是 mariaDb 数据库&#xff0c;所以第一步是卸载数据库&#xff09;。 #查看mariadb数据库&#xff1a; rpm -qa | grep maria…

moment.js根据时间戳计算与当前时间相差多少天

Moment旨在在浏览器和Node.js中工作。 所有代码都应该在这两种环境中都有效&#xff0c;并且所有单元测试都在这两种环境中运行。 目前&#xff0c;以下浏览器用于ci系统&#xff1a;Windows XP上的Chrome&#xff0c;Windows 7上的IE 8,9和10&#xff0c;Windows 10上的IE 1…

小车程序、安装vs2019和必要的相关软件

环境 sqlserver2019 vs2019企业版本 安装vs2019步骤 小车程序 C# .net vs2019 sqlserver2019 小车程序地址 小车运行的几种轨迹 一&#xff0c;自动运行到指定节点 找到manual按钮&#xff0c;找到目的节点&#xff0c;search move。 二&#xff0…

[数学] 三次样条

三次样条 已知有一组点x0,x1,x2,⋯,xnx_0, x_1, x_2, \cdots, x_nx0​,x1​,x2​,⋯,xn​, 其中, xt<xt1x_t<x_{t1}xt​<xt1​, y(xt)yty(x_t)y_ty(xt​)yt​, 及该点处的切线y′(xt)yt′y(x_t)y_ty′(xt​)yt′​ 每两个相邻的点之间可以作一个三次曲线 在所有相邻…

let/const相关内容(二)

let/const作用域提升 1&#xff09;根据前面所学的内容知道&#xff0c;var定义的变量是可以作用域提升的。 console.log(foo); // undefined var foo "foo"虽然在第一行中foo还没有被定义&#xff0c;但是在执行代码前&#xff0c;会预编译&#xff0c;先定义f…

ModStartBlog v6.6.0 多语言增强,缓存后台优化

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议。 功能特性 丰富的模块市场&#xff0c;后台一键快速安装会…

【完美解决】Github action报错remote: Write access to repository not granted.

报错及效果图报错代码效果图解决方案必要步骤可能有效的步骤报错及效果图 本解决方案是笔者通过Github action运行项目时报错的解决方案&#xff0c;如果是本地运行报此错&#xff0c;未必有效果。 报错代码 remote: Write access to repository not granted. fatal: unable t…

密评FAQ:如何确定网络和通信安全层面的测评对象?

信息系统一般通过网络技术来实现与外界的互联互通&#xff0c;GB/T 39786-2021《信息安全技术信息系统密码应用基本要求》规定了信息系统在网络和通信安全层面的密码应用技术要求&#xff0c;这些要求涉及到通信的主体&#xff08;通信双方&#xff09;、信息系统与网络边界外建…

(1)深入理解Java虚拟机-内存模型

深入理解Java虚拟机 Java虚拟机运行时数据区 程序计数器 ​ 程序计数器&#xff08;Program Counter Register&#xff09;是一块较小的内存空间&#xff0c;它可以看作是当前线程所执行的 字节码的行号指示器。在Java虚拟机的概念模型里 [1] &#xff0c;字节码解释器工作时…

大学开学必备清单、大学生必备的五件电子产品

转眼间就到了新一年的春季开学&#xff0c;在校生进入了新的一个年级学习。电子产品早就成为每个人的必备&#xff0c;尤其是大学生在校时期&#xff0c;更是上网课、日常查询资料的必备&#xff0c;当然还有一些社交、娱乐的因素也都是通过各式各样的电子产品来满足和实现。接…