linux网络编程--select多路IO转接模型

news2024/11/16 21:36:22

目录

1.TCP状态转换图

2.端口复用

3.半关闭状态

4.心跳包

5.高并发服务器模型--select

6.提纲总结


学习目录

  • 熟练掌握TCP状态转换图
  • 熟练掌握端口复用的方法
  • 了解半关闭的概念和实现方式
  • 了解多路IO转接模型
  • 熟练掌握select函数的使用
  • 熟练使用fd_set相关函数的使用
  • 能够编写select多路IO转接模型的代码

1.TCP状态转换图

了解TCP状态转换图可以帮助开发人员查找问题.

说明: 上图中粗线表示主动方, 虚线表示被动方, 细线部分表示一些特殊情况, 了解即可, 不必深入研究.对于建立连接的过程客户端属于主动方, 服务端属于被动接受方(图的上半部分)

而对于关闭(图的下半部分), 服务端和客户端都可以先进行关闭.

处于ESTABLISHED状态的时候就可以收发数据了, 双方在通信过程当中一直处于ESTABLISHED状态, 数据传输期间没有状态的变化.

TIME_WAIT状态一定是出现在主动关闭的一方.

主动关闭的Socket端会进入TIME_WAIT状态,并且持续2MSL时间长度,MSL就是maximum segment lifetime(最大分节生命期),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间将在网络中消失。

使用netstat -anp可以查看连接状态

 注:数据传输的时候带了一个字节的数据, 所以server发送给client的ACK=x+2

为什么需要2MSL?

原因之一: 让四次挥手的过程更可靠, 确保最后一个发送给对方的ACK到达;

若对方没有收到ACK应答, 对方会再次发送FIN请求关闭, 此时在2MS时间内被动关闭方仍然可以发送ACK给对方.

TIME_WAIT一定是出现在主动关闭的一方, 也就是说2MS是针对主动关 闭一方来说的;由于TCP有可能存在丢包重传, 丢包重传若发给了已经断 开连接之后相同的socket-pair(该连接是新建的, 与原来的socket-pair完 全相同, 双方使用的是相同的IP和端口), 这样会对之后的连接造成困扰, 严重可能引起程序异常.

如何避免问题2呢??

--很多操作系统实现的时候, 只要端口被占用, 服务就不能启动.

测试: 启动服务端和客户端, 然后先关闭服务端, 再次启动服务端, 此时服务端报错: bind error: Address already in use; 若是先关闭的客户端, 再关闭的服务端, 此时启动服务端就不会报这个错误.

socket-pair的概念: 客户端与服务端连接其实是一个连接对, 可以通过使用netstat -anp | grep 端口号 进行查看.

2.端口复用

解决端口复用的问题: bind error: Address already in use, 发生这种情况是在服务端主动关闭连接以后, 接着立刻启动就会报这种错误.

setsockopt函数

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

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

setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(int));

函数说明可参看<<UNIX环境高级编程>>

由于错误是bind函数报出来的, 该函数调用要放在bind之前, socket之后调用.

3.半关闭状态

半关闭的概念:

如果一方close, 另一方没有close, 则认为是半关闭状态, 处于半关闭状态的 时候, 可以接收数据, 但是不能发送数据. 相当于把文件描述符的写缓冲区 操作关闭了.

注意: 半关闭一定是出现在主动关闭的一方.

shutdown函数

长连接和端连接的概念:

连接建立之后一直不关闭为长连接;

连接收发数据完毕之后就关闭为短连接;

shutdown和close的区别:

shutdown能够把文件描述符上的读或者写操作关闭, 而close关闭文件描述 符只是将连接的引用计数的值减1, 当减到0就真正关闭文件描述符了.

如: 调用dup函数或者dup2函数可以复制一个文件描述符, close其中一个并 不影响另一个文件描述符, 而shutdown就不同了, 一旦shutdown了其中一 个文件描述符, 对所有的文件描述符都有影响 .

4.心跳包

如何检查与对方的网络连接是否正常??

一般心跳包用于长连接.

方法1

keepAlive = 1;

setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));

由于不能实时的检测网络情况, 一般不用这种方法

方法2: 在应用程序中自己定义心跳包, 使用灵活, 能实时把控.

