TCP并发服务器

端口号快速复用函数

通过getsockoptsetsockopt函数,管理套接字的端口号复用设置。具体操作如下:

getsockopt函数

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

功能:获取套接字的某些选项的属性。

参数

  1. sockfd: 套接字描述符。
  2. level: 获取的层级(例如SOL_SOCKET)。
  3. optname: 要获取的操作名称(如SO_REUSEADDR)。
  4. optval: 获取的值(0表示禁用,非0表示启用)。
  5. optlen: 参数4的大小。

返回值: 成功返回0,失败返回-1。


setsockopt函数

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

功能:设置套接字的某些选项的属性。

参数

  1. sockfd: 套接字描述符。
  2. level: 设置的层级(例如SOL_SOCKET)。
  3. optname: 要设置的操作名称(如SO_REUSEADDR)。
  4. optval: 设置的值(0表示禁用,非0表示启用)。
  5. optlen: 参数4的大小。

返回值: 成功返回0,失败返回-1。


示例代码:端口号快速复用

// 获取当前端口号是否能快速复用
int n;
int len = sizeof(n);
getsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, &len);
if (n == 0) {
    printf("端口号快速复用未启动\n");
} else {
    printf("端口号快速复用已经启动\n");
}

// 设置当前套接字端口号快速复用
n = 999;
setsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));


1. 循环服务器模型

描述

循环服务器模型是一次只处理一个客户端请求的传统方式,处理完一个客户端请求后,才会继续处理下一个客户端请求。由于其效率较低,每次只能执行单个任务,因此在高并发场景下表现不佳。

在循环服务器模型中,服务器端采用了一个循环来不断接收客户端请求。每当一个客户端连接成功后,服务器创建一个新的套接字用于通信,并处理客户端的请求。每处理完一个客户端请求后,服务器继续等待下一个客户端的连接。

主要步骤:

  1. 创建套接字:使用 socket() 函数创建套接字。
  2. 绑定:将套接字与服务器的 IP 地址和端口号绑定。
  3. 监听:开始监听客户端连接请求。
  4. 循环接收客户端请求
    • 每次 accept() 成功后,创建一个新的套接字用于通信。
    • 循环收发信息,直到客户端断开连接。
  5. 关闭连接:每次客户端断开连接后,关闭相应的套接字。
  6. 关闭监听套接字

循环服务器代码实现

代码示例
#include <myhead.h>
#define IP "192.168.60.45"
#define PORT 6666
#define BACKLOG 20

int main(int argc, const char *argv[]) {
    // 1. 创建套接字
    int oldfd = socket(AF_INET, SOCK_STREAM, 0);  // IPV4, TCP协议
    if (oldfd == -1) {
        perror("socket");
        return -1;
    }

    // 2. 获取端口号属性,查看是否启用端口号快速复用
    int n;
    int len = sizeof(n);
    if (getsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, &len) == -1) {
        perror("getsockopt");
        return -1;
    }
    if (n) {
        printf("端口号快速复用已经启动\n");
    } else {
        printf("端口号快速复用未启动\n");
    }

    // 设置端口号快速复用
    n = 999;
    if (setsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) {
        perror("setsockopt");
        return -1;
    }
    printf("端口号快速复用成功\n");

    // 3. 绑定IP和端口号
    struct sockaddr_in server = {
        .sin_family = AF_INET,  // IPV4
        .sin_port = htons(PORT),  // 端口号转换为网络字节序
        .sin_addr.s_addr = inet_addr(IP),  // IP地址
    };
    if (bind(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1) {
        perror("bind");
        return -1;
    }

    // 4. 监听
    if (listen(oldfd, BACKLOG) == -1) {
        perror("listen");
        return -1;
    }

    // 5. 接受客户端连接请求并创建新的描述符用于通信
    struct sockaddr_in client;
    socklen_t client_len = sizeof(client);
    int newfd;

    while (1) {
        newfd = accept(oldfd, (struct sockaddr *)&client, &client_len);
        if (newfd == -1) {
            perror("accept");
            return -1;
        }
        printf("%s发来连接请求\n", inet_ntoa(client.sin_addr));

        // 6. 循环收发信息
        char buff[1024];
        while (1) {
            memset(buff, 0, sizeof(buff));
            int len = recv(newfd, buff, sizeof(buff), 0);  // 接收客户端信息
            if (len == 0) {  // 客户端断开连接
                printf("客户端下线\n");
                break;
            }
            printf("%s\n", buff);
            strcat(buff, "5201314");  // 添加回复信息
            send(newfd, buff, sizeof(buff), 0);  // 发送信息
        }
    }

    // 关闭套接字
    close(oldfd);
    close(newfd);
    return 0;
}
代码流程:
  1. 创建套接字socket() 函数创建一个 TCP 套接字。
  2. 设置端口号复用:通过 setsockopt() 设置端口复用,允许在同一端口上建立多个连接。
  3. 绑定:使用 bind() 将套接字与 IP 地址和端口绑定。
  4. 监听:调用 listen() 使套接字进入监听状态,准备接受客户端连接。
  5. 接受连接:通过 accept() 接受客户端连接,返回一个新的套接字描述符用于与客户端通信。
  6. 收发消息:在循环内通过 recv() 接收客户端消息,然后用 send() 发送回应消息。直到客户端断开连接。
  7. 关闭套接字:处理完成后关闭套接字。
