【Linux】多路IO复用技术①——select详解如何使用select在本地主机实现简易的一对多服务器(附图解与代码实现)

news/2024/5/4 22:27:22/文章来源:https://blog.csdn.net/m0_53133879/article/details/134103317

这一篇的篇幅可能有点长,但真心希望大家能够静下心来看完,相信一定会有不小的收获。那么话不多说,我们这就开始啦!!!

目录

一对一服务器中的BUG

如何实现简易的一对多服务器

实现简易一对多服务器的大体步骤

每个步骤的具体流程

1.网络初始化

2.启动监听,等待socket相关事件

多路IO复用技术之select模型

3.监听到相关事件 , 辨别是服务器socket还是客户端socket并进行处理

本地主机实现简易一对多服务器的项目实现

项目构成

结果演示


一对一服务器中的BUG

我们先来看一段我之前写的程序——功能:实现一对一的单进程服务器

大家可以直接看该程序的while循环,来看一下该部分有哪些BUG

/*************************************************************************> File Name: nan_server.c> Author: Nan> Mail: **@qq.com> Created Time: 2023年10月20日 星期五 13时59分10秒************************************************************************/#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>//定义一个开关,用于决定服务器是否开启,默认为开启状态
#define SERVER_SWITCH 1int main()
{//1.分别定义服务端与客户端的网络信息结构体struct sockaddr_in server_addr , client_addr;bzero(&server_addr , sizeof(server_addr));bzero(&client_addr , sizeof(client_addr));//定义一个读写缓冲区与一个存放客户端IP的缓冲区char rw_buffer[1500];char client_IP[16];bzero(rw_buffer , sizeof(rw_buffer));bzero(client_IP , sizeof(client_IP));//2.对服务端网络信息结构体进行初始化server_addr.sin_family = AF_INET;server_addr.sin_port = htons(6060);//server_addr.sin_addr.s_addr = inet_addr("192.0.0.1");server_addr.sin_addr.s_addr = inet_addr("本地主机IPV4地址");//3.创建套接字,该套接字起到监听与传输信息的作用int server_sockfd = socket(AF_INET , SOCK_STREAM , 0);if(server_sockfd == -1){perror("server socket call failed!\n");exit(-1);}//4.将IP地址与端口号绑定到监听套接字上int bind_result = bind(server_sockfd , (struct sockaddr*)&server_addr , sizeof(server_addr));if(bind_result == -1){perror("server bind call failed!\n");exit(-1);}printf("server wait connect!\n");//日志打印,可帮助理解程序执行逻辑//5.监听是否有TCP链接int backlog = 128;listen(server_sockfd , backlog);socklen_t addrlen;int client_sockfd;while(SERVER_SWITCH){printf("已进入循环!\n");//日志打印,可帮助理解程序执行逻辑addrlen = sizeof(client_addr);//6.如果接收成功,返回对应的文件描述符,并执行以下程序if( (client_sockfd = accept(server_sockfd , (struct sockaddr*)&client_addr , &addrlen)) > 0){   printf("accept call success!\n");//将网络信息结构体中的大端序IP转为字符串IP并放到读写缓冲区中inet_ntop(AF_INET , &(client_addr.sin_addr.s_addr) , client_IP , sizeof(client_IP));printf("client_IP = %s\n" , client_IP);//日志打印,帮助检测是否写入IP地址sprintf(rw_buffer , "Hello , %s , welcome connect nan_server\n" , client_IP);printf("读写缓冲区中内容为 %s\n" , rw_buffer);//日志打印,帮助检测是否写入要发送的数据//将读写缓冲区中的内容发送到服务端的套接字中,由套接字向客户端发送数据send(client_sockfd , rw_buffer , sizeof(rw_buffer) , MSG_NOSIGNAL);//清空读写缓冲区与存放IP的缓冲区,以供下一次使用bzero(rw_buffer , sizeof(rw_buffer));                                      bzero(client_IP , sizeof(client_IP));//读取客户端发来的数据recv(client_sockfd , rw_buffer , sizeof(rw_buffer) , 0);printf("client_message : %s\n" , rw_buffer);bzero(rw_buffer , sizeof(rw_buffer));}else if(client_sockfd == -1){perror("accept call failed!\n");continue;}}close(server_sockfd);
}

