Linux TCP多线程服务器

news2024/9/20 20:41:00

一、多线程开发

线程和进程

程序写好存储在硬盘介质里,CPU读取程序到内存 ,这个在内存中的可执行程序实例就叫进程。一个程序如果多次读取到内存中,那他们就是各自独立的进程
内存中的任何位置都有相应的地址方便访问,而在内存中的进程,自己内部都有一个虚拟独立的地址空间。
每个进程首先有加载的程序,通常只有一个程序计数器,用来记录当前程序执行的位置,会按照程序顺序计算,这里的一个执行流就是一个线程。如果有多个线程,就需要多个程序计数器,每个线程会独立运行。除此之外,每个进程还有寄存器、
堆栈等程序运行时的状态信息,同时线程间共享的则有地址空间、全局变量、打开的文件等等信息。

那么为什么进程中还要有更小的”进程“ - 线程

假如进程是一个文档编辑器,存放着相应的程序和文档,现在用户用键盘敲下回车,交互的程序接收键盘的按下事件,布局的程序将文字重新排布渲染出来,另外每隔一段时间,写入的程序保存文档到硬盘中,所以这三个程序最好能并行执行,但他们又需要访问修改同一个文档,所以肯定是在同一个进程中,所以现在需要更轻量级的3个线程,交互线程, 渲染线程,保存线程。
因此,线程是并行的最小单位,假如计算机只有一个单核CPU,即同一时刻只能执行一个线程,对每个线程快速切换轮流执行。为了简化,CPU在内核中为每个线程提供各自虚拟的CPU,每个线程会认为自己独占着CPU,他们就不需要考虑时间片轮转的问题了。
一句人尽皆知的终结:进程是资源分配的最小单位,线程是CPU调度/程序执行的最小单位

并行和并发

假设有多个线程(CPU需要执行的任务),CPU的一个核心挨个挨个处理叫批处理。通过时间片轮转的方式快速切换,就是并发(多线程处理),虽然CPU切换速度非常快宏观上看起来多个线程是并行运行的,但实际不是。批处理和并发的实际处理时间是一样的
而现代的多核系统下,假设系统中有两个核心有两个线程,那么此时操作系统可以把两个线程分配给两个CPU核心,那么此时,这两个线程就可以同时进行了,这就是并行

并行和并发的区别就是在于是否一个核心还是多个核心吗?
实际上更加复杂,上述内容有一个前提就是假设线程之间是独立的,现在假设系统中有两个核心有两个线程,这两个线程需要持有同一把锁,拿到锁的线程才能向前运行,所以此时即使有两个核心也无法做到并行。这也是为什么高性能程序要避免使用锁。
所以说并行和并发不是说单核就是并发,多核就是并行,还要看线程之间的依赖关系。只有在多核,且任务之间没有关系的情况下,才叫真正的并行。当然,多核系统下,如果线程数多余核心数,既有并行也有并发。假设两个核心1,2,4个任务a,b,c,d,ab分配给了核心1,cd分配给了核心2,此时ab就是并发,cd也是并发,a与c就可以并行,b与d也可以并行

在这里插入图片描述
网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。

多线程开发

top     ps-ef  都可以查看进程

进程
进程有独立的地址空间
Linux为每个进程创建task struct
每个进程都参与内核调度,互不影响
线程
线程没有独立的内存空间,一个进程内所有线程公用一个
线程仅仅有自己的独立栈空间
使用线程的好处
大大提高了任务切换的效率避免了额外的TLB & cache的刷新

1、创建线程 pthread_create
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数
pthread_t *thread: 线程id指针,存放tid的
onst pthread_attr_t *attr:	线程属性
void *(*start_routine) (void *):	线程回调函数(函数指针)
void *arg:	函数参数
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>

int funct(int a){
	printf("hello\n");
	pause();
}

int main()
{
	pthread_t tid;
	int ret=pthread_create(&tid,NULL,funct,NULL);
	if(ret<0){
		printf("create thread error\n");
	}
	sleep(2);
	pause(); //进程结束,线程也就结束了
	return 0;
}
查看线程 ps -eLf | grep progressNAme
查看自己的tid pthread_t pthread_self(void)

在这里插入图片描述

2、线程的退出
  • 主动退出
    return 退出,
    void pthread_exit(void* retval) //类似于exit(),直接可以NULL
  • 被动退出
    pthread_cancel(tid);
  • 资源回收
    int pthread_join(pthread_t thread,void **retval); 需要父进程回收,是一个阻塞函数,即会等待线程结束
    int pthread_detach(pthread_t thread);