到此为止, 概念相关的东西就讲完毕了.

5.高并发服务器模型--select

继续研究高并发服务器的问题.

多路IO技术: select, 同时监听多个文件描述符, 将监控的操作交给内核去处理,

数据类型fd_set: 文件描述符集合--本质是位图(关于集合可联想一个信号集sigset_t)

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

函数介绍: 委托内核监控该文件描述符对应的读,写或者错误事件的发生.

参数说明:

nfds: 最大的文件描述符+1

readfds: 读集合, 是一个传入传出参数

传入: 指的是告诉内核哪些文件描述符需要监控

传出: 指的是内核告诉应用程序哪些文件描述符发生了变化

writefds: 写文件描述符集合(传入传出参数)

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

timeout:

NULL--表示永久阻塞, 直到有事件发生

0 --表示不阻塞, 立刻返回, 不管是否有监控的事件发生

>0--到指定事件或者有事件发生了就返回

返回值: 成功返回发生变化的文件描述符的个数

失败返回-1, 并设置errno值.

/usr/include/x86_64-linux-gnu/sys/select.h和

/usr/include/x86_64-linux-gnu/bits/select.h

从上面的文件中可以看出, 这几个宏本质上还是位操作.

void FD_CLR(int fd, fd_set *set);

将fd从set集合中清除.

int FD_ISSET(int fd, fd_set *set);

功能描述: 判断fd是否在集合中

返回值: 如果fd在set集合中, 返回1, 否则返回0.

void FD_SET(int fd, fd_set *set);

将fd设置到set集合中.

void FD_ZERO(fd_set *set);

初始化set集合.

调用select函数其实就是委托内核帮我们去检测哪些文件描述符有可读数据,可写,错误发生;

代码思路:

代码的具体实现: 编写代码并进行测试.

可以使用发生事件的总数进行控制, 减少循环次数

调用select函数涉及到了用户空间和内核空间的数值交互过程.

事件一共包括两部分, 一类是新连接事件, 一类是有数据可读的事件

问题分析: select函数的readfds是一个传出传入参数

测试和总结select用法

关于select的思考:

问题: 如果有效的文件描述符比较少, 会使循环的次数太多.

解决办法: 可以将有效的文件描述符放到一个数组当中, 这样遍历效率就高了.

select优点:

1 一个进程可以支持多个客户端

2 select支持跨平台

select缺点:

1 代码编写困难

2 会涉及到用户区到内核区的来回拷贝

3 当客户端多个连接, 但少数活跃的情况, select效率较低

例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下

4 最大支持1024个客户端连接

select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的, 而是由FD_SETSIZE=1024限制的.

FD_SETSIZE=1024  fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核, 一般不建议这么做.

作业:

编写代码, 让select监控标准输入, 监控网络, 如果标准输入有数据就写入网络, 如果网络有数据就读出网络数据, 然后打印到标准输出.

注意: select不仅可以监控socket文件描述符, 也可以监视标准输入.

POSIX表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准.

关于fd_set类型的底层定义:

/usr/include/x86_64-linux-gnu/sys/select.h和

/usr/include/x86_64-linux-gnu/bits/select.h

/usr/include/x86_64-linux-gnu/sys/select.h文件中:

 

__NFDBITS计算出来的值是: 8*8=64

 

上面是在头文件中一步一步跟踪的定义, 最简单的方法就是使用预处理将头文件和宏全部替换掉, 直接就可以看到最终的定义了.

如: gcc -E select.c -o select.i

打开select.i后

typedef struct

  {

    __fd_mask __fds_bits[1024 / (8 * (int) sizeof (__fd_mask))];

  } fd_set;

进一步转换后:

typedef struct

{

long int __fds_bits[1024/(8*8))];

//long int __fds_bits[16];

}

这个数组一共占用: 8 * 16 * 8 = 1024, 也就是说fd_set这个文件描述符表中一共有1024个bit位, 每个bit位只有0和1两种值, 1表示有, 0表示没有.

//IO多路复用技术select函数的使用 
#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>