如果大家看不出来的话,再给大家一点提示:这些BUG都和阻塞与socket缓冲区有关

现在来公布一下答案吧,这个服务器的BUG在于——

  1. 如果一直没有客户端向服务器发起TCP链接请求,socket缓冲区中没有表示链接请求的标志数据SYN可读取,服务器的程序就会一直阻塞在accept函数那里,无法执行其他程序,整个服务器一直处于阻塞等待状态
  2. recv函数的调用,由于服务器使用阻塞状态的recv函数,如果客户端迟迟不发送信息,socket缓冲区就会一直为空,整个服务器就会一直阻塞等待该客户端发送数据,无法处理其他客户端的链接请求

那么我们要怎么处理这些BUG呢?

其实看完上面的BUG,大家或多或少都能明白这两个BUG的本质——其实无非就是,由于socket缓冲区中没有数据,可accept函数和recv函数却一直在等待数据的读取,导致服务器一直阻塞等待数据的读取

要想解决这两个BUG,其实也很简单——我们需要一个类似信号的功能,当socket缓冲区中有数据,触发相关事件,需要服务器进行处理时,我们再去进行处理,socket缓冲区中没有数据时,服务器不要一直阻塞等待数据

PS:socket的相关事件共分三种——1.读事件、2.写事件、3.异常事件

如何实现简易的一对多服务器

实现简易一对多服务器的大体步骤

  1. 网络初始化
  2. 启动监听,等待socket相关事件(也就是查看socket缓冲区中是否有数据需要处理)
  3. 监听到相关事件辨别是服务器socket还是客户端socket并进行处理

PS:处理socket相关事件这里要分两类——

  1. 服务器套接字接收到客户端的TCP链接请求,调用accept函数
  2. 服务器中为该客户端创建的套接字接收到客户端发来的信息,调用recv函数

每个步骤的具体流程

1.网络初始化

网络初始化这个步骤就不多做介绍了,就是简单的初始化网络信息结构体、创建服务器套接字、绑定套接字等操作

不会的同学可以去看一下我之前写的这篇博客,相关函数与使用方法都在里面:【Linux】如何在本地主机实现简易的一对一服务器(附图解与代码实现)icon-default.png?t=N7T8http://t.csdnimg.cn/thQS8

2.启动监听,等待socket相关事件

既然是要实现一对多的服务器,就代表着我们要为每个链接的客户端分别创建对应的套接字,同时这也就意味着我们自然也要去监听这些套接字

多路IO复用技术这么名字听起来很高大上,其实本质上就是一个IO事件监听技术,也就是一次性可以监听多个socket,来判断这些socket中是否有数据需要处理并反馈给服务器。所以在这个过程中我们也就要用到该技术中的一种——select

接下来我们来讲解一下select的原理实现与相关函数

多路IO复用技术之select模型

原理实现

select中有一个集合,叫做监听集合,我们可以将需要监听的套接字放入套接字文件描述符表中,由该集合负责帮我们监听该文件描述符表中这些套接字文件描述符对应的这些套接字的缓冲区中是否有数据需要处理

这个监听集合的大小为1024(固定大小,不可改),但需要注意的是,虽然这个集合的大小为1024,但实际能帮我们监听的客户端套接字只有1020个,因为前1-3个分别用于监听标准输入、标准输出和标准出错,第四个用于存放服务器套接字

可能只用文字描述过于抽象了,大家可以看下面的这个图来帮助理解

通过这个监听集合,我们就可以实现对多个socket的同时监听

这时候可能就有同学好奇了,监听?他咋监听啊?是什么高级手段吗?

其实这个监听真的是一种很朴实无华的方法,就是遍历,一次次的遍历,当监听集合完成一遍遍历,发现有套接字处于就绪状态,也就是某些套接字的缓冲区中有数据需要处理时,他就会传出一个就绪码(处于就绪状态的套接字数量)和一个就绪集合(就绪的套接字的位码置1,未就绪的套接字的位码置0)

还是画个图来帮助大家理解

以上就是select模型的相关原理了,接下来我们来讲一讲相关函数

以下函数的头文件都是#include<sys/select.h>