缺点:
  • 同步阻塞:当前的设计是同步阻塞的,服务器在处理一个客户端请求时不能同时处理其他客户端的请求。因此,如果有多个客户端连接,必须等待前一个客户端的请求处理完成后才能继续。
  • 客户端阻塞:新客户端要通信时,必须等到旧客户端退出。服务器只能在当前连接断开后接受新的客户端请求。
  • 新客户端要通信时,必须等待旧客户端退出。
改进:

为了解决这个问题,可以采用 多线程多进程 模型来并发处理客户端请求,从而避免客户端之间的阻塞。


2. 基于TCP的并发服务器

2.1 多进程并发服务器

多进程并发服务器的模型通常由一个父进程负责监听客户端的连接请求,每当接收到一个连接请求时,父进程创建一个新的子进程来处理与客户端的通信。父进程和子进程之间通过文件描述符传递数据。父进程负责监听和接收客户端连接,子进程则处理数据收发。

多进程服务器模型的关键问题:

1. 子进程在哪创建: 父进程在接收到客户端的连接请求时会创建一个子进程来处理该请求。

示例代码:

pid_t pid = fork();
if (pid > 0) 
{
    // 父进程:继续监听新的连接请求,关闭新文件描述符
    close(newfd);
} 
else if (pid == 0) 
{
    // 子进程:关闭旧的监听套接字,并处理客户端通信
    close(server_fd);
    // 与客户端通信...
    close(newfd); // 处理完成后关闭与客户端的连接
    exit(0); // 子进程处理完请求后退出
} 
else 
{
    perror("fork");
    return -1;
}

2. 子进程怎么回收:子进程处理完任务后会退出,因此需要确保父进程能够回收已经退出的子进程,防止僵尸进程的产生。

常见的方式:

  • 阻塞回收:父进程使用 wait()waitpid() 函数在子进程退出时阻塞等待并回收它们。
  • 非阻塞回收:如果不想让父进程被 wait() 阻塞,可以使用信号处理机制,通过捕捉 SIGCHLD 信号并在信号处理函数中回收子进程。

示例代码(非阻塞回收):

// 信号处理函数,用于回收僵尸进程
void handle_sigchld(int sig) {
    while (waitpid(-1, NULL, WNOHANG) > 0);  // 回收已退出的子进程
}

// 在主程序中捕捉 SIGCHLD 信号
signal(SIGCHLD, handle_sigchld);

3. 文件描述符: 由于系统对打开的文件描述符有限制,因此需要确保在父子进程中正确管理文件描述符。

在操作系统中,文件描述符是有限的,每个进程可以打开的文件(包括套接字)的数量是有限的。如果服务器并发连接数较多,且每个连接都创建一个新的子进程,就可能遇到文件描述符耗尽的问题。可以通过以下方式解决或避免这一问题:

解决方案:

  • 增加文件描述符限制:可以通过 ulimit -n 命令临时增加系统的文件描述符限制,或者在 /etc/security/limits.conf 文件中永久增加限制。
  • 使用线程池或连接池:而不是为每个客户端创建一个新的子进程,可以通过线程池或连接池来管理客户端连接。多线程或线程池模型可以减少系统开销,因为线程的创建和销毁比进程更加轻量。
  • 复用文件描述符:通过一些技术手段(例如 select()poll()epoll())来管理多个连接,不需要为每个客户端创建一个独立的进程或线程,从而避免耗尽文件描述符。

示例:修改文件描述符限制

  1. 临时修改文件描述符的限制:
ulimit -n 65535
  1. 永久修改文件描述符的限制: 在 /etc/security/limits.conf 文件中添加: 
* soft nofile 65535
* hard nofile 65535

总结

  1. 子进程创建:父进程在接收到客户端连接请求后,使用 fork() 创建子进程来处理该客户端的通信。
  2. 子进程回收:父进程可以通过 wait()waitpid() 回收子进程,也可以通过信号处理函数来非阻塞回收已退出的子进程。
  3. 文件描述符限制:多进程模型可能会受到系统文件描述符限制的影响,解决方法包括增加文件描述符限制、使用线程池或连接池等技术来减少文件描述符的占用。
多进程并发服务器执行模型:

定义信号处理函数,非阻塞回收僵尸进程。

绑定子进程退出时的信号。

  1. 创建套接字
  2. 绑定
  3. 监听
  4. 循环接收客户端连接
  5. 让父进程接收客户端请求并关闭新文件描述符,子进程关闭旧的描述符只负责数据收发。
多进程服务器代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <wait.h>

#define IP "192.168.60.45"
#define PORT 6666
#define BACKLOG 100

// 信号处理函数,用于回收僵尸进程
void fun(int sss)
{
    if (sss == SIGCHLD)
    {
        while (waitpid(-1, NULL, 0) > 0); // 循环回收子进程
    }
}