int main()
{
	int i;
	int n;
	int lfd;
	int cfd;
	int ret;
	int nready;
	int maxfd;//最大的文件描述符
	char buf[FD_SETSIZE];
	socklen_t len;
	int maxi;  //有效的文件描述符最大值
	int connfd[FD_SETSIZE]; //有效的文件描述符数组
	fd_set tmpfds, rdfds; //要监控的文件描述符集
	struct sockaddr_in svraddr, cliaddr;

	//创建socket
	lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd<0)
	{
		perror("socket error");
		return -1;
	}

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

	//绑定bind
	svraddr.sin_family = AF_INET;
	svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	svraddr.sin_port = htons(8888);
	ret = bind(lfd, (struct sockaddr *)&svraddr, sizeof(struct sockaddr_in));
	if(ret<0)
	{
		perror("bind error");
		return -1;
	}

	//监听listen
	ret = listen(lfd, 5);
	if(ret<0)
	{
		perror("listen error");
		return -1;
	}

	//文件描述符集初始化
	FD_ZERO(&tmpfds);
	FD_ZERO(&rdfds);

	//将lfd加入到监控的读集合中
	FD_SET(lfd, &rdfds);

	//初始化有效的文件描述符集, 为-1表示可用, 该数组不保存lfd
	for(i=0; i<FD_SETSIZE; i++)
	{
		connfd[i] = -1;
	}

	maxfd = lfd;
	len = sizeof(struct sockaddr_in);

	//将监听文件描述符lfd加入到select监控中
	while(1)
	{
		//select为阻塞函数,若没有变化的文件描述符,就一直阻塞,若有事件发生则解除阻塞,函数返回
		//select的第二个参数tmpfds为输入输出参数,调用select完毕后这个集合中保留的是发生变化的文件描述符
		tmpfds = rdfds;
		nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
		if(nready>0)
		{
			//发生变化的文件描述符有两类, 一类是监听的, 一类是用于数据通信的
			//监听文件描述符有变化, 有新的连接到来, 则accept新的连接
			if(FD_ISSET(lfd, &tmpfds))	
			{
				cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);			
				if(cfd<0)
				{
					if(errno==ECONNABORTED || errno==EINTR)
					{
						continue;
					}
					break;
				}

				//先找位置, 然后将新的连接的文件描述符保存到connfd数组中
				for(i=0; i<FD_SETSIZE; i++)
				{
					if(connfd[i]==-1)
					{
						connfd[i] = cfd;
						break;
					}
				}
				//若连接总数达到了最大值,则关闭该连接
				if(i==FD_SETSIZE)
				{	
					close(cfd);
					printf("too many clients, i==[%d]\n", i);
					//exit(1);
					continue;
				}

				//确保connfd中maxi保存的是最后一个文件描述符的下标
				if(i>maxi)
				{
					maxi = i;
				}

				//打印客户端的IP和PORT
				char sIP[16];
				memset(sIP, 0x00, sizeof(sIP));
				printf("receive from client--->IP[%s],PORT:[%d]\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, sIP, sizeof(sIP)), htons(cliaddr.sin_port));

				//将新的文件 描述符加入到select监控的文件描述符集合中
				FD_SET(cfd, &rdfds);
				if(maxfd<cfd)
				{
					maxfd = cfd;
				}

				//若没有变化的文件描述符,则无需执行后续代码
				if(--nready<=0)
				{
					continue;
				}	
			}

			//下面是通信的文件描述符有变化的情况
			//只需循环connfd数组中有效的文件描述符即可, 这样可以减少循环的次数
			for(i=0; i<=maxi; i++)
			{
				int sockfd = connfd[i];
				//数组内的文件描述符如果被释放有可能变成-1
				if(sockfd==-1)
				{
					continue;
				}

				if(FD_ISSET(sockfd, &tmpfds))
				{
					memset(buf, 0x00, sizeof(buf));
					n = read(sockfd, buf, sizeof(buf));
					if(n<0)
					{
						perror("read over");
						close(sockfd);
						FD_CLR(sockfd, &rdfds);
						connfd[i] = -1; //将connfd[i]置为-1,表示该位置可用
					}
					else if(n==0)
					{
						printf("client is closed\n");	
						close(sockfd);
						FD_CLR(sockfd, &rdfds);
						connfd[i] = -1; //将connfd[i]置为-1,表示该位置可用
					}
					else
					{
						printf("[%d]:[%s]\n", n, buf);
						write(sockfd, buf, n);
					}

					if(--nready<=0)
					{
						break;  //注意这里是break,而不是continue, 应该是从最外层的while继续循环
					}
				}	
			}
		}	
	}

	//关闭监听文件描述符
	close(lfd);

	return 0;
}
//IO多路复用技术select函数的使用 
#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>

