板凳--------第60章 SOCKET:服务器设计

news2024/12/27 16:11:22

60.1 迭代型和并发型服务器 1016

1.迭代型: 服务器每次只处理一个客户端,只有当完全处理完一个客户端的请求后才会去处理下一个客户端。只适用于快速处理客户端请求的场景,因为每个客户端都必须等待,直到前面所有的客户端都处理完了服务器才能继续服务下一个客户端。
2.并发型: 服务器能够同时处理多个客户端的请求。适用于每个请求都需要大量处理时间,或是当客户端和服务器在进行扩展对话中需要来回传递信息的场景。本章重点

60.2 迭代型UDP echo服务器 1016

#include <syslog.h>
#include "id_echo.h"
#include "become_daemon.h"
int
main(int argc, char *argv[])
{
    int sfd;
    ssize_t numRead;
    socklen_t len;
    struct sockaddr_storage claddr;
    char buf[BUF_SIZE];
    char addrStr[IS_ADDR_STR_LEN];

    if (becomeDaemon(0) == -1)
        errExit("becomeDaemon");

    sfd = inetBind(SERVICE, SOCK_DGRAM, NULL);
    if (sfd == -1) {
        syslog(LOG_ERR, "Could not create server socket (%s)", strerror(errno));
        exit(EXIT_FAILURE);
    }

    /* Receive datagrams and return copies to senders */

    for (;;) {
        len = sizeof(struct sockaddr_storage);
        numRead = recvfrom(sfd, buf, BUF_SIZE, 0,
                           (struct sockaddr *) &claddr, &len);
        if (numRead == -1)
            errExit("recvfrom");

        if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
                        != numRead)
            syslog(LOG_WARNING, "Error echoing response to %s (%s)",
                    inetAddressStr((struct sockaddr *) &claddr, len,
                                   addrStr, IS_ADDR_STR_LEN),
                    strerror(errno));
    }
}
#include "id_echo.h"

int
main(int argc, char *argv[])
{
    int sfd, j;
    size_t len;
    ssize_t numRead;
    char buf[BUF_SIZE];

    if (argc < 2 || strcmp(argv[1], "--help") == 0)
        usageErr("%s host msg...\n", argv[0]);

    /* Construct server address from first command-line argument */

    sfd = inetConnect(argv[1], SERVICE, SOCK_DGRAM);
    if (sfd == -1)
        fatal("Could not connect to server socket");

    /* Send remaining command-line arguments to server as separate datagrams */

    for (j = 2; j < argc; j++) {
        len = strlen(argv[j]);
        if (write(sfd, argv[j], len) != len)
            fatal("partial/failed write");

        numRead = read(sfd, buf, BUF_SIZE);
        if (numRead == -1)
            errExit("read");

        printf("[%ld bytes] %.*s\n", (long) numRead, (int) numRead, buf);
    }

    exit(EXIT_SUCCESS);
}

(base) wannian07@wannian07-PC:~/Desktop/std/linux_prog_interface$ gcc id_echo_cl.c -o id_echo_cl error_functions.c become_daemon.c inet_sockets.c
(base) wannian07@wannian07-PC:~/Desktop/std/linux_prog_interface$ gcc id_echo_cl.c -o id_echo_cl error_functions.c become_daemon.c inet_sockets.c

在这里插入图片描述

60.3 并发型TCP echo服务器 1019

客户端发送无限量数据给服务器,适合并发型,使多个客户端能够同时得到服务
1.服务器调用becomeDaemon()成为一个守护进程
2.为使程序更小,使用Internet域套接字函数库
3.服务器为每个客户端传教一个子进程,确保不会出现僵尸进程。通过信号SIGCHLD安装信号处理例程来实现
4.服务器主体部分由for()循环组成,接受客户端的连接,通过fork()传教子进程。子进程调用handleRequest()处理客户端,父进程在for()循环下接受下一个客户端连接。
5.每次调用fork()后,监听套接字和连接套接字都在子进程中得到复制。
在这里插入图片描述