3、线程同步

访问共享资源之前上锁,结束之后上锁。但频繁的加锁解锁是低效的,会完全打破多线程的并发运行;另外多个锁的嵌套使用很有可能导致锁现象

二、Linux多线程TCP服务器

socket函数

int socket(int domain, int type, int protocol);  创建一个通信端点,并返回一个指向该端点的文件描述符

domain :	选择用于通信的协议族。如AF_INET   :    IPv4 Internet protocols                    

type:	套接字具有指定的类型,该类型指定了通信语义。 如 SOCK_STREAM  提供有序、可靠、双向、基于连接的字节流。 可支持带外数据传输机制

protocl:	指定与套接字一起使用的特定协议。 通常情况下,在给定的协议族中,只有一种协议支持特定的套接字类型,
		在这种情况下,协议可以指定为 0。然而也可能存在多种协议,在这种情况下,必须在本手册中指定特定的协议

memset函数

void *memset(void *s, int c, size_t n); 初始化内存
The memset() function fills the first n bytes of the memory area pointed to by s with the constant byte c

bind函数

给socket帮 ip和端口号

 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
 
当使用 socket() 创建套接字时,它存在于name space中,但没有分配地址。bind() 将 addr 指定的地址分配给文件描述符 sockfd 所指向的套接字。 addrlen 指定 addr 指向的地址结构体的大小。传统上,这一操作被称为 "为套接字分配name"。

On success, zero is returned.  On error, -1 is returned

listen函数

socket变为监听模式

int listen(int sockfd, int backlog);  listen for connections on a socket
listen() 会将 sockfd 所指向的套接字标记为可连接套接字,即使用 accept() 接受传入连接请求的套接字。

sockfd :文件描述符,指向 SOCK_STREAM 或 SOCK_SEQPACKET 类型的套接字。

backlog :定义了 sockfd 的待处理连接队列可能增长的最大长度。  如果连接请求在队列已满时到达,客户端可能会收到
带有 ECONNREFUSED 指示的错误信息,或者,如果底层协议支持重传,该请求可能会被忽略,以便以后重新尝试连接时获得成功。

accept函数

等待客户端连接,阻塞状态

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);   accept a connection on a socket

accept()  用于基于连接的套接字类型(SOCK_STREAM、SOCK_SEQPACKET)。 它从监听套接字、sockfd 的待处理连接队列中
提取第一个连接请求,创建一个新的连接套接字,并返回一个指向该套接字的新文件描述符。 新创建的套接字不处于监听状态。 原始
套接字 sockfd 不受此调用影响。

参数
 sockfd :是用 socket() 创建的套接字,用 bind() 绑定到本地地址,并在 listen() 之后监听连接。
 
addr :指向 sockaddr 结构的指针。 该结构由通信层已知的对等套接字地址填充。 addr 返回地址的具体格式由套接字的地址系列决定。 当 addr 				
       为 NULL 时,将不填写任何内容;在这种情况下,addrlen 将不被使用,也应为 NULL。

addrlen :一个值-结果参数:调用者必须初始化它,使其包含 addr 指向的结构体的大小(字节);

返回:返回一个指向该套接字的新文件描述符。它将包含对等地址的实际大小。如果提供的缓冲区太小,返回的地址将被截断;在这种情况下,addrlen 返回的值将大于调用时提供的值。

recv函数

size_t recv(int sockfd, void *buf, size_t len, int flags);  recv, recvfrom, recvmsg - receive a message from a socket
用于接收来自套接字的信息。 它们可用于接收无连接和面向连接套接字上的数据。
recv() 与 read() 的唯一区别在于是否有标志。 如果 flags 参数为零,recv() 通常等同于 read()

如果套接字上没有可用的消息,接收调用将等待消息的到来,除非套接字是非阻塞的,在这种情况下将返回值-1,并将外部变量 errno 设为 EAGAIN 或 EWOULDBLOCK。  
接收调用通常会返回任何可用的数据,最多不超过请求的数量,而不是等待收到请求的全部数据。

参数:
int sockfd:已连接的套接字confd,
void *buf:	接收缓冲区
size_t len:希望接收数据的最大字节长度
int flags:

返回值: return the number of bytes received, or -1 if an error occurred. 连接套接字关闭时,返回0

实现单线程

#include <stdio.h>      // 引入标准输入输出库  
#include <stdlib.h>     // 引入标准库,用于exit等函数  
#include <string.h>     // 引入字符串处理库,用于strcmp等函数  
#include <unistd.h>     // 引入POSIX操作系统API,用于close等函数  
#include <sys/socket.h> // 引入套接字编程库  
#include <netinet/in.h> // 引入IPv4和IPv6地址定义  
#include <arpa/inet.h>  // 引入地址转换函数,如inet_addr  
  
#define MAXLINE 4096    // 定义接收缓冲区的大小  
  
int main(int argc, char** argv)  
{  
    int listenfd, connfd; // 分别用于监听和连接的套接字文件描述符  
    struct sockaddr_in serveraddr; // 存储服务器地址信息的结构体  
    char buff[MAXLINE]; // 接收消息的缓冲区  
    int n, ret; // 分别用于存储接收到的字节数和比较结果  
  
    // 创建TCP套接字  
    //netstat   a 不仅显示套接字内容的命令,还显示尚未开始通信等状态的所有套接字 
    //			n 显示ip地址和端口号 
    // 			o 显示使用该套接字的程序PID
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {  
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);  
        exit(0); // 创建失败则退出  
    }  
  
    // 初始化服务器地址信息  
    memset(&serveraddr, 0, sizeof(serveraddr));  
    serveraddr.sin_family = AF_INET; // 使用IPv4  
    serveraddr.sin_addr.s_addr = inet_addr("10.47.158.12"); // 设置服务器IP地址  
    serveraddr.sin_port = htons(6666); // 设置服务器端口号,htons用于主机字节序到网络字节序的转换  
  
    // 绑定套接字到服务器地址和端口  
    if (bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) == -1) {  
        printf("bind socket error: %s(errno:%d)\n", strerror(errno), errno);  
        exit(0); // 绑定失败则退出  
    }  
  
    // 监听连接请求  
    if (listen(listenfd, 10) < 0) {  
        printf("listen socket error: %s(errno:%d)\n", strerror(errno), errno);  
        exit(0); // 监听失败则退出  
    }  
  
    printf("=========waiting for client's request.....=====\n"); // 打印等待客户端请求的消息  
  
    // 无限循环,接受并处理客户端连接  
    while(1) {  
    	//如果已建立连接,则不再调用accept,否则调用accept会生成新的connfd ,结果只会打印一次数据
    	if(connfd==0){
    	        if ((connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1) {  
            		printf("accept socket error:%s(errno:%d)\n", strerror(errno), errno);      
            		continue;  
        	}  
    	}

        // 从连接套接字接收消息  
        n = recv(connfd, buff, MAXLINE, 0);  
        if (n <= 0) {  
            // 如果n <= 0,表示连接已关闭或发生错误  
            close(connfd);  
            continue;  
        }  
        buff[n] = '\0'; // 在接收到的字符串末尾添加空字符  
  
        // 比较接收到的消息是否为"exit\n"  
        ret = strcmp(buff, "exit\n");  
        if (ret == 0) {  
            // 如果是,则关闭监听套接字并退出循环  
            close(listenfd);  
            close(connfd); // 注意:虽然此时connfd可能不再需要,但关闭是个好习惯  
            break;  
        }  
        printf("recv msg from client: %s\n", buff); // 打印接收到的消息  
  
        // 关闭连接套接字(在实际应用中,可能会在此处处理更多数据或保持连接)  
        close(connfd);  
    }  
  
    // 退出循环后,关闭监听套接字(注意:在上面的"exit"情况下,这一步会被跳过)  
    close(listenfd);  
  
    return 0; // 程序正常退出  
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include <arpa/inet.h>

#define MAXLINE 4096

int main(int argc, char** argv)
{
        int listenfd, connfd;
        struct sockaddr_in serveraddr;
        char buff[MAXLINE];
        int n, ret;

        if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { // third param is 0,means select default protocol
                printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
                exit(0);
        }

        memset(&serveraddr, 0, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET; //ipv4网络类型
        serveraddr.sin_addr.s_addr = (inet_addr("192.168.72.128"));  // INADDR_ANY: set addr 0.0.0.0, means all addr
        serveraddr.sin_port = htons(1234);  // 绑定端口号

        if ( bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) == -1) {
                printf("bind socket error: %s(errno:%d)\n", strerror(errno), errno);
                exit(0);
        }

        if (listen(listenfd, 10) < 0) {
                printf("listen socket error: %s(errno:%d)\n", strerror(errno), errno);
                exit(0);
        }

        printf("=========waiting for client's request.....=====\n");
        

    //    if ( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1) {
      //          printf("accept socket error:%s(errno:%d)\n", strerror(errno), errno);
                //continue;
      //  }

        while(1) {
        	// ccept(listenfd, (struct sockaddr*)&clientaddr, &addrlen);  //clientaddr  ip地址, &addrlen 端口号
		connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
		if(connfd<0){
			printf("accept socket error:%s(errno:%d)\n", strerror(errno), errno);
			return -1;
		}
		
		if(connfd>0){
			while(1){
			n = recv(connfd, buff, MAXLINE, 0); //断开连接后则不再阻塞
			if(n==0){
				printf("connect %d close\n",connfd);
				printf("=========waiting for client's request.....=====\n");
				close(connfd);
				
				break;
			}
                	buff[n] = '\0';
                	printf("recv msg from client: %s\n", buff);
                	
                	ret = strcmp(buff, "exit\n"); //相等返回0,客户端发送exit\n则断开连接
                	if (ret == 0){
                		close(connfd);
                		break;
                	}
			
			}

                      	  
		}
        }
        close(listenfd);
        return 0;
}

实现多线程

TCP服务器:该代码实现了一个基本的TCP服务器,能够监听指定端口上的连接请求,并接受来自客户端的数据。

多线程处理:服务器使用多线程来同时处理多个客户端连接。每个新连接都会触发一个新线程的创建,该线程负责与该客户端的通信。主线程(main函数中的循环)继续监听新的连接请求,并为每个新请求创建新的线程。这个过程会一直持续下去,直到程序被外部方式(如用户中断)终止。

数据接收与响应:服务器能够接收来自客户端的数据,并打印到标准输出。如果接收到特定的字符串(exit\n),则关闭与该客户端的连接。

错误处理:代码中包含了对套接字操作(如创建、绑定、监听、接受连接)的错误处理,如果操作失败,则会打印错误信息并退出程序(或继续监听其他连接)

#include<stdio.h>  
#include<stdlib.h>  
#include<string.h>  
#include<errno.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<netinet/in.h>  
#include<unistd.h>  
#include <arpa/inet.h>  
#include <pthread.h>  
  
#define MAXLINE 4096  
  
void* handle_client(void* arg) {  
    int connfd = *(int*)arg;  
    free(arg);  // 释放传递的指针  
    char buff[MAXLINE];  
    int n;  
  
    while (1) {  
        n = recv(connfd, buff, MAXLINE, 0);  
        if (n == 0) {  
            printf("Client %d disconnected\n", connfd);  
            close(connfd);  
            break;  
        }  
        buff[n] = '\0';  
        printf("Received from client: %s\n", buff);  
  
        if (strcmp(buff, "exit\n") == 0) {  
            close(connfd);  
            break;  
        }  
  
        // 在这里可以添加对客户端消息的处理逻辑  
    }  
  
    return NULL;  
}  
  
int main(int argc, char** argv)  
{  
    int listenfd, connfd;  
    struct sockaddr_in serveraddr;  
    pthread_t tid;  
    int *new_sock;  
    char buff[MAXLINE];
    int n, ret;

     if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { // third param is 0,means select default protocol
     	printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        exit(0);
     }

     memset(&serveraddr, 0, sizeof(serveraddr));
     serveraddr.sin_family = AF_INET; //ipv4网络类型
     serveraddr.sin_addr.s_addr = (inet_addr("192.168.72.128"));  // INADDR_ANY: set addr 0.0.0.0, means all addr
     serveraddr.sin_port = htons(1234);  // 绑定端口号

     if ( bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) == -1) {
         printf("bind socket error: %s(errno:%d)\n", strerror(errno), errno);
         exit(0);
     }

     if (listen(listenfd, 10) < 0) {
         printf("listen socket error: %s(errno:%d)\n", strerror(errno), errno);
         exit(0);
     }

  
    printf("=========Waiting for client's request.....=====\n");  
  
    while (1) {  
        connfd = accept(listenfd, NULL, NULL);  
        if (connfd < 0) {  
            printf("Accept error: %s (errno: %d)\n", strerror(errno), errno);  
            continue;  
        }  
  
        // 为新连接创建新线程  
        new_sock = malloc(sizeof(int));  
        *new_sock = connfd;  
        if (pthread_create(&tid, NULL, handle_client, (void*)new_sock) != 0) {  
            perror("Failed to create thread");  
            close(connfd);  
            free(new_sock);  
            continue;  
        }  
  
        // 可以在这里继续处理其他连接,或者什么都不做(由主线程循环负责)  
    }  
  
    close(listenfd);  
    return 0;  
}