int main()
{
	int i;
	int n;
	int lfd;
	int cfd;
	int ret;
	int nready;
	int maxfd;//最大的文件描述符
	char buf[FD_SETSIZE];
	socklen_t len;
	int maxi;  //有效的文件描述符最大值
	int connfd[FD_SETSIZE]; //有效的文件描述符数组
	fd_set tmpfds, rdfds; //要监控的文件描述符集
	struct sockaddr_in svraddr, cliaddr;

	//创建socket
	lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd<0)
	{
		perror("socket error");
		return -1;
	}

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

	//绑定bind
	svraddr.sin_family = AF_INET;
	svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	svraddr.sin_port = htons(8888);
	ret = bind(lfd, (struct sockaddr *)&svraddr, sizeof(struct sockaddr_in));
	if(ret<0)
	{
		perror("bind error");
		return -1;
	}

	//监听listen
	ret = listen(lfd, 5);
	if(ret<0)
	{
		perror("listen error");
		return -1;
	}

	//文件描述符集初始化
	FD_ZERO(&tmpfds);
	FD_ZERO(&rdfds);

	//将lfd加入到监控的读集合中
	FD_SET(lfd, &rdfds);

	//初始化有效的文件描述符集, 为-1表示可用, 该数组不保存lfd
	for(i=0; i<FD_SETSIZE; i++)
	{
		connfd[i] = -1;
	}

	maxfd = lfd;
	len = sizeof(struct sockaddr_in);

	//将监听文件描述符lfd加入到select监控中
	while(1)
	{
		//select为阻塞函数,若没有变化的文件描述符,就一直阻塞,若有事件发生则解除阻塞,函数返回
		//select的第二个参数tmpfds为输入输出参数,调用select完毕后这个集合中保留的是发生变化的文件描述符
		tmpfds = rdfds;
		nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
		if(nready>0)
		{
			//发生变化的文件描述符有两类, 一类是监听的, 一类是用于数据通信的
			//监听文件描述符有变化, 有新的连接到来, 则accept新的连接
			if(FD_ISSET(lfd, &tmpfds))	
			{
				cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);			
				if(cfd<0)
				{
					if(errno==ECONNABORTED || errno==EINTR)
					{
						continue;
					}
					break;
				}

				//先找位置, 然后将新的连接的文件描述符保存到connfd数组中
				for(i=0; i<FD_SETSIZE; i++)
				{
					if(connfd[i]==-1)
					{
						connfd[i] = cfd;
						break;
					}
				}
				//若连接总数达到了最大值,则关闭该连接
				if(i==FD_SETSIZE)
				{	
					close(cfd);
					printf("too many clients, i==[%d]\n", i);
					//exit(1);
					continue;
				}

				//确保connfd中maxi保存的是最后一个文件描述符的下标
				if(i>maxi)
				{
					maxi = i;
				}

				//打印客户端的IP和PORT
				char sIP[16];
				memset(sIP, 0x00, sizeof(sIP));
				printf("receive from client--->IP[%s],PORT:[%d]\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, sIP, sizeof(sIP)), htons(cliaddr.sin_port));

				//将新的文件 描述符加入到select监控的文件描述符集合中
				FD_SET(cfd, &rdfds);
				if(maxfd<cfd)
				{
					maxfd = cfd;
				}

				//若没有变化的文件描述符,则无需执行后续代码
				if(--nready<=0)
				{
					continue;
				}	
			}

			//下面是通信的文件描述符有变化的情况
			//只需循环connfd数组中有效的文件描述符即可, 这样可以减少循环的次数
			for(i=0; i<=maxi; i++)
			{
				int sockfd = connfd[i];
				//数组内的文件描述符如果被释放有可能变成-1
				if(sockfd==-1)
				{
					continue;
				}

				if(FD_ISSET(sockfd, &tmpfds))
				{
					memset(buf, 0x00, sizeof(buf));
					n = read(sockfd, buf, sizeof(buf));
					if(n<0)
					{
						perror("read over");
						close(sockfd);
						FD_CLR(sockfd, &rdfds);
						connfd[i] = -1; //将connfd[i]置为-1,表示该位置可用
					}
					else if(n==0)
					{
						printf("client is closed\n");	
						close(sockfd);
						FD_CLR(sockfd, &rdfds);
						connfd[i] = -1; //将connfd[i]置为-1,表示该位置可用
					}
					else
					{
						printf("[%d]:[%s]\n", n, buf);
						write(sockfd, buf, n);
					}

					if(--nready<=0)
					{
						break;  //注意这里是break,而不是continue, 应该是从最外层的while继续循环
					}
				}	
			}
		}	
	}

	//关闭监听文件描述符
	close(lfd);

	return 0;
}