6.每个子进程在处理完一个客户端后终止。
客户端:

#include "inet_sockets.h"
#include "tlpi_hdr.h"

#define BUF_SIZE 100

int
main(int argc, char *argv[])
{
    int sfd;
    ssize_t numRead;
    char buf[BUF_SIZE];

    if (argc != 2 || strcmp(argv[1], "--help") == 0)
        usageErr("%s host\n", argv[0]);

    sfd = inetConnect(argv[1], "echo", SOCK_STREAM);
    if (sfd == -1)
        errExit("inetConnect");

    switch (fork()) {
    case -1:
        errExit("fork");

    case 0:             /* Child: read server's response, echo on stdout */
        for (;;) {
            numRead = read(sfd, buf, BUF_SIZE);
            if (numRead <= 0)                   /* Exit on EOF or error */
                break;
            printf("%.*s", (int) numRead, buf);
        }
        exit(EXIT_SUCCESS);

    default:            /* Parent: write contents of stdin to socket */
        for (;;) {
            numRead = read(STDIN_FILENO, buf, BUF_SIZE);
            if (numRead <= 0)                   /* Exit loop on EOF or error */
                break;
            if (write(sfd, buf, numRead) != numRead)
                fatal("write() failed");
        }

        /* Close writing channel, so server sees EOF */

        if (shutdown(sfd, SHUT_WR) == -1)
            errExit("shutdown");
        exit(EXIT_SUCCESS);
    }
}

服务器:

#include <signal.h>
#include <syslog.h>
#include <sys/wait.h>
#include "become_daemon.h"
#include "inet_sockets.h"       /* Declarations of inet*() socket functions */
#include "tlpi_hdr.h"

#define SERVICE "echo"          /* Name of TCP service */
#define BUF_SIZE 4096

static void             /* SIGCHLD handler to reap dead child processes */
grimReaper(int sig)
{
    int savedErrno;             /* Save 'errno' in case changed here */

    savedErrno = errno;
    while (waitpid(-1, NULL, WNOHANG) > 0)
        continue;
    errno = savedErrno;
}

/* Handle a client request: copy socket input back to socket */