介绍一下一会会用到的变量:

  • fd_set set ; //创建监听集合
  • int sockfd ; //套接字文件描述符
  • int max_fd ; //套接字文件描述符表中的描述符个数
  • struct timeval *timeout ; //时间结构体,在这里表示工作模式——1.阻塞、2.非阻塞、3.定时阻塞(非阻塞与定时阻塞需要设置该结构体)

PS : timeout中有两个成员,一个表示秒(timeout.tv_sec),一个表示微秒(timeout.tv_usec)

struct timeval
{
__time_t  tv_sec;        /* Seconds. */
__suseconds_t  tv_usec;  /* Microseconds. */
};
  • timeout = NULL 就表示阻塞监听
  • timeout.tv_sec = 0 、timeout.tv_sec = 0 就表示非阻塞监听
  • timeout.tv_sec = 4、timeout.tv_usec = 30 就表示阻塞4秒30微秒,之后不阻塞
函数功能返回值
FD_ZERO(&set);初始化监听集合,将所有位的位码都初始化为0因为它们只是对文件描述符集合进行操作,而不是返回任何值,所以他们的返回值都是void
FD_SET(sockfd , &set);将set集合中与sockfd对应位的位码设置为1因为它们只是对文件描述符集合进行操作,而不是返回任何值,所以他们的返回值都是void
FD_CLR(sockfd , &set);将set集合中与sockfd对应位的位码设置为0因为它们只是对文件描述符集合进行操作,而不是返回任何值,所以他们的返回值都是void
FD_ISSET(sockfd , &set);获取set集合中与sockfd对应位的位码0或1
int select(max_fd, 是否监听读事件 , 是否监听写事件 , 是否监听错误事件 , timeout);监听我们要求的文件描述符的状态变化情况,并通过返回值告知(PS:想监听对应时间就传入&set,不想就传NULL)返回处于就绪状态的套接字数量

3.监听到相关事件 , 辨别是服务器socket还是客户端socket并进行处理

上面的就绪集合还需要我们自己去遍历,从而找到哪些套接字需要进行数据处理,并辨别是服务器套接字还是客户端套接字

如果是服务器套接字,就说明是有客户端向服务器发送了TCP链接请求,有以下步骤需要执行:

  1. 调用accept函数进行链接并获取与该客户端对应的套接字文件描述符
  2. 将其放入套接字文件描述符存放数组(由于该服务器为单进程,所以我们需要建立一个数组来存放这些客户端套接字文件描述符)(这个地方不太懂的话别着急,结合代码来看一定会让你豁然开朗)
  3. 将该套接字文件描述符放入套接字文件描述符表中,来让监听集合对该套接字进行监听

如果是客户端套接字,就说明是客户端向服务器发送了数据,有以下步骤需要执行:

  1. 调用recv函数读取套接字缓冲区中的数据
  2. 根据客户端发来的数据进行相应处理

具体过程如下图所示:

本地主机实现简易一对多服务器的项目实现

项目构成

该项目由几个程序共同组成,分别是以下几个:

  • func_2th_parcel.h:定义二次包裹的函数名
  • func_2th_parcel.c:对网络初始化相关的函数进行二次包裹
  • select_server.c:使用select模型的服务器程序
  • client.c:客户端程序