6.提纲总结

TCP状态转换图:
1 三次握手过程:
    客户端: SYN_SENT---connect()   
    服务端:      LISTEN--listen()   SYN_RCVD
    当三次握手完成后, 都处于ESTABLISHED状态   
2 数据传输过程中状态不发生变化, 都是ESTABLISHED状态
3 四次挥手过程:
    主动关闭方: FIN_WAIT_T  FIN_WAIT_2 TIME_WAIT
    被动关闭方: CLOSE_WAIT  LAST_ACK
    
思考题?
    1 SYN_SENT状态出现在哪一方? 客户端
    2 SYN_RCVD状态出现在哪一方? 服务端
    3 TIME_WAIT状态出现在哪一方?  主动关闭方
    4 在数据传输的时候没有状态变化.
    
TIME_WAIT是如何出现的:
    启动服务端, 启动客户端, 连接建好, 而且也可以正常发送数据;
    然后先关闭服务端, 服务端就会出现TIME_WAIT状态.

为什么需要2MSL时间:

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

shutdown和close的区别:
1 shutdown可以实现半关闭, close不行
2 shutdown关闭的时候, 不考虑文件描述符的引用计数, 是直接彻底关闭
  close考虑文件描述符的引用计数, 调用一次close只是将引用计数减1, 
  只有减小到0的时候才会真正关闭.

长连接和端连接的概念:
    长连接: 连接建立好之后,一直保持连接不关闭
    短连接: 连接使用完之后就立刻关闭.
    
什么是心跳包?
    用于监测长连接是否正常的字符串.
在什么情况下使用心跳包?
    主要用于监测长连接是否正常.
如何使用心跳包?
    通信双方需要协商规则(协议), 如4个字节长度+数据部分
    
使用select的开发服务端流程:
1 创建socket, 得到监听文件描述符lfd---socket()
2 设置端口复用-----setsockopt()
3 将lfd和IP  PORT绑定----bind()
4 设置监听---listen()
5 fd_set readfds;  //定义文件描述符集变量
  fd_set tmpfds;
  FD_ZERO(&readfds);  //清空文件描述符集变量
  FD_SET(lfd, &readfds);//将lfd加入到readfds集合中;
  maxfd = lfd;
  while(1)
  {
      tmpfds = readfds;
      nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
      if(nready<0)
      {
          if(errno==EINTR)//被信号中断
          {
              continue;
          }
          break;
      }
      
      //有客户端连接请求到来
      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++)
      {
          if(FD_ISSET(i, &tmpfds))
          {
            //read数据
              n = read(i, buf, sizeof(buf));
              if(n<=0)
              {
                  close(i);
                  //将文件描述符i从内核中去除
                  FD_CLR(i, &readfds);
              }
              
              //write应答数据给客户端
              write(i, buf, n);
          }
          
        if(--nready==0)
          {
              break;
          }
      }
      
      close(lfd);
      
      return 0;
  }
 
代码优化方向:
int client[1024]
for()
{
    client[i] = -1;
}

1 将通信文件描述符保存到一个整形数组中, 使用一个变量记录
  数组中最大元素的下标maxi.