mysql部分

mysql_real_connect

MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user,  
                          const char *passwd, const char *db, unsigned int port,  
                          const char *unix_socket, unsigned long clientflag);
  • mysql: 指向 MYSQL 结构的指针,这个结构由 mysql_init 函数初始化。这个结构包含了连接所需的所有信息。
  • host: MySQL 服务器的地址,可以是 IP 地址或者域名。如果 host 是 NULL 或 “localhost”,客户端将尝试通过 UNIX socket 连接。
  • user: 连接 MySQL 服务器所使用的用户名。
  • passwd: 用户的密码。如果密码是 NULL,客户端将尝试使用空密码进行连接。
  • db: 尝试连接后立即使用的默认数据库名。如果不需要默认数据库,可以将其设置为 NULL。
  • port: MySQL 服务器的端口号。如果设置为 0,客户端将使用默认的 MySQL 端口(通常是 3306)。
  • unix_socket: UNIX socket 文件的路径(如果服务器运行在 UNIX 上)。如果 host 参数是 NULL 或 “localhost”,并且 unix_socket 不是 - NULL,客户端将尝试通过 UNIX socket 而不是 TCP/IP 连接到服务器。
  • clientflag: 一个位掩码,用于修改客户端的默认行为。这些标志可以组合使用,以提供对连接行为的更细粒度控制。例如MYSQL_CLIENT_COMPRESS 可以用来请求使用压缩协议。
  • 返回值: 成功,mysql_real_connect 返回一个指向 MYSQL 结构的指针,该结构与传递给函数的指针相同;失败,返回 NULL。你可以通过调用 mysql_error 函数来获取关于连接失败的错误消息。