static void
handleRequest(int cfd)
{
    char buf[BUF_SIZE];
    ssize_t numRead;

    while ((numRead = read(cfd, buf, BUF_SIZE)) > 0) {
        if (write(cfd, buf, numRead) != numRead) {
            syslog(LOG_ERR, "write() failed: %s", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }

    if (numRead == -1) {
        syslog(LOG_ERR, "Error from read(): %s", strerror(errno));
        exit(EXIT_FAILURE);
    }
}

int
main(int argc, char *argv[])
{
    int lfd, cfd;               /* Listening and connected sockets */
    struct sigaction sa;

    if (becomeDaemon(0) == -1)
        errExit("becomeDaemon");

    /* Establish SIGCHLD handler to reap terminated child processes */

    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sa.sa_handler = grimReaper;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        syslog(LOG_ERR, "Error from sigaction(): %s", strerror(errno));
        exit(EXIT_FAILURE);
    }

    lfd = inetListen(SERVICE, 10, NULL);
    if (lfd == -1) {
        syslog(LOG_ERR, "Could not create server socket (%s)", strerror(errno));
        exit(EXIT_FAILURE);
    }

    for (;;) {
        cfd = accept(lfd, NULL, NULL);  /* Wait for connection */
        if (cfd == -1) {
            syslog(LOG_ERR, "Failure in accept(): %s", strerror(errno));
            exit(EXIT_FAILURE);
        }

        /* Handle each client request in a new child process */

        switch (fork()) {
        case -1:
            syslog(LOG_ERR, "Can't create child (%s)", strerror(errno));
            close(cfd);                 /* Give up on this client */
            break;                      /* May be temporary; try next client */

        case 0:                         /* Child */
            close(lfd);                 /* Unneeded copy of listening socket */
            handleRequest(cfd);
            _exit(EXIT_SUCCESS);

        default:                        /* Parent */
            close(cfd);                 /* Unneeded copy of connected socket */
            break;                      /* Loop to accept next connection */
        }
    }
}

(base) wannian07@wannian07-PC:~/Desktop/std/linux_prog_interface$ gcc is_echo_cl.c -o is_echo_cl error_functions.c become_daemon.c inet_sockets.c
(base) wannian07@wannian07-PC:~/Desktop/std/linux_prog_interface$ gcc is_echo_sv.c -o is_echo_sv error_functions.c become_daemon.c inet_sockets.c

60.4 并发型服务器的其他设计方案 1021

https://www.bilibili.com/read/cv28123863/
TCP多进程并发服务器(进程池版本)&TCP多进程并发服务器(动态创建进程版本)
在这里插入图片描述

TCP多线程并发服务器(进程池版本)&TCP多线程并发服务器(动态创建进程版本)

在这里插入图片描述

https://www.bilibili.com/read/cv28123863/

在服务器上预先创建进程或线程
1.服务器程序在启动阶段(即在任何客户端请求到来之前)就立刻预先创建好一定数量的子进程(或线程),而不是针对每个客户端来创建一个新的子进程(或线程)。这些子进程构成了一种服务池(server pool)
2.服务池中的每个子进程一次只处理一个客户端。在处理完客户端请求后,子进程并不会终止,而是获取下一个待处理的客户端继续处理,如此类推。
3.服务器应用中仔细地管理子进程。服务池应该足够大,以确保能充分响应客户端的请求。 a. 服务器父进程必须对未占用的子进程加以监视,服务器处于负载高峰期时增加服务池的大小,这样就总会有足够多的子进程存在,从而可以立刻服务于新的客户端请求。
b. 负载下降了,那么应该相应地降低服务池的大小,因为过多的空余进程会降低系统的整体性能。
c. 服务池中的子进程必须遵循某些协议,使得它们能以独占的方式选择一个客户端连接。
d. 服务池中的每个子进程在监听描述符的 accept()调用上阻塞就足够了。
e. 服务器父进程在创建任何子进程之前先创建监听套接字, 然后每个子进程在 fork()调用中继承该套接字的文件描述符。当一个新的客户端连接到来时,只有其中一个子进程能完成 accept()调用。
f. 由于 accept()在一些老式的实现中并不是一个原子化的系统调用,因此可能需要通过一些互斥技术(例如文件锁)来支持,以确保每次只有一个子进程可以执行 accept()调用
在单个进程中处理多个客户端
设计让单个服务器进程来处理多个客户端, 必须采用一种能允许单个进程同时监视多个文件描述符上 I/O 事件的 I/O 模型(I/O 多路复用、信号驱动 I/O 或者 epoll)。
在设计单进程服务器时,服务器进程必须做一些通常由内核来处理的调度任务。在每个客户端一个服务器进程地解决方案中,依靠内核来确保每个服务器进程能公平地访问到服务器主机的资源。用单个服务器进程来处理多个客户端时,服务器进程必须自行确保一个或多个客户端不会霸占服务器,从而使其他的客户端处于饥饿状态。
采用服务器集群
使用多个服务器系统—服务器集群(server farm)。构建服务器集群最简单的一种方法是 DNS 轮转负载共享(DNS round-robin load sharing)(或负载分发, load distribution),一个地区的域名权威服务器将同一个域名映射到多个 IP地址上(即,多台服务器共享同一个域名)。后续对 DNS 服务器的域名解析请求将以循环轮转的方式以不同的顺序返回这些 IP 地址。
优势: DNS 循环轮转的是成本低,而且容易实施。
问题1、是远端 DNS 服务器上所执行的缓存操作, 这意味着今后位于某个特定主机(或一组主机)上的客户端发出的请求会绕过循环轮转 DNS 服务器,并总是由同一个服务器来负责处理。
问题2、循环轮转 DNS 并没有任何内建的用来确保达到良好负载均衡(不同的客户端在服务器上产生的负载不同)或者是确保高可用性的机制(如果其中一台服务器宕机或者运行的服务器 程序崩溃了怎么办?)。
服务器亲和性(server affinity),确保来自同一个客户端的请求序列能够全部定向到同一台服务器上,这样由服务器维护的任何有关客户端状态的信息都能保持准确。
服务器负载均衡(server load balancing)由一台负载均衡服务器将客户端请求路由到服务器集群中的其中一个成员上。(为了确保高可用性,可能还会有一台备用的服务器。一旦负载均衡主服务器崩溃,备用服务器就立刻接管主服务器的任务。)这消除了由远端 DNS 缓存所引起的问题,因为服务器集群只对外提供了一个单独的 IP 地址(也就是负载均衡服务器的 IP 地址)。负载均衡服务器结合一些算法来衡量或计算服务器负载(可能是根据服务器集群的成员所提供的量值),并智能化地将负载分发到集群中的各个成员之上。负载均衡服务器也会自动检测集群中失效的成员(如果需要,还会自动检测新增加的服务器成员)。最后,负载均衡服务器可能还会提供对服务器亲和力的支持。
https://www.jb51.net/article/122310.htm
服务器负载均衡的基本功能和实现原理

60.5 inetd( Internet 超级服务器)守护进程

/etc/services , 列出了系统中服务项目,大部分服务器进程通常只是等待着偶尔发送过来的连接请求或数据报, 等待。服务器进程会占用内核进程表中的槽位,会占用一些内存和交换空间,对系统产生负载。
守护进程 inetd 用来消除运行大量非常用服务器进程的需要。好处:
1.与其为每个服务运行一个单独的守护进程,现在只用一个进程—inetd 守护进程—就可以监视一组指定的套接字端口,并按照需要启动其他的服务。因此可降低系统上运行的进程数量。
2.inetd 简化了启动其他服务的编程工作。因为由 inetd 执行的一些步骤通常在所有的网络服务启动时都会用到。 由于 inetd 监管着一系列的服务,可按照需要启动其他的服务,因此 inetd 有时候也被称为 Internet 超级服务器。
inetd 守护进程所做的操作
inetd 守护进程通常在系统启动时运行。 inetd 执行如下步骤:
1.对于在配置文件/etc/inetd.conf 中指定的每项服务, inetd 都会创建一个恰当类型的套接字(即流式套接字或数据报套接字),然后绑定到指定的端口号上。此外,每个 TCP套接字都会通过 listen()调用允许客户端发来连接。
2.通过 select()调用, inetd 对前一步中创建的所有套接字进行监视,看是否有数据报或请求连接发送过来
3.select()调用进入阻塞态,直到一个 UDP 套接字上有数据报可读或者 TCP 套接字上收到了连接请求。在 TCP 连接中, inetd 在进入下一个步骤之前会先为连接执行 accept()调用。
4.要启动这个套接字上指定的服务, inted 调用 fork()创建一个新的进程, 然后通过 exec()启动服务器程序。在执行 exec()前,子进程执行如下的步骤。
(a)除了用于 UDP 数据报和接受 TCP 连接的文件描述符外,将其他所有从父进程继 承而来的文件描述符都关闭。
(b)在文件描述符 0、 1 和 2 上复制套接字文件描 述符,并关闭套接字文件描述符本身(因为已经不需要它了)。完成这一步之后, 启动的服务器进程就能通过这三个标准的文件描述符同套接字通信了。
(c)为启动的服务器进程设定用户和组 ID,设定的值可在 /etc/inetd.conf 中的相应条目找到。
5.第 3 步中,如果在 TCP 套接字上接受了一个连接, inetd 就关闭这个连接套接字。
6.inetd 服务跳转回第 2 步继续执行。
/etc/inetd.conf 文件
inetd 守护进程的操作由一个配置文件来控制,通常是/etc/inetd.conf。该文件中的每一行都描述了一种由 inetd 处理的服务
echo 服务: /etc/inetd.conf 文件中的每一行都由以下字段组成,由空格来将它们分隔开。
1.服务名称(service name):指定了一项服务的名称,这项服务可在/etc/services 文件中找到。结合协议字段(protocol),就可以通过查找/etc/services 文件以确定 inetd 应该为这项服务监视哪一个端口号。
2.套接字类型(Socket type):该字段指定了这项服务所用的套接字类型—流式 套接字(stream)还是数据报套接字(dgram)。
3.协议( protocol):该字段指定了这个套接字所使用的协议。这个字段可以包含文件 /etc/protocols 中所列出的任何 Internet 协议,所有的服务都会指定 tcp或 udp。
4. 标记(flags):该字段的内容要么是 wait,要么是 nowait。这个字段指明了由 inetd 启 动的服务器(暂时的)是否会接管用于该服务的套接字。如果启动的服务器需要管理 这个套接字,那么该字段被指定为 wait。这将导致 inetd 把这个套接字从它所监视的文件描述符集合中移除,直到这个服务器 程序退出为止。
5.登录名( login name):该字段由/etc/passwd 中的用户名部分组成,还可以在其后 紧跟一个句号以及一个/etc/group 中的组名称。这些名称确定了运行的服务器程 序的用户 ID 和组 ID。
6.服务器程序(server program):该字段指定了被执行的服务器程序的路径名。
7.服务器程序参数(server program arguments):该字段指定了一个或多个参数,参数之间由空格符分隔。当执行服务器程序时,这些参数就作为程序的参数列表。在被执行的服务器程序中, 第一个参数对应于 argv[0], 通常和服务器程序名称的基础部分相同。 下一个参数对应于 argv[1],以此类推。
由 inetd 调用的流式套接字(TCP)服务器通常都被设计为只处理一个单独的客户端连接,处理完后就终止, 把监听其他连接的任务留给了 inetd。 修改了/etc/inetd.conf 文件后,需要发送一个 SIGHUP 信号给 inetd,请求它重新读取配置文件
inetd 可以简化服务器程序的编程工作,并发型(通常是 TCP)服务器。这是因为 inetd 已经帮它所调用的服务器程序完成了以下步骤。
1.执行所有和套接字相关的初始化工作,调用 socket()、 bind()以及 listen()(针对 TCP服务器)。
2.对于一个 TCP 服务,为新到来的连接执行 accept()操作。
3.创建一个新的进程来处理到来的 UDP 数据报或者是 TCP 连接。自动将调用的服务器进程设置为守护进程。 inetd 通过 fork()处理所有与进程创建相关的细节,通过 SIGCHLD 信号处理例程清除所有退出的子进程。
4.将代表 UDP 套接字或 TCP 连接套接字的文件描述符复制到标准文件描述符 0、 1 和2 上,并关闭所有其他的文件描述符(因为它们并不会在调用的服务器进程中用到)。
5.执行服务器程序。
在/etc/inetd.conf 文件中创建如下的条目,使得 inetd 可以调用该服务器程序

#include <syslog.h>
#include "tlpi_hdr.h"
#define BUF_SIZE 4096
int
main(int argc, char *argv[])
{
    char buf[BUF_SIZE];
    ssize_t numRead;
    while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) {
        if (write(STDOUT_FILENO, buf, numRead) != numRead) {
            syslog(LOG_ERR, "write() failed: %s", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }
    if (numRead == -1) {
        syslog(LOG_ERR, "Error from read(): %s", strerror(errno));
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
} 

60.6 总结

https://www.cnblogs.com/wangbin2188/tag/Linux/

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

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

相关文章

NGINX_六 nginx 日志文件详解

六 nginx 日志文件详解 nginx 日志文件分为 **log_format** 和 **access_log** 两部分log_format 定义记录的格式&#xff0c;其语法格式为log_format 样式名称 样式详情配置文件中默认有log_format main $remote_addr - $remote_user [time_local] "req…

如何有效管理信息技术课堂

有效管理信息技术课堂是确保学生学习效果、维护课堂秩序和提升学生兴趣的关键。以下是一些详细的方法和策略&#xff0c;旨在帮助教师更好地管理信息技术课堂&#xff1a; 一、制定明确的课堂规则 强调课堂纪律&#xff1a;确保学生明确了解并遵守课堂纪律&#xff0c;如准时…

数据中心:AI范式下的内存挑战与机遇

在过去的十年里&#xff0c;数据中心和服务器行业经历了前所未有的扩张&#xff0c;这一进程伴随着CPU核心数量、内存带宽(BW)&#xff0c;以及存储容量的显著增长。这种超大规模数据中心的扩张不仅带来了对计算能力的急剧需求&#xff0c;也带来了前所未有的内存功率密度挑战&…

【HarmonyOS NEXT】鸿蒙 如何在包含web组件的页面 让默认焦点有效

页面包含web组件Button组件等&#xff0c;把页面的默认焦点放到Button组件上&#xff0c;不起效果。 因为web组件默认会在组件加载完成后获取焦点&#xff1b; 可以在web的网页加载完成时onPageEnd回调中&#xff0c;将设置默认获焦的组件通过focusControl.requestFocus方法主…

微信发布分班查询结果

亲爱的老师们&#xff01;期末考完&#xff0c;新学期就快要来了&#xff0c;还在为分班查询头疼吗&#xff1f;别担心&#xff0c;今天我要和大家分享一个超级实用的小技巧——如何通过微信发布分班查询结果&#xff0c;让家长们和学生们都能掌握新学期的动态&#xff1f; 分…

Manim本地安装

目录 背景Manim安装及配置一个上手例子参考文献 背景 通过上一期的介绍&#xff0c;我们对Manim有了初步的认识也知道Manim版本的区别&#xff0c;这一期&#xff0c;我们来给自己的计算机安装一个社区版ManimCE&#xff0c;方便以后玩Manim。笔者的硬件配置是联想笔记本Windo…

Google Gemini API 打造翻译助手

API申请 https://aistudio.google.com/app/apikey 目前API可以免费受限使用&#xff0c;大概一分钟60次调用 https://ai.google.dev/pricing prompt编写 您是一位精通各种语言的专业翻译家&#xff0c;尤其擅长【替换成你想翻译的文本类别&#xff0c;比如&#xff1a;农业、…

【挑战100天首通《谷粒商城》】-【第一天】06、环境-使用vagrant快速创建linux虚拟机

文章目录 课程介绍1、安装 linux 虚拟机2、安装 VirtualBoxStage 1&#xff1a;开启CPU虚拟化Stage 2&#xff1a;下载 VirtualBoxStage 2&#xff1a;安装 VirtualBoxStage 4&#xff1a;安装 VagrantStage 4-1&#xff1a;Vagrant 下载Stage 4-2&#xff1a;Vagrant 安装Stag…

表组装示例

代码; #include <gtk-2.0/gtk/gtk.h> #include <glib-2.0/glib.h> #include <stdio.h>int main(int argc, char *argv[]) {gtk_init(&argc, &argv);GtkWidget *window;window gtk_window_new(GTK_WINDOW_TOPLEVEL);gtk_window_set_title(GTK_WINDO…

实战18:基于tkinter+jupyter notebook开发的情感分析系统

项目演示: 完整代码: import pandas as pd import numpy as np from collections import Counter import re import jieba from tqdm import tqdm from sklearn.metrics import roc_curve, auc import joblib import gensim from sklearn.svm import SVC from gensim.mode…

34.构建核心注入代码

上一个内容&#xff1a;33.获取入口点 以 33.获取入口点 它的代码为基础进行修改 实现的功能是把LoadLibrary函数注入到目标进程实现加载我们的模块。LoadLibrary只有有程序使用过了它的代码就会加载到内存中&#xff08;因为动态链接库是内存加载&#xff09;就是a程序要用L…

基于JSP技术的固定资产管理系统

开头语&#xff1a;你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSPServlet 工具&#xff1a;MyEclipse、Tomcat 系统展示 首页 注册界面…

全排列(C++)

2024年6月16日1&#xff1a;48&#xff0c;正式开启每日一题~ 题目要求&#xff1a;给定正整数n&#xff08;n≥1&#xff09;&#xff0c;给出1~n的全排列&#xff0c;例如&#xff0c;当n3时全排列是{{1&#xff0c;2&#xff0c;3}&#xff0c;{1&#xff0c;3&#xff0c;…

数字化物资管理系统的未来:RFID技术的创新应用

在信息化和智能化不断发展的背景下&#xff0c;物资管理系统的数字化转型已成为各行各业关注的焦点。RFID技术作为一种先进的物联网技术&#xff0c;通过全面数字化实现物资信息的实时追踪和高效管理&#xff0c;为企业的物资管理提供了强有力的支持。 首先&#xff0c;RFID技…

docker将容器打包提交为镜像,再打包成tar包

将容器打包成镜像可以通过以下步骤来实现。这里以 Docker 为例&#xff0c;假设你已经安装了 Docker 并且有一个正在运行的容器。 1. 找到正在运行的容器 首先&#xff0c;你需要找到你想要打包成镜像的容器的 ID 或者名字。可以使用以下命令查看所有正在运行的容器&#xff…

SQLite扩展插件终极集合

作为一个嵌入式数据库引擎&#xff0c;SQLite 与其他数据库管理系统相比&#xff0c;缺少了一些功能。不过 SQLite 提供了一个扩展机制&#xff0c;因此我们可以在网络上找到大量的 SQLite 插件。 今天我们介绍的这个插件叫做 sqlean&#xff0c;它打包了许多流行的 SQLite 扩…

Go微服务: redis分布式锁保证数据原子操作的一致性

概述 随着云计算和大数据技术的飞速发展&#xff0c;分布式系统已经成为现代IT架构的重要组成部分在分布式系统中&#xff0c;数据的一致性是一个至关重要的挑战&#xff0c;特别是在并发访问和修改共享资源的场景下分布式锁是一种跨进程、跨机器节点的互斥锁&#xff0c;用于…

Python web 开发 flask 实践

1、前言 前文已经介绍了很多关于 python 的算法和脚本的写法&#xff0c;在本文将开启python的 web 的开发&#xff0c;和java 类似的&#xff0c;对于 web 开发也需要引入框架&#xff0c;对于 python 的 web 开发来说常见的有 flask 和 django 两种&#xff0c;在本文中将要…

Comparison method violates its general contract! 神奇的报错

发生情况 定位到问题代码如下&#xff08;脱敏处理过后&#xff09;&#xff0c;意思是集合排序&#xff0c;如果第一个元素大于第二个元素&#xff0c;比较结果返回1&#xff0c;否则返回-1&#xff0c;这里粗略的认为小于和等于是一样的结果 List<Integer> list Arr…

【Android14 ShellTransitions】(六)SyncGroup完成

这一节的内容在WMCore中&#xff0c;回想我们的场景&#xff0c;是在Launcher启动某一个App&#xff0c;那么参与动画的就是该App对应Task&#xff08;OPEN&#xff09;&#xff0c;以及Launcher App对应的Task&#xff08;TO_BACK&#xff09;。在确定了动画的参与者后&#x…