2 如果数组中有无效的文件描述符, 直接跳过

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

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

相关文章

winform学习(3)-----Windows窗体应用和Windows窗体应用(.Net Framework)有啥区别?

1.模板选择 在学习winform的时候总是会对这两个应用不知道选择哪个&#xff1f;而且在学习的时候也没有具体的说明 首先说一下我是在添加控件的时候出现了以下问题 对于使用了Windows窗体应用这个模板的文件在工具箱中死活不见控件。 在转换使用了Windows窗体应用(.NET Fram…

JAVA基础原理篇_1.1—— 关于JVM 、JDK以及 JRE

目录 一、关于JVM 、JDK以及 JRE 1. JVM 2. JDK 3. JRE 二、为什么说 Java 语言“编译与解释并存”&#xff1f; 2.2 将高级编程语言按照程序的执行方式分为两种&#xff1a; 2.2 Java的执行过程&#xff1a; 2.3 所以为什么Java语言“编译与解释"共存&#xff1a…

Quartz中Misfire机制源码级解析

文章目录 前文案例展示Misfire机制1. 启动过程补偿2. 定时任务补偿3. 查询待触发列表时间区间补偿 前文 Misfire是啥意义的&#xff1f;使用翻译软件结果是"失火"。一个定时软件&#xff0c;还来失火&#xff1f;其实在Java里面&#xff0c;fire的含义更应该是触发&…

Ai创作系统ChatGPT源码搭建教程+附源码

系统使用Nestjs和Vue3框架技术&#xff0c;持续集成AI能力到本系统&#xff01; 更新内容&#xff1a; 同步官方图片重新生成指令 同步官方 Vary 指令 单张图片对比加强 Vary(Strong) | Vary(Subtle) 同步官方 Zoom 指令 单张图片无限缩放 Zoom out 2x | Zoom out 1.5x 新增GP…

Linux学习之信号

trap -l和kill -l都可以列出来信号所有值。 而trap "命令1;命令2;" 信号可以捕捉到信号之后再执行命令1、命令2等命令&#xff0c;这里的命令可以不止两条。 快捷键产生信号 echo "This is a test" > trapTest.txt将This is a test写入到trapTest.t…

分布式配置中心Nacos

文章目录 一、Nacos分布式配置中心1、使用分布式配置中心的优点2、Nacos配置中心和应用间的数据同步的三种模式3、Namespace命名空间4、DataID配置5、Group配置 二、Nacos分布式配置中心的使用1、将应用对接Nacos配置中心2、Nacos实现配置动态刷新RefreshScope3、Dubbo服务对接…

QT--day6(人脸识别、图像处理)

人脸识别&#xff1a; /***********************************************************************************头文件****************************************************************************************/#ifndef WIDGET_H #define WIDGET_H#include <QWidget>…

HCIP期中实验

考试需求 1 、该拓扑为公司网络&#xff0c;其中包括公司总部、公司分部以及公司骨干网&#xff0c;不包含运营商公网部分。 2 、设备名称均使用拓扑上名称改名&#xff0c;并且区分大小写。 3 、整张拓扑均使用私网地址进行配置。 4 、整张网络中&#xff0c;运行 OSPF 协议…

汽车销售企业消费税,增值税高怎么合理解决?

《税筹顾问》专注于园区招商、企业税务筹划&#xff0c;合理合规助力企业节税&#xff01; 汽车行业一直处于炙手可热的阶段&#xff0c;这是因为个人或者家庭用车的需求在不断攀升&#xff0c;同时随着新能源的技术进一步应用到汽车领域&#xff0c;一度实现了汽车销量的翻倍。…

JavaSE类和对象(2)(重点:封装、包、static静态变量和方法)

目录 一、封装 1.封装&#xff1a;从语法来看&#xff0c;就是被private修饰的成员变量或者成员方法。只能在当前类当中使用。 2.快捷键&#xff0c;自动生成set或者get方法 3.限定访问修饰符&#xff08;private、 protected、public&#xff09; public&#xff1a;可以理…

python数学建模_2:灰色预测模型(GM(1,1))使用文档