在使用完 MYSQL 结构后,应使用 mysql_close 函数来关闭连接并释放相关资源。
mysql_real_connect 是线程安全的,但 MYSQL 结构本身不是线程安全的。如果你在多线程环境中工作,每个线程都应该有自己的 MYSQL 结构实例。

mysql_error (指定要使用的 MySQL 连接标识符)

返回值
成功时,如果上一个操作没有错误,则返回空字符串(“”)。
失败时,返回描述上一个 MySQL 操作的错误的字符串。

#include <mysql.h>  
#include <stdio.h>  
#include <stdlib.h>  
  
int main() {  
    MYSQL *conn;  //MYSQL 是一个结构体类型,它包含了与 MySQL 数据库服务器建立连接所需的所有信息
    MYSQL_RES *res;  //指向MYSQL_RES的指针,存储结果
    MYSQL_ROW row;  //一个字符指针数组(char **)的别名,它用于表示结果集中的一行数据
  
    // 初始化 MySQL 连接  
    conn = mysql_init(NULL);  
  
    // 连接到 MySQL 数据库  
    if (!mysql_real_connect(conn, "localhost", "your_username", "your_password", "testdb", 0, NULL, 0)) {  
   		 //fprintf 函数是 C 语言标准库中的一个非常有用的函数,它用于向文件或标准输
        fprintf(stderr, "%s\n", mysql_error(conn));  出(如屏幕)写入格式化的数据。
        exit(1);  
    }  
  
    // 插入数据  
    if (mysql_query(conn, "INSERT INTO users (name) VALUES ('John Doe')")) {  
        fprintf(stderr, "%s\n", mysql_error(conn));  
        mysql_close(conn);  
        exit(1);  
    }  
  
    // 查询数据  
    if (mysql_query(conn, "SELECT * FROM users")) {  
        fprintf(stderr, "%s\n", mysql_error(conn));  
        mysql_close(conn);  
        exit(1);  
    }  
  
    res = mysql_use_result(conn);  
    while ((row = mysql_fetch_row(res)) != NULL) {  
        printf("%s\n", row[1]); // 假设 name 是第二列(索引从 0 开始)  
    }  
    mysql_free_result(res);  
  
    // 更新数据  
    if (mysql_query(conn, "UPDATE users SET name = 'Jane Doe' WHERE id = LAST_INSERT_ID()")) {  
        fprintf(stderr, "%s\n", mysql_error(conn));  
        mysql_close(conn);  
        exit(1);  
    }  
  
    // 再次查询以验证更新  
    if (mysql_query(conn, "SELECT * FROM users")) {  
        fprintf(stderr, "%s\n", mysql_error(conn));  
        mysql_close(conn);  
        exit(1);  
    }  
  
    res = mysql_use_result(conn);  
    while ((row = mysql_fetch_row(res)) != NULL) {  
        printf("%s\n", row[1]);  
    }  
    mysql_free_result(res);  
  
    // 删除数据  
    if (mysql_query(conn, "DELETE FROM users WHERE id = LAST_INSERT_ID()")) {  
        fprintf(stderr, "%s\n", mysql_error(conn));  
        mysql_close(conn);  
        exit(1);  
    }  
  
    // 关闭数据库连接  
    mysql_close(conn);  
  
    return 0;  
}

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

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