int main(int argc, const char *argv[])
{
    // 1. 捕获子进程退出时的信号
    if (signal(SIGCHLD, fun) == SIG_ERR)
    {
        perror("signal");
        return -1;
    }

    // 2. 创建TCP类型的套接字
    int oldfd = socket(AF_INET, SOCK_STREAM, 0);
    if (oldfd == -1)
    {
        perror("socket");
        return -1;
    }

    // 3. 设置端口号快速复用
    int n = 1;
    if (setsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1)
    {
        perror("setsockopt");
        return -1;
    }
    printf("端口号快速复用成功\n");

    // 4. 绑定
    struct sockaddr_in server = {
        .sin_family = AF_INET,
        .sin_port = htons(PORT),
        .sin_addr.s_addr = inet_addr(IP),
    };
    if (bind(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1)
    {
        perror("bind");
        return -1;
    }

    // 5. 监听
    if (listen(oldfd, BACKLOG) == -1)
    {
        perror("listen");
        return -1;
    }

    struct sockaddr_in client;
    socklen_t client_len = sizeof(client);
    char buff[1024];

    // 6. 主循环接收客户端连接
    while (1)
    {
        int newfd = accept(oldfd, (struct sockaddr *)&client, &client_len); // 接收客户端连接
        printf("%s发来连接请求\n", inet_ntoa(client.sin_addr));

        pid_t pid = fork(); // 创建子进程
        if (pid > 0) // 父进程监听,并关闭新的描述符
        {
            close(newfd);
        }
        else if (pid == 0) // 子进程处理数据收发
        {
            close(oldfd); // 子进程关闭旧的描述符
            while (1)
            {
                int len = recv(newfd, buff, sizeof(buff), 0);
                if (len == 0)
                {
                    printf("客户端退出\n");
                    break;
                }
                printf("客户端%s发来消息:%s\n", inet_ntoa(client.sin_addr), buff);
                strcat(buff, inet_ntoa(client.sin_addr)); // 回去时加上客户端IP
                send(newfd, buff, sizeof(buff), 0);
            }
            close(newfd);
            exit(0); // 子进程退出
        }
        else
        {
            perror("fork");
            return -1;
        }
    }
    return 0;
}

客户端代码:

客户端通过连接到服务器来发送数据并接收服务器的响应。该程序创建一个TCP套接字,连接到指定的服务器IP和端口,并与服务器进行数据交互。

#include <myhead.h>

#define IP "192.168.60.45"
#define PORT 6666

int main(int argc, const char *argv[])
{
    // 1. 创建套接字
    int oldfd = socket(AF_INET, SOCK_STREAM, 0);
    if (oldfd == -1)
    {
        perror("socket");
        return -1;
    }

    // 2. 连接服务器
    struct sockaddr_in server = {
        .sin_family = AF_INET,
        .sin_port = htons(PORT),
        .sin_addr.s_addr = inet_addr(IP),
    };
    if (connect(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1)
    {
        perror("connect");
        return -1;
    }

    // 3. 数据收发
    char buff[1024];
    while (1)
    {
        fgets(buff, sizeof(buff), stdin); // 键盘输入字符串
        buff[strlen(buff) - 1] = '\0';   // 去除换行符

        // 发送数据到服务器
        send(oldfd, buff, sizeof(buff), 0);

        // 接收服务器数据
        int len = recv(oldfd, buff, sizeof(buff), 0);
        if (len == 0)
        {
            printf("服务器意外退出\n");
            break;
        }
        printf("接收服务器消息:%s\n", buff);
    }

    // 关闭套接字
    close(oldfd);

    return 0;
}

 总结
  • 多进程并发模型: 通过父进程监听和创建子进程来处理每个客户端的请求,使用fork创建子进程,在子进程中负责数据收发,父进程关闭新文件描述符,子进程关闭旧文件描述符。通过信号处理函数回收子进程,避免僵尸进程。
  • TCP客户端: 连接到服务器后,通过套接字发送数据并接收响应,支持实时数据交互。

这种模型适用于客户端和服务器之间的实时通信,特别是在需要处理多个客户端请求的场景中。


2.2 多线程并发服务器

1. 多线程的优势
  • 资源开销小:线程的资源开销比进程小,创建和销毁线程的速度比进程快。
  • 响应速度快:如果客户端连接较多,可以通过线程池提前创建线程来分配给客户端,减少响应延迟。
  • 线程销毁开销小:线程占用资源较少,销毁线程的资源开销也较小。
2. 服务器模型
  • 主线程:负责监听客户端连接。
  • 子线程:负责接收和发送数据,与客户端通信。
3. 服务器的工作流程
  1. 创建原始套接字:使用 socket() 创建一个 TCP 类型的套接字。
  2. 绑定 IP 地址和端口号:使用 bind() 绑定服务器的 IP 地址和端口。
  3. 监听客户端连接:使用 listen() 开始监听客户端的连接请求。
  4. 接收客户端连接:使用 accept() 接收客户端的连接请求,并为每个客户端创建一个新线程。
  5. 线程处理客户端请求:每个线程接收到客户端消息后进行处理并发送响应。线程执行完后退出。
4. 线程池
  • 为了处理大量并发请求,线程池的机制可以预先创建一定数量的线程,接收到客户端请求时,从线程池中取出一个线程来处理该请求。
5. 代码实现
#include <myhead.h>

// 定义服务器的 IP 地址、端口号和最大连接数
#define IP "192.168.60.45"
#define PORT 9999
#define BACKLOG 10

// 定义结构体,用于传递客户端信息和新文件描述符
typedef struct {
    struct sockaddr_in client; // 客户端信息
    int newfd;                 // 新的文件描述符
} ZYJ;

// 线程体函数,处理与客户端的通信
void *fun(void *sss) {
    // 获取新文件描述符和客户端信息
    int newfd = ((ZYJ *)sss)->newfd;
    struct sockaddr_in client = ((ZYJ *)sss)->client;
    
    // 打印客户端的 IP 地址
    printf("%s发来信息\n", inet_ntoa(client.sin_addr));
    
    char buff[1024];
    while (1) {
        // 接收客户端发来的消息
        int len = recv(newfd, buff, sizeof(buff), 0);
        if (len == 0) { // 客户端退出
            printf("客户端退出\n");
            break;
        }
        
        // 打印收到的消息
        printf("收到消息:%s\n", buff);
        
        // 在消息末尾加上 "1973"
        strcat(buff, "1973");
        
        // 将处理后的消息发送回客户端
        send(newfd, buff, sizeof(buff), 0);
    }
    
    // 线程结束时退出
    pthread_exit(NULL);
}

int main(int argc, const char *argv[]) {
    // 1. 创建套接字
    int oldfd = socket(AF_INET, SOCK_STREAM, 0);
    if (oldfd == -1) {
        perror("socket");
        return -1;
    }

    // 2. 绑定 IP 地址和端口号
    struct sockaddr_in server = {
        .sin_family = AF_INET,
        .sin_port = htons(PORT),
        .sin_addr.s_addr = inet_addr(IP),
    };
    if (bind(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1) {
        perror("bind");
        return -1;
    }

    // 3. 监听客户端的连接请求
    if (listen(oldfd, BACKLOG) == -1) {
        perror("listen");
        return -1;
    }

    struct sockaddr_in client;
    socklen_t client_len = sizeof(client);
    
    // 4. 主线程循环接收客户端连接
    while (1) {
        int newfd = accept(oldfd, (struct sockaddr *)&client, &client_len); // 接收客户端连接请求
        
        ZYJ sb;
        sb.newfd = newfd;          // 保存新的文件描述符
        sb.client = client;        // 保存客户端信息

        pthread_t tid;
        // 5. 创建子线程处理客户端请求
        tid = pthread_create(&tid, NULL, fun, &sb);
        if (tid == -1) {
            perror("pthread_create");
            return -1;
        }

        // 6. 将线程设为分离状态,系统会自动回收线程资源
        pthread_detach(tid);
    }

    return 0;
}
代码功能
  1. 创建套接字:通过 socket() 创建一个 TCP 套接字。
  2. 绑定 IP 地址和端口:通过 bind() 将套接字绑定到指定的 IP 地址和端口号。
  3. 监听连接:调用 listen() 开始监听来自客户端的连接请求,最多允许 BACKLOG 个连接排队。
  4. 接受连接:主线程通过 accept() 接收客户端的连接请求,并返回一个新的文件描述符 newfd 用于与客户端进行通信。
  5. 创建子线程:每当接受到新的连接,主线程会通过 pthread_create() 创建一个子线程处理客户端的消息传递和接收。每个子线程的处理逻辑通过 fun() 函数完成。
  6. 线程回收:通过 pthread_detach() 将线程设置为分离状态,子线程在完成后会自动回收资源,避免主线程等待子线程退出。
关键函数
  • socket():创建一个套接字。
  • bind():将套接字与本地的 IP 地址和端口号绑定。
  • listen():让套接字进入监听状态,准备接受客户端连接。
  • accept():阻塞等待客户端连接,并返回一个新的套接字用于与客户端通信。
  • pthread_create():创建新的线程来处理客户端请求。
  • pthread_detach():将线程设为分离状态,线程结束后自动回收资源。
  • recv():接收客户端发送的消息。
  • send():向客户端发送消息。
适用场景

这个多线程模型适用于客户端连接量较大或较为频繁的场景。每个客户端连接都会分配一个线程进行处理,因此可以在短时间内处理多个客户端的请求。

线程池优化

虽然该程序为每个连接创建了一个独立的线程,但在实际应用中,为了提高资源利用率和性能,可以引入线程池。线程池事先创建好一组线程,客户端请求到来时从线程池中取出一个线程来处理,而不是每次都创建一个新的线程。这样可以减少线程的创建和销毁开销。

注意事项
  • 多线程同步问题:虽然本例中没有涉及多线程之间的共享资源,但在复杂应用中可能需要考虑线程同步机制(如互斥锁、条件变量等)。
  • 线程回收:线程被设为分离状态(pthread_detach()),这样可以在线程结束后由系统自动回收资源,而不需要调用 pthread_join()

总结

该程序展示了一个典型的多线程并发服务器模型,使用线程处理每个客户端请求,减少了主线程的负担,提高了处理效率。通过 pthread_detach() 来自动回收资源,避免了线程泄露问题。


6.  客户端代码示例
#include <myhead.h>

#define IP "192.168.60.45"
#define PORT 9999

int main(int argc, const char *argv[])
{
    // 1、创建套接字
    int oldfd = socket(AF_INET, SOCK_STREAM, 0);
    if (oldfd == -1)
    {
        perror("socket");
        return -1;
    }

    // 2、连接服务器
    struct sockaddr_in server = {
        .sin_family = AF_INET,
        .sin_port = htons(PORT),
        .sin_addr.s_addr = inet_addr(IP),
    };
    if (connect(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1)
    {
        perror("connect");
        return -1;
    }

    // 3、数据收发
    char buff[1024];
    while (1)
    {
        fgets(buff, sizeof(buff), stdin); // 键盘输入字符串
        buff[strlen(buff) - 1] = '\0';   // 去除换行符

        // 发送数据到服务器
        send(oldfd, buff, sizeof(buff), 0);

        // 接收服务器数据
        int len = recv(oldfd, buff, sizeof(buff), 0);
        if (len == 0)
        {
            printf("服务器意外退出\n");
            break;
        }
        printf("接收服务器消息:%s\n", buff);
    }

    // 关闭套接字
    close(oldfd);

    return 0;
}

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

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

相关文章

vue3的宏到底是什么东西?

前言 从vue3开始vue引入了宏&#xff0c;比如defineProps、defineEmits等。我们每天写vue代码时都会使用到这些宏&#xff0c;但是你有没有思考过vue中的宏到底是什么&#xff1f;为什么这些宏不需要手动从vue中import&#xff1f;为什么只能在setup顶层中使用这些宏&#xff…

无重复字符的最长子串习题分析

习题&#xff1a;&#xff08;leetcode 3 &#xff09; 给定一个字符串s&#xff0c;请你找出其中不含有重复字符的最长子串的长度。 分析&#xff1a; 对于寻找子串、数组中某部分等&#xff0c;我们可以使用滑动窗口和双指针思想来求解。 滑动窗口通常用于解决需要连续子…

Linux服务器的Tomcat9中部署War包

文章目录 Linux服务器的Tomcat9中部署War包一、引言二、部署Tomcat91、安装Tomcat91.1、下载Tomcat91.2、解压安装1.3、启动Tomcat9 2、配置环境变量&#xff08;可选&#xff09; 三、部署War包1、准备War包2、部署War包3、配置Context&#xff08;可选&#xff09; 四、启动和…

如果接口返回值图片有很长一串码,需要添加前缀

需要在前面添加前缀&#xff1a;data:image/jpeg;base64,然后将值赋值给<img :src"originalImage" /> this.tableLists.map((item)>{item.originalImage "data:image/jpeg;base64,"item.originalImage})以上方法会导致出现一个小bug&#xff0c;…

2024年11月21日Github流行趋势

项目名称&#xff1a;twenty 项目维护者&#xff1a;charlesBochet, lucasbordeau, Weiko, FelixMalfait, bosiraphael项目介绍&#xff1a;正在构建一个由社区支持的现代化Salesforce替代品。项目star数&#xff1a;21,798项目fork数&#xff1a;2,347 项目名称&#xff1a;p…

Excel——宏教程(精简版)

一、宏的简介 1、什么是宏&#xff1f; Excel宏是一种自动化工具&#xff0c;它允许用户录制一系列操作并将其转换为VBA(Visual Basic for Applications)代码。这样&#xff0c;用户可以在需要时执行这些操作&#xff0c;以自动化Excel任务。 2、宏的优点 我们可以利用宏来…

【eNSP】OSPF、RIP与静态路由互通实验(四)

OSPF、RIP与静态路由互通实验 实验目的实验要求实验步骤步骤 1&#xff1a;配置R1、R2、R3、R4、R5、R6、R7的端口ip步骤 2&#xff1a;配置R1、R2、R3的OSPF动态路由协议步骤 3&#xff1a;配置R3、R4、R5的RIP动态路由协议步骤 4&#xff1a;配置R3作为边界路由器&#xff0c…

Vision-Language Models for Vision Tasks: A Survey 论文解读

摘要 大多数视觉识别研究在深度神经网络&#xff08;DNN&#xff09;训练中严重依赖于人工标注的数据&#xff0c;且通常为每个单一的视觉识别任务训练一个DNN&#xff0c;导致这种视觉识别范式既繁琐又耗时。为解决这两个挑战&#xff0c;近年来对视觉语言模型&#xff08;VL…

Stable Diffusion核心网络结构——U-Net

​ &#x1f33a;系列文章推荐&#x1f33a; 扩散模型系列文章正在持续的更新&#xff0c;更新节奏如下&#xff0c;先更新SD模型讲解&#xff0c;再更新相关的微调方法文章&#xff0c;敬请期待&#xff01;&#xff01;&#xff01;&#xff08;本文及其之前的文章均已更新&a…

C#桌面应用制作计算器进阶版01

基于C#桌面应用制作计算器做出了少量改动&#xff0c;其主要改动为新增加了一个label控件&#xff0c;使其每一步运算结果由label2展示出来&#xff0c;而当点击“”时&#xff0c;最终运算结果将由label1展示出来&#xff0c;此时label清空。 修改后运行效果 修改后全篇代码 …

python: generator model using sql server 2019

設計或生成好數據庫&#xff0c;可以生成自己設計好的框架項目 # encoding: utf-8 # 版权所有 &#xff1a;2024 ©涂聚文有限公司 # 许可信息查看 &#xff1a;言語成了邀功盡責的功臣&#xff0c;還需要行爲每日來值班嗎 # 描述&#xff1a; : 生成实体 # Author …

【Nginx从入门到精通】04-安装部署-使用XShell给虚拟机配置静态ip

文章目录 总结1、XShell &#xff1a;方便管理多台机器2、配置ip文件&#xff1a;区分大小写 一、查看上网模式二、Centos 7 设置静态ipStage 1 &#xff1a;登录root账号Stage 2 &#xff1a;设置静态ip : 修改配置文件 <font colororange>ifcfg-ens33Stage 2-1&#xf…

【深度学习】利用Java DL4J构建金融欺诈检测系统

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

当你项目服务器磁盘报警

当你们公司运维收到这样的邮件&#xff0c;大概率日志文件过大引起的 在Linux下如何不停止服务,清空nohup.out文件呢&#xff1f; nohup.out会一直一直自己增长下去&#xff0c;如果你的服务器硬盘不给力的话&#xff0c;很容易把应用也挂掉&#xff08;硬盘没空间 &#xff0…

面向未来的智能视觉参考设计与汽车架构,思尔芯提供基于Arm技术的创新方案

引言&#xff1a; 随着科技的飞速发展&#xff0c;智能视觉IoT已成为科技领域的热门话题&#xff0c;为智能家居、智慧城市等领域带来新机遇。然而&#xff0c;物联网市场的碎片化特性对智能视觉芯片设计构成挑战。同时&#xff0c;汽车行业正经历技术驱动的变革&#xff0c;软…

C++ 20 中 vector list stack queue 分别从功能 效率等全方面分析其差异

C++ 20 中 vector list stack queue 分别从功能 效率等全方面分析其差异 在 C++20 中,std::vector、std::list、std::stack 和 std::queue 是常用的容器或容器适配器。以下从功能、效率、使用场景等方面对它们进行详细对比。 功能对比 2.效率对比 存储和访问 扩容效率std::…

51单片机之串口通讯

1.串口简介 串口&#xff0c;全称串行通信接口或串行通讯接口&#xff08;通常指COM接口&#xff09;&#xff0c;是一种常用于电子设备间通讯的全双工扩展接口。 串口通讯的技术基础&#xff0c;指一位一位地按顺序传送数据。其特点是线路简单&#xff0c;只需一对传输线&…

UE5 DownloadImage加载jpg失败的解决方法

DownloadImage加载jpg失败的解决方法 现象解决方案具体方法 现象 用UE自带的 DownloadImage 无法下载成功&#xff0c;从 failure 引脚出来。 接入一个由监控器自动保存起的图像&#xff0c;有些可以正常加载成功&#xff0c;有些无法加载成功。 经调查问题出现在&#xff0c;…

Springboot 整合 Java DL4J 搭建智能问答系统

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

相较于发达国家约70%的普及率,中国【台式洗碗机】渗透率仅在2%-3%之间,潜在市场增量可观

内容摘要 据 HengCe 最新调研&#xff0c;2023年中国台式洗碗机市场销售收入达到了 万元&#xff0c;预计2030年可以达到 万元&#xff0c;2024-2030期间年复合增长率(CAGR)为 %。本研究项目旨在梳理台式洗碗机领域产品系列&#xff0c;洞悉行业特点、市场存量空间及增量空间&…