/*************************************************************************> File Name: func_2th_parcel.h> Author: Nan> Mail: **@qq.com> Created Time: 2023年10月18日 星期三 18时32分22秒************************************************************************/#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/mman.h>
#include <time.h>
#include <ctype.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <poll.h>
#include <sys/epoll.h>//socket函数的二次包裹
int SOCKET(int domain , int type , int protocol);//bind函数的二次包裹
int BIND(int sockfd , const struct sockaddr* addr , socklen_t  addrlen);//listen函数的二次包裹
int LISTEN(int sockfd , int backlog);//send函数的二次包裹
ssize_t SEND(int sockfd , const void* buf , size_t len , int flags);//recv函数的二次包裹
ssize_t RECV(int sockfd , void* buf , size_t len , int flags);//connect函数的二次包裹
int CONNECT(int sockfd , const struct sockaddr* addr , socklen_t addrlen);//accept函数的二次包裹
int ACCEPT(int sockfd , struct sockaddr* addr , socklen_t addrlen);//网络初始化函数
int SOCKET_NET_CREATE(const char* ip , int port);//服务端与客户端建立连接并返回客户端套接字文件描述符
int SERVER_ACCEPTING(int server_fd);
/*************************************************************************> File Name: func_2th_parcel.c> Author: Nan> Mail: **@qq.com> Created Time: 2023年10月18日 星期三 18时32分42秒************************************************************************/#include <func_2th_parcel.h>int SOCKET(int domain , int type , int protocol){int return_value;if((return_value = socket(domain , type , protocol)) == -1){perror("socket call failed!\n");return return_value;}return return_value;
}int BIND(int sockfd , const struct sockaddr* addr , socklen_t addrlen){int return_value;   if((return_value = bind(sockfd , addr , addrlen)) == -1){perror("bind call failed!\n");return return_value;}                      return return_value;   
}int LISTEN(int sockfd , int backlog){int return_value;   if((return_value = listen(sockfd , backlog)) == -1){perror("listen call failed!\n");return return_value;}                      return return_value;   
}ssize_t SEND(int sockfd , const void* buf , size_t len , int flags){ssize_t return_value;if((return_value = send(sockfd , buf , len , flags)) == -1){perror("send call failed!\n");return return_value;}return return_value;
}ssize_t RECV(int sockfd , void* buf , size_t len , int flags){ssize_t return_value;   if((return_value = recv(sockfd , buf , len , flags)) == -1){perror("recv call failed!\n");return return_value;}                      return return_value;   
}int CONNECT(int sockfd , const struct sockaddr* addr , socklen_t addrlen){int return_value;   if((return_value = connect(sockfd , addr , addrlen)) == -1){perror("connect call failed!\n");return return_value;}                      return return_value;   
}int ACCEPT(int sockfd , struct sockaddr* addr , socklen_t addrlen){int return_value;   if((return_value = accept(sockfd , addr , &addrlen)) == -1){perror("accept call failed!\n");return return_value;}                      return return_value;   
}int SOCKET_NET_CREATE(const char* ip , int port){int sockfd;struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);inet_pton(AF_INET , ip , &addr.sin_addr.s_addr);sockfd = SOCKET(AF_INET , SOCK_STREAM , 0);BIND(sockfd , (struct sockaddr*)&addr , sizeof(addr));LISTEN(sockfd , 128);return sockfd;
}int SERVER_ACCEPTING(int server_fd)
{int client_sockfd;struct sockaddr_in client_addr;char client_ip[16];char buffer[1500];bzero(buffer , sizeof(buffer));bzero(&client_addr , sizeof(client_addr));socklen_t addrlen = sizeof(client_addr);client_sockfd = ACCEPT(server_fd , (struct sockaddr*)&client_addr , addrlen);bzero(client_ip , 16);//将客户端的IP地址转成CPU可以识别的序列并存储到client_ip数组中inet_ntop(AF_INET , &client_addr.sin_addr.s_addr , client_ip , 16);sprintf(buffer , "Hi , %s welcome tcp test server service..\n" , client_ip);printf("client %s , %d , connection success , client sockfd is %d\n" , client_ip , ntohs(client_addr.sin_port) , client_sockfd);SEND(client_sockfd , buffer , strlen(buffer) , MSG_NOSIGNAL);return client_sockfd;
}
/*************************************************************************> File Name: select_server.c> Author: Nan> Mail: **@qq.com> Created Time: 2023年10月25日 星期三 18时53分30秒************************************************************************/#include <func_2th_parcel.h>int main(void)
{//一、进行网络初始化int server_sockfd;//服务器套接字文件描述符int max_fd;//套接字文件描述符表中的描述符个数int client_sockfd_array[1020];//存放客户端套接字文件描述符的数组  int client_sockfd;//客户端套接字文件描述符int ready_num = 0;//获取处于就绪状态的套接字数目char rw_buffer[1500];//读写缓冲区int flag;int recv_len = 0;//客户端发来的数据长度memset(client_sockfd_array , -1 , sizeof(client_sockfd_array));//将套接字数组每一位都置为-1,方便后面查找就绪套接字bzero(rw_buffer , sizeof(rw_buffer));fd_set listen_set , ready_set;//监听集合,就绪集合server_sockfd = SOCKET_NET_CREATE("192.168.79.128" , 6060);//初始化服务器套接字max_fd = server_sockfd;//初始化最大套接字数目//初始化监听集合,将server_sockfd设置为监听套接字FD_ZERO(&listen_set);FD_SET(server_sockfd , &listen_set);printf("select_server wait TCP connect\n");//二、启动监听,等待socket相关事件while(1){ready_set = listen_set;//阻塞等待socket相关事件if((ready_num = select(max_fd + 1 , &ready_set , NULL , NULL , NULL)) == -1){perror("select call failed\n");exit(0);}//三、监听到相关事件 , 辨别是服务器socket还是客户端socket并进行处理while(ready_num){//辨别就绪,如果是服务端套接字就绪if(FD_ISSET(server_sockfd , &ready_set)){client_sockfd = SERVER_ACCEPTING(server_sockfd);//与客户端建立TCP链接FD_SET(client_sockfd , &listen_set);//将该套接字放入监听集合中//如果max_fd小于客户端套接字返回的描述符,说明这个新的客户端套接字放到了最后一位,max_fd需要加1if(max_fd < client_sockfd){max_fd = max_fd + 1;}for(int i = 0 ; i < 1020 ; i++){//将该客户端套接字,放到数组中有空缺的地方if(client_sockfd_array[i] == -1){client_sockfd_array[i] = client_sockfd;break;}}//将就绪集合中服务器套接字这一位的位码置为0,因为如果ready_num > 1,不做该处理服务器会一直认为是客户端发送了TCP链接请求,从而导致错误处理FD_CLR(server_sockfd , &ready_set);}//如果是客户端套接字就绪else{for(int i = 0 ; i < 1020 ; i++){//检测存放的客户端套接字是否处于就绪状态if(client_sockfd_array[i] != -1){//如果该套接字处于就绪状态if(FD_ISSET(client_sockfd_array[i] , &ready_set)){recv_len = RECV(client_sockfd_array[i] , rw_buffer , sizeof(rw_buffer) , 0);//获取数据长度printf("客户端%d 发来数据 : %s , 现在进行处理\n" , client_sockfd_array[i] , rw_buffer);flag = 0;}//如果recv_len = 0,就说明与客户端套接字对应的客户端退出了,将对应客户端套接字移出套接字存储数组与监听集合if(recv_len == 0){printf("客户端%d 已下线\n" , client_sockfd_array[i]); FD_CLR(client_sockfd_array[i] , &ready_set);client_sockfd_array[i] = -1;break;}//进行业务处理:小写字母转大写字母while(recv_len > flag){rw_buffer[flag] = toupper(rw_buffer[flag]);flag++;}SEND(client_sockfd_array[i] , rw_buffer , recv_len , MSG_NOSIGNAL);printf("已向客户端%d 发送处理后的数据 : %s\n" , client_sockfd_array[i] , rw_buffer);bzero(rw_buffer , sizeof(rw_buffer));recv_len = 0;FD_CLR(client_sockfd_array[i] , &ready_set);break;}}}ready_num--;}}close(server_sockfd);printf("server shutdown\n");return 0;  
}
/*************************************************************************> File Name: client.c> Author: Nan> Mail: **@qq.com> Created Time: 2023年10月19日 星期四 18时29分12秒************************************************************************/#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <time.h>//服务器实现大小写转换业务int main()
{//1.定义网络信息结构体与读写缓冲区并初始化struct sockaddr_in dest_addr;char buffer[1500];bzero(&dest_addr , sizeof(dest_addr));bzero(buffer , sizeof(buffer));dest_addr.sin_family = AF_INET;dest_addr.sin_port = htons(6060);//字符串ip转大端序列inet_pton(AF_INET , "192.168.79.128" , &dest_addr.sin_addr.s_addr);int sockfd = socket(AF_INET , SOCK_STREAM , 0);int i;//2.判断连接是否成功if((connect(sockfd , (struct sockaddr*) &dest_addr , sizeof(dest_addr))) == -1){perror("connect failed!\n");exit(0);}recv(sockfd , buffer , sizeof(buffer) , 0);printf("%s" , buffer);bzero(buffer , sizeof(buffer));//3.循环读取终端输入的数据while( (fgets(buffer , sizeof(buffer) , stdin) ) != NULL){i = strlen(buffer);buffer[i-1] = '\0';//向服务端发送消息send(sockfd , buffer , strlen(buffer) , MSG_NOSIGNAL);//接收服务端发来的消息recv(sockfd , buffer , sizeof(buffer) , 0);//打印服务端发来的信息printf("response : %s\n" , buffer);//清空读写缓冲区,以便下一次放入数据bzero(buffer , sizeof(buffer));}//4.关闭套接字,断开连接close(sockfd);return 0;
}