相关文章

8.23-docker基础命令学习

docker 1.docker容器 [rootdocker ~]# systemctl start docker[rootdocker ~]# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEcentos latest 5d0da3dc9764 2 years ago 231MB​# 容器执行完就退出了​[rootdocker ~]# docker run -it …

C++20中的简写函数模板(abbreviated function template)

简写函数模板(abbreviated function template):当占位符类型(auto或concept auto)出现在函数声明或函数模板声明的参数列表中时&#xff0c;该声明将声明一个函数模板&#xff0c;并且每个占位符的一个虚构模板参数(one invented template parameter)将附加到模板参数列表。如下…

【412】【K 次乘运算后的最终数组 I】

第一次打周赛&#xff0c;难绷 后两题都差200个样例。 这题很简单&#xff0c;看题就可以 class Solution:def getFinalState(self, nums: List[int], k: int, multiplier: int) -> List[int]:nlen(nums)for i in range(k):mnmin(nums)for j in range(n):if nums[j]mn:nums…

自定义类加载器使用geotools读取高程报 ImageRead: No OperationDescriptor is registered 问题

背景 项目中使用了 自定义classLoader ,&#xff0c;然后使用下面简化后的代码读取高程数据 public class Test{public static void main(String[] args) throwS Exception{CustomClassLoader cl new CustomClassLoader();Class<?> clazz cl.loadClass(“Test”);Te…

后端代码练习5--验证码案例

我们日常生活中&#xff0c;在进行应用程序注册或者登录的时候&#xff0c;出于安全性的考虑&#xff0c;我们都会被进行一项验证的操作&#xff0c;即通过网页给我们的图片进行一些列的操作&#xff0c;最终完成对我们身份的验证并给我们这些用户返回验证码&#xff0c;让我们…

C语言-有两个磁盘文件A和B,各存放一行字母,今要求把这两个文件的信息合并(按字母顺序排列),输出到一个新文件C中去-深度代码解析

1、题目要求 有两个磁盘文件A和B&#xff0c;各存放一行字母&#xff0c;今要求把这两个文件的信息合并&#xff08;按字母顺序排列&#xff09;&#xff0c;输出到一个新文件C中去 2、准备工作 问题1&#xff1a;为什么不需要手动创建C.txt文件&#xff1f; 答&#xff1a;根…

技术分享-商城篇-订单模块-取消/收货功能(十六)

前言 再上一篇文章技术分享-商城篇-用户订单管理&#xff08;十五) 中&#xff0c;订单模块用户操作含有&#xff1a;取消订单、去支付、确认收货、删除订单、查看详情、去退款、查看物流、再次购买等业务操作&#xff0c;以上的每一个操作&#xff0c;都是对应不同的业务和状…

AudioNotes -将音频内容转 markdown

文章目录 一、关于 AudioNotes效果展示音视频识别和整理与音视频内容对话 二、使用方法1、安装 Ollama2、拉取模型3、部署服务3.1 Docker部署&#xff08;推荐&#xff09;&#x1f433;3.2 本地部署 &#x1f4e6; 一、关于 AudioNotes AudioNotes 能够快速提取音视频的内容&…

贪心处理任务(华为od机考题)