灰色预测模型(GM(1,1))使用文档简介使用时机处理数据类型函数说明使用示例注意事项具体项目 灰色预测模型(GM(1,1))使用文档 简介 灰色预测模型(GM(1,1))是灰色系统理论的重要部分&#xff0c;常用于对包含不确定性的系统进行建模和预测。 使用时机 当数据量较小&#xff0…

树莓派通过天线+gps获取经纬度并调用高德地图api在地图上标点

完整项目为《基于机器视觉的行人和路面缺陷检测及其边缘设备部署》 完整功能视频演示地址&#xff1a;本科最后的课设&#xff1a;“车载系统的辅助系统——基于机器视觉的行人和路面缺陷检测”完结撒花*罒▽罒*_哔哩哔哩_bilibili 该博客介绍的功能为&#xff1a; 1&#xff1…

VS下开发Qt应用环境搭建

VS下开发Qt应用环境搭建 版本说明环境搭建步骤QT新增组件重新安装QTVS中的配置 版本说明 vs2019 QT5.14 我之前是按照QT基础组件的安装&#xff0c;但是这个安装只是最基础的组件&#xff0c;如果想要在VS中使用QT&#xff0c;还得安装其他组件&#xff0c;下面的安装流程、 …

轻松解决宝塔面板设置了授权IP访问,但是IP变动导致访问不了面板

为了宝塔面板的安全与隐蔽性&#xff0c;我们很多站长会设置授权IP&#xff0c;授权IP的作用是&#xff1a;设置访问授权IP,多个请使用逗号(,)隔开;注意&#xff1a;一旦设置授权IP,只有指定IP的电脑能访问面板! 但是很多站长不是通过专用的虚拟通道访问&#xff0c;用的都是宽…

【Python系列】Python基础语法轻松入门—从变量到循环

目录 写在前面 语法介绍 变量 数据类型 整数 浮点数 字符串 列表 元组 字典 运算符 算术运算符 比较运算符 逻辑运算符 条件语句 循环语句 图书推荐 图书介绍 参与方式 中奖名单 写在前面 Python 是一种高级、解释型的编程语言&#xff0c;具有简单易学…

恒心工程PMO运作实践︱恒丰银行苏州分行行长(拟)姜兆华

恒丰银行股份有限公司苏州分行党委书记、行长&#xff08;拟&#xff09;&#xff0c;原总行金融科技部副总经理、恒心工程PMO办公室主任姜兆华先生受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾&#xff0c;演讲议题&#xff1a;恒心工程PMO运作实践——以商业银行核…

京东的成功秘诀:找到自己独特而有效的商业模式

你知道京东为什么能够从一个卖电器的小网站发展成为中国最大的电商平台吗&#xff1f;如果京东一开始靠卖电器赚钱&#xff0c;不可能有今天&#xff0c;在十几年刘强东刚创业的时候&#xff0c;如果京东靠卖电器赚钱&#xff0c;你知道想当年的国美黄光裕和想当年的苏宁的张近…

磁盘均衡器:HDFS Disk Balancer

HDFS Disk Balancer 背景产生的问题以及解决方法 hdfs disk balancer简介HDFS Disk Balancer功能数据传播报告 HDFS Disk Balancer开启相关命令 背景 相比较于个人PC&#xff0c;服务器一般可以通过挂载多块磁盘来扩大单机的存储能力在Hadoop HDFS中&#xff0c;DataNode负责最…

F5 LTM 知识点和实验 7-使用SNATs处理流量

第七章:使用SNATs处理流量 SNATs: 传统的vs都是对目的地址和端口进行改变,而源地址没有改变,如果你需要对源地址和源端口进行更改,则需要使用SNAT能力,好处在于: 1、允许不可路由地址(网络内部)的设备获得可路由地址以进入网络外部。2、确保目标服务器通过BIG-IP系统返…

[Docker实现测试部署CI/CD----相关服务器的安装配置(1)]

目录 0、CI/CD系统最终架构图规划IP地址 1、git配置Git下载pycharm配置gitidea配置git 2、GitLab安装与配置主机要求拉取镜像定义 compose.yml启动gitlab浏览器访问并修改密码查看登录密码修改密码 3、SonarQube 安装与配置拉取镜像修改虚拟内存的大小启动SonarQube登录 SonarQ…