结果演示

以上就是本篇博客的全部内容了,大家有什么地方没有看懂的话,可以在评论区留言给我,咱要力所能及的话就帮大家解答解答

今天的学习记录到此结束啦,咱们下篇文章见,ByeBye!

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软考下午第一题 案列分析

期待分值 10&#xff0c;前三问12左右分&#xff0c;最后一题2、3分左右&#xff0c;重点在于拿下前面三题。 小心谨慎&#xff0c;不要大意。 数据流图 外部系统 数据存储 加工&#xff08;&#xff09;process 数据流 第二小题 说明给出存储名称&#xff0c;就使用该名称&…

最短路径:迪杰斯特拉算法

简介 英文名Dijkstra 作用&#xff1a;找到路中指定起点到指定终点的带权最短路径 核心步骤 1&#xff09;确定起点&#xff0c;终点 2&#xff09;从未走过的点中选取从起点到权值最小点作为中心点 3&#xff09;如果满足 起点到中心点权值 中心点到指定其他点的权值 < 起…

STM32单片机智能小车一PWM方式实现小车调速和转向

目录 1. 电机模块开发 2. 让小车动起来 3. 串口控制小车方向 4. 如何进行小车PWM调速 5. PWM方式实现小车转向 1. 电机模块开发 L9110s概述 接通VCC&#xff0c;GND 模块电源指示灯亮&#xff0c; 以下资料来源官方&#xff0c;具体根据实际调试 IA1输入高电平&#xff…