一、题目 1.原题 在某个项目中有多个任务&#xff08;用 tasks 数组表示&#xff09;需要您进行处理&#xff0c; 其中 tasks[i] [si, ei]&#xff0c; 你可以在 si < day < ei 中的任意一天处理该任务。 请返回你可以处理的最大任务数。 注&#xff1a;一天可以完成一…

硬件面试经典 100 题(81~90)题

81、请问下图电路中二极管 D1、D2 有什么作用&#xff1f; 在 Vi 输入电压接近于零时&#xff0c;D1、D2 给三极管 T1、T2 提供偏置电压&#xff0c;使 T1、T2 维持导通&#xff0c;以消除交越失真。 陈氏解释 这道题参见&#xff1a;硬件面试经典 100 题&#xff08;51~70 题…

【学习笔记】STM32F407探索者HAL库开发(三)IO分配

【学习笔记】STM32F407探索者HAL库开发&#xff08;三&#xff09;IO分配 1 STM32F407 IO资源分配表2 STM32F407ZGT6 引脚定义3 IO分配的重要性3.1 硬件设计优化3.2 软件编程3.3 系统性能提升 4 F1/F7/H7芯片的IO分配差异4.1 引脚数量和分组4.2 功能模式4.2.1 输入模式4.2.2 输…

Kubernetes 外部 etcd 集群的快速 Docker Compose 部署指南

一、背景 在高可用 Kubernetes 部署中&#xff0c;需要单独部署外部 etcd 集群&#xff0c;而不是使用 kubeadm 默认在 master 节点上部署的 etcd。以下是关于这一配置场景的详细记录。 二、etcd简介 etcd 是一个高可用的分布式键值存储系统&#xff0c;主要用于存储和管理配…

使用Qt+Visual Stuidio写一个简单的音乐播放器(1)

1.使用QMediaPlayer播放音乐 第三步:在代码头部加上: #include <QtMultimedia/QMediaPlayer> // VS向.pro文件添加代码的方式 #pragma execution_character_set("utf-8") // qt支持显示中文 QMediaPlayer类是一个高级媒体播放类。它可以用来播放歌曲、电…

leetcode 893. Groups of Special-Equivalent Strings

原题链接 You are given an array of strings of the same length words. In one move, you can swap any two even indexed characters or any two odd indexed characters of a string words[i]. Two strings words[i] and words[j] are special-equivalent if after any …

力扣: 设计链表

文章目录 需求代码结尾 需求 你可以选择使用单链表或者双链表&#xff0c;设计并实现自己的链表。 单链表中的节点应该具备两个属性&#xff1a;val 和 next 。val 是当前节点的值&#xff0c;next 是指向下一个节点的指针/引用。 如果是双向链表&#xff0c;则还需要属性 p…

Java 的数组详解

数组的定义 数组是相同类型数据的有序集合 数组描述的是相同类型的若干个数据&#xff0c;按照一定的先后次序排列组合而成 其中&#xff0c;每一个数据称作一个数组元素&#xff0c;每个数组元素可以通过一个下标(编号、标记)来访问它&#xff0c;下标是从 0 开始的&#xf…

100套动画PPT模版分享

100套动画PPT模板 目录下载链接 目录 下载链接 「动画模板」链接&#xff1a;https://pan.quark.cn/s/73ea2523f198 点击下载

中小型企业如何管理文档?8款工具来帮你

文章介绍了以下几个工具&#xff1a;PingCode、Worktile、氚云、泛微、中通天鸿、Tower、知因智慧、SharePoint。 在中小型互联网企业中&#xff0c;文档知识库的管理常常让人头疼。团队成员散布在不同的地点&#xff0c;文档分散在各种工具中&#xff0c;查找信息变得异常困难…

Linux启动流程和Systemd特性

文章目录 内核设计流派linux启动流程1.硬件加电自检2.启动加载器bootloader3.加载kernel4.init初始化5.用户终端启动 systemdsystemd特性systemd的unitunit配置文件 systemctl管理系统服务service unit服务状态 service unit文件格式Unit段Service段Install段 内核设计流派 1.…

资源第二篇:bundle 的config.json 文件内容的解析

简介 本篇文章主要是对bundle包的核心文件config.json 的分析。config.json记录着整个bundle包的具体信息&#xff0c;并通过config.json 去解析整个bundle包。 bundle 目录下的文件结构 import 存放所有的json。场景、预制体、texture2D配置等jsonnative 存放所有的实际资源…