BUUCTF qr 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 这是一个二维码&#xff0c;谁用谁知道&#xff01; 密文&#xff1a; 下载附件&#xff0c;得到一张二维码图片。 解题思路&#xff1a; 1、这是一道签到题&#xff0c;扫描二维码得到flag。 flag&#xff1a;…

外汇天眼:在2023年Expo上探索金融科技的未来!

从2020年初至2022年年底&#xff0c;全球范围内爆发的新冠疫情蔓延&#xff0c;对各国经济造成了严重冲击&#xff0c;导致贸易活动几近停滞&#xff0c;国际人员流动受限&#xff0c;产业链陷入危机。为应对这一局面&#xff0c;美欧经济体采取了前所未有的扩张性财政和货币政…

高效分割分段视频:提升您的视频剪辑能力

在数字媒体时代&#xff0c;视频剪辑已经成为一项重要的技能。无论是制作个人影片、广告还是其他类型的视频内容&#xff0c;掌握高效的视频剪辑技巧都是必不可少的。本文将介绍如何引用云炫AI智剪高效地分割和分段视频&#xff0c;以提升您的视频剪辑能力。以下是详细的操作步…

设计模式—创建型模式之原型模式

设计模式—创建型模式之原型模式 原型模式&#xff08;Prototype Pattern&#xff09;用于创建重复的对象&#xff0c;同时又能保证性能。 本体给外部提供一个克隆体进行使用。 比如我们做一个SjdwzMybatis&#xff0c;用来操作数据库&#xff0c;从数据库里面查出很多记录&…

半导体产线应用Power Link 转EtherCAT协议网关数字化转型

随着数字化转型的推进&#xff0c;越来越多的企业开始意识到数字化转型的重要性&#xff0c;并将其作为发展战略的关键之一。半导体产线作为一个高度自动化的生产系统&#xff0c;自然也需要数字化转型来提高效率、降低成本和提高质量。Power Link 转EtherCAT协议网关是半导体产…

RISC-V IDE MRS无感远程协助模块详解

RISC-V IDE MRS无感远程协助模块详解 一、说明 1.1 概述 针对RISC-V/ARM等内核MCU的嵌入式集成开发环境MRS(MounRiver Studio)从V1.90版本开始内置无感远程协助模块&#xff08;Sensorless Remote Assistant Module&#xff0c;以下简称SRA模块&#xff09;。SRA模块是一款支…

MAC缓解WebUI提示词反推

当前环境信息&#xff1a; 在mac上安装好stable diffusion后&#xff0c;能做图片生成了之后&#xff0c;遇到一些图片需要做提示词反推&#xff0c;这个时候需要下载一个插件&#xff0c;参考&#xff1a; https://gitcode.net/ranting8323/stable-diffusion-webui-wd14-tagg…

0基础学习VR全景平台篇第114篇:全景图优化和输出 - PTGui Pro教程

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 前情回顾&#xff1a;之前&#xff0c;我们详细介绍了如何用编辑器、控制点、垂直线等功能优化错位和矫正水平&#xff0c;然而这些调整不会马上生效。 我们需要在【优化】选项卡…

python爬虫selenium和ddddocr使用

python爬虫selenium和ddddocr使用 selenium使用 selenium实际上是web自动化测试工具&#xff0c;能够通过代码完全模拟人使用浏览器自动访问目标站点并操作来进行web测试。 通过pythonselenium结合来实现爬虫十分巧妙。 由于是模拟人的点击来操作&#xff0c;所以实际上被反…

UE4 体积云制作 学习笔记

首先Noise本来就是一张噪点图 云的扰动不能太大&#xff0c;将Scale调小&#xff0c;并将InputMin调整为0 形成这样一张扰动图 扰动需要根据材质在世界的位置进行调整&#xff0c;所以Position需要加上WorldPosition 材质在不同世界位置&#xff0c;噪点不同 除以一个数&#…

【Jenkins】新建任务FAQ

问题1. 源码管理处填入Repository URL&#xff0c;报错&#xff1a;无法连接仓库&#xff1a;Error performing git command: ls-remote -h https://github.com/txy2023/GolangLearning.git HEAD 原因&#xff1a; jenkins全局工具配置里默认没有添加git的路径&#xff0c;如果…

【Redis】认识Redis-特点特性应用场景对比MySQL重要文件及作用

文章目录 认识redisredis的主要特点redis的特性&#xff08;优点&#xff09;redis是单线程模型&#xff0c;为什么效率这么高&#xff0c;访问速度这么快redis应用场景redis不可以做什么MySQL和Redis对比启动RedisRedis客户端Redis重要文件及作用 认识redis redis里面相关的小…

SCNet:自校正卷积网络(附代码)

论文地址&#xff1a;https://mftp.mmcheng.net/Papers/20cvprSCNet.pdf 代码地址&#xff1a;https://github.com/MCG-NKU/SCNet 1.是什么&#xff1f; SCNet是一种卷积神经网络&#xff0c;它使用自校准卷积&#xff08;Self-Calibrated Convolutions&#xff09;来增强子…

web:[网鼎杯 2020 青龙组]AreUSerialz

题目 点进题目发现 需要进行代码审计 function __destruct() {if($this->op "2")$this->op "1";$this->content "";$this->process();}这里有__destruct()函数&#xff0c;在对象销毁时自动调用&#xff0c;根据$op属性的值进行…

一个基于Excel模板快速生成Excel文档的小工具

介绍 DocumentGenerator是一个Excel快速生成工具&#xff0c;目标以后还能实现Word、pdf等的文件的生成。该程序独立运行&#xff0c;可通过HTTP接口调用其生成接口。 典型使用场景为如下&#xff1a; 使用者编写模板文件使用者准备模板文件的填充JSON数据内容使用者通过网络…

【LVS实战】02 搭建一个LVS-NAT实验

一、网络结构 用虚拟机搭建如下的几台机器&#xff0c;并配置如下的ip 关于虚拟机网卡和网络的配置&#xff0c;可以参考 iptables章节&#xff0c;05节&#xff1a;网络转发实验 主机A模拟外网的机器 B为负载均衡的机器 C和D为 RealServer 二、C和D主机的网关设置 C和D机…

Qt 重写QSlider简单实现滑动解锁控件(指定百分比回弹效果)

组件效果图: 应用场景: 用于滑动解锁相关场景,Qt的控件鼠标监听机制对于嵌入式设备GUI可触摸屏依旧可用。 实现方式: 主要是通过继承QSlider以及搭配使用QStyleOptionSlider来实现效果。 注意细则: QStyleOptionSlider是用于定制空白区域是否可移动滑块,根据需求可…