IPC之九:使用UNIX Domain Socket进行进程间通信的实例

socket 编程是一种用于网络通信的编程方式,在 socket 的协议族中除了常用的 AF_INET、AF_RAW、AF_NETLINK等以外,还有一个专门用于 IPC 的协议族 AF_UNIX,IPC 是 Linux 编程中一个重要的概念,常用的 IPC 方式有管道、消息队列、共享内存等,本文主要介绍用于本地进程间通信的 UNIX Domain Socket,本文给出了多个具体的实例,每个实例均附有完整的源代码;本文所有实例在 Ubuntu 20.04 上编译测试通过,gcc版本号为:9.4.0;本文的实例中涉及多进程编程等,本文对 Linux 编程的初学者有一些难度。

1 UNIX Domain Socket 的基本概念

  • 本文不再回顾有关 socket 编程的相关知识,但是阅读本文需要掌握基本的 socket 编程方法,这方面的资料比较丰富,请自行解决;
  • 关于 UNIX Domain Socket 的在线文档:man 7 unix
  • 在使用 socket() 创建一个 socket 时,如果使用 AF_UNIX 协议族,而不是我们常用的 AF_INET 时,创建的 sockst 被称为 UNIX Domain Socket;
  • AF_UNIX 是一个用来进行进程间通信的协议族,AF_LOCAL 和 AF_UNIX 是完全一样的;
  • AF_UNIX 协议族和 AF_INET 的主要区别:
    • 当使用 AF_UNIX 时,一个进程发送的数据无需再经过协议栈,而是通过内核缓冲区将数据直接拷贝到另一个进程的缓冲区中;
    • 当使用 AF_UNIX 时,无需再绑定 IP 地址,发送和接收过程也与 IP 地址无关;
  • 作为 IPC 方法,socket 的 AF_UNIX 家族并没有共享内存的效率高,我认为其存在的价值主要是它的使用方法几乎和 socket 网络编程方法无异,这无疑给那些熟悉 socket 编程的程序员带来了极大的方便;
  • 使用 AF_UNIX 建立的 socket 被称为 UNIX Domain Socket,又名 UDS 或者 IPC socket,常用于互联网通信的,使用 AF_INET(AF_INET6) 建立的 socket 被称为 Internet Socket;
  • 一个 Internet socket 需要绑定在一个 IP 地址和一个端口上,与 Internet Socket 不同,UNIX socket 必须绑定到一个文件路径上,在后面会详细说明;
  • UNIX socket 也可以是匿名的,也就是无需绑定到文件路径上,没有名称,通常匿名的 UNIX socket 只能用于父子进程或者有同一个父进程的子进程之间通信;
  • 以下如无特别说明,socket 特指 UNIX Domain Socket,而非 Internet Socket。

2 命名 UNIX Socket 的使用

  • 创建 UNIX Socket

    #include <sys/socket.h>
    #include <sys/un.h>unix_socket = socket(AF_UNIX, type, 0);
    
    • type 可以是:
      • SOCK_STREAM:面向连接的数据流传输;
      • SOCK_DGRAM:面向无连接的数据报传输;
      • SOCK_SEQPACKET:面向连接的顺序数据包传输,可以按照发送的顺序传递消息;
  • 绑定一个文件路径

    • 一个命名的 UNIX domain socket 必须绑定到一个路径下;

    • 在 IPv4 下,Internet Socket 绑定 IP 地址和端口时,使用 struct sockaddr_in,在 Unix Socket 上绑定文件路径时需要使用结构 struct sockaddr_un

      struct sockaddr_un {sa_family_t sun_family;               /* AF_UNIX */char        sun_path[108];            /* Pathname */
      };
      
      • sun_family 一定是 AF_UNIX
      • sun_path 指向一个文件路径,比如 /tmp/unix_socket
    • bind()

      #include <sys/types.h>          /* See NOTES */
      #include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
      
      • sockfd 是使用 socket() 建立的 UNIX Socket;
      • addr 指向 struct sockaddr_un 的指针
  • 下面代码片段创建了一个 socket 并绑定到了一个文件路径上

    ......
    int unix_sock;
    struct sockaddr_un serveraddr;unix_sock = socket(AF_UNIX, SOCK_STREAM, 0);memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sun_family = AF_UNIX;
    strcpy(serveraddr.sun_path, "/tmp/unix_socket");bind(unix_sock, (struct sockaddr *)&serveraddr, SUN_LEN(&serveraddr));
    ......
    
  • 建议使用宏 SUN_LEN(addr) 来计算填好文件路径的 struct sockaddr_un 的长度,因为其中的 sun_path 字段是不定长的,这个宏可以帮助我们计算出正确的结果,宏 SUN_LEN(addr) 定义在头文件 sys/un.h 中;

  • 当把一个 socket 绑定到一个文件上时,在文件系统上,这个文件会真实存在,但是这个文件不能使用 open() 打开,而只能使用 socket() 打开;

  • 建议在使用完一个命名 UNIX domain socket 后,显式地使用 unlink() 或者 remove() 将其删除,避免其残留在文件系统中;

  • 尽管我们的例子中使用的文件名在 /tmp/ 目录下,但在实际应用中我们并不建议这样做,因为 /tmp/ 目录是任何人都可以读写的,这可能会带来一些安全隐患,通常可以把这个文件建立在项目所在的目录下,相对比较安全;

  • 当我们用 bind() 将一个文件绑定到 socket 上时,相应的文件将被建立,该文件的默认权限为所绑定的 socket 的权限,通常情况下使用 socket() 建立的 socket 的权限是 0777,可以使用 fstat 获得,但这时建立的 socket 文件的默认属性是 0775;

  • 如果我们在 bind() 之前先使用 fchmod() 修改 socket 的权限,那么再使用 bind() 绑定一个文件时,其建立的文件的权限也会产生变化,下面这段代码可以演示这种权限的变化;

    ......
    #define SOCK_NAME       "./unix_socket.sock"
    ......
    int sockfd;
    struct sockaddr_un addr;
    struct stat sock_stat;sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    fchmod(sockfd, 0660);memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, SOCK_NAME);
    bind(sockfd, (struct sockaddr *)&addr, SUN_LEN(&addr));system("ls -l unix_socket.sock");
    ......
    
  • 这段代码在当前目录下生成的 socket 文件的权限为 0660,最后一行的 system("ls -l") 显示的文件清单将清楚地显示出来,你可以尝试一下,如果没有 fchmod() 这行代码,生成的文件的权限为 0775;

  • 使用 ls -l 显示文件清单时,该文件的前面有一个 s 标志,表示这是一个 socket 文件;

  • socket 以及 socket 文件是可以使用 stat()、fstat() 获取属性,同时可以使用 chmod()、fchmod() 来改变权限的;

  • 改变一个 socket 文件权限的意义在于安全性,因为其它进程是需要通过 socket 文件来使用这个 UNIX domain socket 的,那么这个进程必须要有相应的权限才行;

  • 可以使用 read()/write()、send()/recv()、sendto()/recvfrom()、sendmsg()/recvmsg() 来发送和接收数据,与 Internet Socket 下的使用方法无大的差异;

  • read()/write()

    #include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
    ssize_t write(int fd, const void *buf, size_t count);
    
    • 这对函数是文件的读/写函数,因为 socket 返回的是标准的文件描述符,所以可以使用这两个函数进行接收和发送数据;
    • 这对函数通常仅用于面向连接的 socket,也就是通常不用于 SOCK_DGRAM 类型的 socket;
    • 这对函数是比较简单的,write() 基本不会出错;
    • 使用 read() 接收数据时可能会有阻塞问题,当使用 SOCK_STREAM 时,可能还有粘包问题,不过这些问题在 Internet Socket 的 TCP 编程中也是一样存在的,并不是 UNIX Socket 所特有的,如果有这方面的问题,请参考有关资料;
  • recv()/send()

    #include <sys/types.h>
    #include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    
    • 这对函数理论上既可以用于有连接的 socket(AF_STREAM),也可以用于无连接的 socket(AF_DGRAM),但通常仅用于面向连接的 socket,也就是通常不用于 SOCK_DGRAM 类型的 socket;
    • 与 read()/write() 相比,这对函数多了一个参数 flags
    • 在使用 send() 发送数据时,绝大多数情况下,flags 都可以设置为 0,最常用的 falgs 设置是 MSG_DONTWAIT,但对于向 UNIX Socket 发送数据而言,如果发送的数据不是很大,是不可能产生阻塞的,所以不需要设置 MSG_DONTWAIT;
    • 在使用 recv() 接收数据时,当 socket 上没有数据时,会产生阻塞,如果不希望阻塞,可以设置 flags 为 MSG_DONTWAIT,这样可以让程序有更好的适应性;
    • 在使用 recv() 接收数据时,与 Internet Socket 的 TCP 编程一样,可能出现"粘包"问题,请参考相关资料;
  • recvfrom()/sendto()

    #include <sys/types.h>
    #include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
    
    • 这对函数通常多用于面向无连接的 socket,也就是通常仅用于 SOCK_DGRAM 类型的 socket;

    • Internet Socket 的 UDP 编程中,需要使用 struct sockaddr_in 设置对端的 IP 地址和端口,而 UNIX Socket 要使用 struct sockaddr_un 来设置对端的文件路径;

    • 源程序:sendto-recvfrom.c(点击文件名下载源程序)演示了如何使用 sendto() 和 recvfrom() 进行面向报文的进程间通信;

    • 编译:gcc -Wall -g sendto-recvfrom.c -o sendto-recvfrom

    • 运行:./sendto-recvfrom

    • 运行截图:

      screenshot of running sendto-recvfrom


  • recvmsg()/sendmsg()
    #include <sys/types.h>
    #include <sys/socket.h>ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
    ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
    
    • 这对函数在 socket 上接收/发送数据相对较为复杂,主要是 struct msghdr 结构比较复杂,这对函数既可以用于有连接的 socket(AF_STREAM),也可以用于无连接的 socket(AF_DGRAM),在实践中也确实如此;
    struct msghdr {void         *msg_name;       /* Optional address */socklen_t     msg_namelen;    /* Size of address */struct iovec *msg_iov;        /* Scatter/gather array */size_t        msg_iovlen;     /* # elements in msg_iov */void         *msg_control;    /* Ancillary data, see below */size_t        msg_controllen; /* Ancillary data buffer len */int           msg_flags;      /* Flags (unused) */
    };struct iovec {void  *iov_base;    /* Starting address */size_t iov_len;     /* Number of bytes to transfer */
    };
    • 在多数的编程实践中,我们并不需要使用这对函数,使用 recv()/send()read()/write() 或者 sendto()/recvfrom() 已经足够;

    • struct msghdr 看似有很多字段,其实也并不是很复杂;

      • 最后一个字段 msg_flags 在 sendmsg() 中没有使用,在 recvmsg() 调用返回时将被设置为某个标志,可以不用管它;
      • msg_controlmsg_controllen 是用来传输控制信息的,如果我们不传输控制信息,这两个字段填 NULL 和 0 即可;
      • 最前面的两个字段 msg_namemag_namelen,在无连接的 socket(SOCK_DGRAM) 中有意义,在 sendmsg() 中用于指定目的地址,在 recvmsg() 中用于填充发送方的源地址,如果我们使用有连接的 socket(SOCK_STREAM),则这两个字段只需填 NULL 和 0;
      • msg_iov 是一个结构数组,而 msg_iovlen 为这个数组的元素个数,这是这个结构中的灵魂所在;
    • 关于这对函数的使用可以参考在线文档 man sendmsgman recvmsgman writev,本文并不打算详细描述这对函数的使用方法,但会给出一个具体实例;

    • 源程序:sendmsg-recvmsg.c(点击文件名下载源程序)演示了如何使用 sendmsg() 和 recvmsg() 进行面向报文的进程间通信;

    • 编译:gcc -Wall -g sendmsg-recvmsg.c -o sendmsg-recvmsg

    • 运行:./sendmsg-recvmsg

    • 运行截图:

      screenshot of running sendmsg-recvmsg


  • 实际上,这些在 socket 上发送/接收数据的函数并不需要成对使用,这个意思是说,我们可以使用 send() 发送数据,然后使用 recvmsg() 去接收 send() 发送的数据,没有任何问题。

  • 关闭 socket 和删除 socket 所关联的文件

    #include <unistd.h>close(unix_sock)
    unlink(UNIX_SOCK_FILE_NAME);
    

3 UNIX socket 的抽象命名空间

当我们使用 socket(AF_UNIX, ......) 建立一个 socket,并将其绑定到一个文件路径上(比如:/tmp/unix_sock.sock)时,系统会在文件系统上建立一个真实的文件,当所有打开这个 socket 的进程均关闭 socket 后,这个真实的文件并不会因此而消失,必须显式地使用 unlink() 或者 remove() 删除这个文件,才能将这个文件删除,如果一个打开 socket 的进程异常退出,没有显式删除 socket 相关联的文件,将导致在文件系统上留下一些没有用处的文件,我们把这种现象称为“文件系统污染”,显然,UNIX domain socket 会造成文件系统污染;

  • Linux 为 Unix Domain Socket 提供一种特殊的寻址方案,即所谓的"抽象命名空间(Abstract Namespace)",它可以避免使用 UNIX domain socket 时造成文件系统污染;

  • 使用抽象命名空间的 socket,在建立连接时可以不用在文件系统上建立一个真实文件,当所有打开这个 socket 的进程终止后,这个在抽象命名空间的 socket 也会随之消失;

  • 使用抽象命名空间建立 socket,不必再显式地删除其绑定的文件路径,即使打开该 socket 的进程是异常退出,这个 socket 也会随着所有进程的终止而消失;

  • 显式地删除一个 socket 关联的文件路径还会带来一些其它不可预知的问题,比如有可能删除一个其它进程正在使用的文件等等;

  • 抽象命名空间最大的问题是其代码的可移植性并不好,所以在实际应用中很少见到相应的代码;

  • 抽象命名空间的另一个问题是其安全性,在文件系统上建立的 socket 文件可以使用文件权限来控制其读写权限,相对较为安全,抽象命名空间没有权限,任何知道其名称的进程均可以使用该 socket,其安全性不好;

  • 实际上,在抽象命名空间中建立一个 socket 与在普通用户空间上建立一个 socket 的区别并不大,在设定地址时,都是使用 struct sockaddr_un,但使用抽象命名空间时 sun_path 字段的第一个字符必须是 '\0',下面代码片段演示了如何将一个 socket 绑定到抽象命名空间上:

    ......int sock_server = -1;
    int rc;
    socklen_t length;
    struct sockaddr_un server_addr;sock_server = socket(AF_UNIX, SOCK_STREAM, 0);
    memset(&server_addr, 0, sizeof(struct sockaddr_un));
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, "#unix_sock.sock");
    length = SUN_LEN(&server_addr);
    server_addr.sun_path[0] = '\0';bind(sock_server, (struct sockaddr *)&server_addr, length);......
    
  • 这段代码将字符串 #unix_sock.sock 拷贝到 sun_path 字段,其中的 '#' 是一个占位字符,在后面的代码中,'#' 被替换为 '\0'

  • 要注意的是,一定要在替换 '#' 前使用 SUN_LEN() 宏去计算 server_addr 的长度,否则因为 '\0' 的替换将导致长度计算错误;

  • 源程序:abstract-socket.c(点击文件名下载源程序)演示了如何使用抽象命名空间 socket 进行进程间通信;

    • 该程序建立了两个进程,一个是服务端进程,另一个是客户端进程;
    • 服务端进程在抽象命名空间上建立了一个 socket 并侦听在该 socket 上;
    • 客户端进程连接到该 socket 上并发送消息,服务端进程收到消息并给客户端一个回应消息,然后两个进程分别退出;
    • 为了简化程序,突出抽象命名空间的使用,该程序的 server 进程没有处理多个客户端进程连接的情况;
    • 在 server 进程中,socket 绑定完抽象命名空间的 socket 名称并开始侦听该 socket 后,执行了 lsof 命令,可以很清楚第看到一个名为 @unix_socket.sock,类型为 STREAM 的对象被打开;
  • 编译:gcc -Wall -g abstract-socket.c -o abstract-socket

  • 运行:./abstract-socket

  • 运行截图:

    screenshot of running abstract-socket


4 顺序数据包(SOCK_SEQPACKET)

  • 通常情况下,基于连接的 socket(SOCK_STREAM) 被认为可以可靠地传输数据,而基于无连接的 socket(SOCK_DGRAM) 被认为是不可靠的,在传输数据中,有可能丢失数据;

  • 但是,使用 SOCK_STREAM 创建的 socket 是基于数据流的,数据报之间的边界是不清晰的,接收方收到的数据包可能被拆分或者合并,导致收到的数据包可能并不是一个完整的数据报或者其中还包含着下一个数据报的一部分,这就是常说的“粘包(Sticky Packet)”;

  • 使用 SOCK_DGRAM 创建的 socket 也是有明显优点的,它是基于数据报的,所以可以保证每次收到的数据是一个数据报,不会有类似“粘包”的问题;

  • 当然,我们希望有一种 socket,它是基于连接的,数据传输可靠,同时,数据报之间的边界清晰,不会产生“粘包”;

  • 顺序数据包(SOCK_SEQPACKET)正是这样一种 socket,它是基于连接的的可靠传输,同时保证有明确的报文边界,通常认为它是介于 SOCK_STREAM 和 SOCK_DGRAM 之间的一种连接形式;

  • 目前这种 socket 仅能在 UNIX domain socket(AF_UNIX) 上使用,在 Internet socket(AF_INET) 上不能使用,所以我们在通常的应用中看不到使用 SOCK_SEQPACKET 的例子;

  • 使用 SOCK_SEQPACKET 建立 socket 的编程方法与 SOCK_STREAM 非常类似,除了在建立 socket 时使用 SOCK_SEQPACKET 以外基本没有任何区别;

  • 以下是 ChatGPT 3.5 给出的 SOCK_SEQPACKET 的特性:

    1. 有界数据包传输SOCK_SEQPACKET 提供有界数据包传输,这意味着它可以保留数据包的边界。发送方在发送数据时定义了数据包的边界,接收方可以按照这个边界来接收和处理数据包。这对于需要确保消息边界的应用程序非常有用。
    2. 可靠性:与 SOCK_DGRAM 套接字类型不同,SOCK_SEQPACKET 提供可靠的数据传输。它使用面向连接的传输协议来确保数据的可靠性,即数据在发送和接收之间保持顺序,并且不会丢失或重复。
    3. 面向连接SOCK_SEQPACKET 是面向连接的套接字类型。在进行数据传输之前,需要先建立连接。连接的建立过程确保了通信双方之间的可靠通信,并提供了数据包传输的顺序保证。
    4. 双向通信SOCK_SEQPACKET 套接字类型支持双向通信,即可以在连接上进行双向的数据传输。发送方可以发送数据给接收方,接收方可以发送响应数据给发送方。
    5. 适用于可靠的流式服务:由于 SOCK_SEQPACKET 提供可靠的、有界的数据包传输,它适用于需要确保消息边界和可靠性的应用程序。它通常用于基于TCP的应用程序,如文件传输、视频流传输和实时通信应用。

    需要注意的是,与其他套接字类型相比,SOCK_SEQPACKET 套接字可能会引入一些性能开销,因为它需要维护消息边界和数据包的顺序。因此,在选择套接字类型时,应权衡应用程序的需求和性能要求。

  • 源程序:seqpacket.c(点击文件名下载源程序)演示了如何使用顺序数据包进行进程间通信,同时演示了 SOCK_STREAM 的“粘包”现象以及 SOCK_SEQPACKET 有清晰报文边界的特性;

    • 建立了三个进程,第一个是 SOCK_STREAM 类型 socket 的服务端进程,第二个是 SOCK_SEQPACKET 类型 socket 的服务端进程,第三个是客户端进程;
    • 客户端进程使用同样的程序分别向两个服务端进程发送了三条连续的报文;
    • 两个服务端进程除了 socket 类型不同外,其它程序完全一样;
    • SOCK_STREAM 服务端进程会一次性地收到客户端发来的三条报文,三条报文“粘”在一起;
    • SOCK_SEQPACKET 服务端每次只会收到一条完整报文,三条报文需要接收三次,报文边界清晰;
    • 为简单起见,服务端程序连续收到 5 个 EAGAIN(EWOULDBLOCK) 错误时认为客户端进程已经完成发送;
  • 编译:gcc -Wall -g seqpacket.c -o seqpacket

  • 运行:./seqpacket

  • 运行截图:

    screenshot of running seqpacket


5 匿名 UNIX Socket 的使用

  • 前面介绍的 UNIX domain socket 均是需要命名的,或者引用一个文件路径,或者在抽象命名空间中引用一个名称;

  • Linux 同样支持匿名的 UNIX domain socket,就像在文章《IPC之一:使用匿名管道进行父子进程间通信的例子》介绍的匿名管道一样,匿名 UNIX domain socket 也是仅能用于父子进程或拥有同一个父进程的子进程间进行通信;

  • 在使用匿名管道进行全双工通信时,需要建立两个管道,一个用于读,另一个用于写,匿名 UNIX domain socket 与之类似,也是需要建立一对 socket,一个用于读,另一个用于写,使用 socketpair() 建立一对 socket;

    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>int socketpair(int domain, int type, int protocol, int sv[2]);
    
    • 调用成功,该函数返回 0,sv 数组中存放这一对 socket,调用失败返回 -1,errno 中为错误代码;
    • 返回的一对 socket 是已经连接好的,这意味着无需再调用其它函数,可以直接在 sv 返回的 socket 上进行读/写操作;
    • socketpair() 目前只能用在 AF_UNIX 上,所以参数 domain 只能是 AF_UNIX;
    • type 可以是 SOCK_STREAM、SOCK_DGRAM 或者 SOCK_SEQPACKET;
    • protocol 通常填 0 即可;
    • sv 是一个整数数组的指针,用于在调用成功时返回一对 socket,调用失败时,sv 数组的内容不会被改动;
  • 使用 socketpair() 建立的 socket 对,与使用 socket() 建立的 socket,在编程上没有区别;

  • 因为是匿名的,所以只能通过继承的方式将 socket 对传递给子进程,所以只能用于父子进程间或拥有同一父进程的子进程之间进行通信;

  • 源程序:socketpair.c(点击文件名下载源程序)演示了如何 socketpair() 建立的 socket 对进行进程间通信;

    • 在父进程中建立一个 socket 对;
    • 建立了两个子进程,第一个是服务端进程,第二个是客户端进程,socket 对通过继承传到子进程;
    • 服务端进程接收客户端进程发送的消息
  • 编译:gcc -Wall -g socketpair.c -o socketpair

  • 运行:./socketpair

  • 运行截图:

    screenshot of running socketpair

欢迎订阅 『进程间通信专栏』


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

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

相关文章

摄影企业网站搭建的作用是什么

几乎每个成年人都有一部手机&#xff0c;在互联网信息时代&#xff0c;手机的作用不言而喻&#xff0c;拍照/摄像成为了不少人经常会做的事&#xff0c;拍一张美美的照片发到社交圈赢得赞声&#xff0c;或是为以后留下回忆或发给自己在意的人&#xff0c;但这只限于生活记叙类图…

即将来临的2024年,汽车战场再起波澜?

我们来简要概况一下11月主流车企的销量表现&#xff1a; 根据数据显示&#xff0c;11月吉利集团总销量29.32万辆&#xff0c;同比增长28%。这在当月国内主流车企中综合实力凌厉&#xff0c;可谓表现得体。而与吉利直接竞争的比亚迪&#xff0c;尽管数据未公布&#xff0c;但我们…

AI Native工程化:百度App AI互动技术实践

作者 | GodStart 导读 随着AI浪潮的兴起&#xff0c;越来越多的应用都在利用大模型重构业务形态&#xff0c;在设计和优化Prompt的过程中&#xff0c;我们发现整个Prompt测评和优化周期非常长&#xff0c;因此&#xff0c;我们提出了一种Prompt生成、评估与迭代的一体化解决方案…

向量投影:如何将一个向量投影到矩阵的行向量生成子空间?

向量投影&#xff1a;如何将一个向量投影到矩阵的行向量生成子空间&#xff1f; 前言 本问题是在学习Rosen梯度投影优化方法的时候遇到的问题&#xff0c;主要是对于正交投影矩阵(NT(NNT)-1N)的不理解&#xff0c;因此经过查阅资料&#xff0c;学习了关于向量投影的知识&…

高级人工智能之群体智能:蚁群算法

群体智能 鸟群&#xff1a; 鱼群&#xff1a; 1.基本介绍 蚁群算法&#xff08;Ant Colony Optimization, ACO&#xff09;是一种模拟自然界蚂蚁觅食行为的优化算法。它通常用于解决路径优化问题&#xff0c;如旅行商问题&#xff08;TSP&#xff09;。 蚁群算法的基本步骤…

【YOLOV8预测篇】使用Ultralytics YOLO进行检测、分割、姿态估计和分类实践

目录 一 安装Ultralytics 二 使用预训练的YOLOv8n检测模型 三 使用预训练的YOLOv8n-seg分割模型 四 使用预训练的YOLOv8n-pose姿态模型 五 使用预训练的YOLOv8n-cls分类模型 <

Altium Designer(AD24)新工程复用设计文件图文教程及视频演示

&#x1f3e1;《专栏目录》 目录 1&#xff0c;概述2&#xff0c;复用方法一视频演示2.1&#xff0c;创建工程2.2&#xff0c;复用设计文件 3&#xff0c;复用方法二视频演示4&#xff0c;总结 欢迎点击浏览更多高清视频演示 1&#xff0c;概述 本文简述使用AD软件复用设计文件…

1860_peakhold的喷油器

Grey 全部学习内容汇总&#xff1a; GitHub - GreyZhang/g_hardware_basic: You should learn some hardware design knowledge in case hardware engineer would ask you to prove your software is right when their hardware design is wrong! 1860_peak&hold的喷油器…

python实现bp神经网络对csv文件进行数据预测

参考资源&#xff1a; sklearn库 bp神经网络[从原理到代码一篇搞定]&#xff08;2&#xff09;_sklearn 神经网络-CSDN博客 十分钟上手sklearn&#xff1a;安装&#xff0c;获取数据&#xff0c;数据预处理 - 知乎 (zhihu.com) 一个实例讲解如何使用BP神经网络(附代码) - 知…

EMD、EEMD、FEEMD、CEEMD、CEEMDAN的区别、原理和Python实现(五)CEEMDAN

目录 前言 1 完全自适应噪声集合经验模态分解CEEMDAN介绍 1.1 CEEMDAN简介 1.2 CEEMDAN主要特点 2 CEEMDAN分解的步骤 3 基于Python的CEEMDAN实现 3.1 导入数据 3.2 CEEMDAN分解 3.3 信号分量的重构 3.4 信号分量的处理 3.5 CEEMDAN优缺点 往期精彩内容&#xff1a;…

Qt designer界面和所有组件功能的详细介绍(全!!!)

PyQt5和Qt designer的详细安装教程&#xff1a;https://blog.csdn.net/qq_43811536/article/details/135185233?spm1001.2014.3001.5501 目录 1. 界面介绍2. Widget Box 常用组件2.1 Layouts&#xff08;布局&#xff09;2.2 Spacers&#xff08;间隔器&#xff09;2.3 Item V…

【黑马甄选离线数仓day10_会员主题域开发_DWS和ADS层】

day10_会员主题域开发 会员主题_DWS和ADS层 DWS层开发 门店会员分类天表: 维度指标: 指标&#xff1a;新增注册会员数、累计注册会员数、新增消费会员数、累计消费会员数、新增复购会员数、累计复购会员数、活跃会员数、沉睡会员数、会员消费金额 维度: 时间维度&#xff08…

网络7层架构

网络 7 层架构 什么是OSI七层模型&#xff1f; OSI模型用于定义并理解数据从一台计算机转移到另一台计算机&#xff0c;在最基本的形式中&#xff0c;两台计算机通过网线和连接器相互连接&#xff0c;在网卡的帮助下共享数据&#xff0c;形成一个网络&#xff0c;但是一台计算…

8、SpringCloud高频面试题-版本1

1、SpringCloud组件有哪些 SpringCloud 是一系列框架的有序集合。它利用 SpringBoot 的开发便利性巧妙地简化了分布式系统基础设施的开发&#xff0c;如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等&#xff0c;都可以用 SpringBoot 的开发风格做到一键启…

湘潭大学-软件工程-选择判断题复习

说明 期末考试单选题和判断题占30分&#xff0c;单选20&#xff0c;判断10分 单选题 选错误的 B依靠松散组合的互联网大众是无法开发出高质量软件产品的 D、所有命名都应尽量使用缩写 C、采用团队的组织方式 D、软件需求一旦确定就不允许变化 以下哪一项是通过运行程序…

全方位掌握卷积神经网络:理解原理 优化实践应用

计算机视觉CV的发展 检测任务 分类与检索 超分辨率重构 医学任务 无人驾驶 整体网络架构 卷积层和激活函数&#xff08;ReLU&#xff09;的组合是网络的核心组成部分 激活函数(ReLU&#xff09; 引入非线性&#xff0c;增强网络的表达能力。 卷积层 负责特征提取 池化层…

MySQL——复合查询

目录 一.基本查询回顾 二. 多表查询 三.自连接 四.子查询 1.单行子查询 2.多行子查询 3.多列子查询 4.在from子句中使用子查询 5.合并查询 一.基本查询回顾 准备数据库&#xff1a; 查询工资高于500或岗位为MANAGER的雇员&#xff0c;同时还要满足他们的姓名首字母为…

.net core 生成jwt+swagger-通过 IHttpContextAccessor读取token信息

1.安装jwt相关包 <ItemGroup><PackageReference Include"Microsoft.AspNetCore.Authentication.JwtBearer" Version"6.0.25" /><PackageReference Include"Microsoft.IdentityModel.Tokens" Version"7.0.3" /><P…

一个简单的设置,就能摆脱iPad音量键随方向变的困扰

新款iPad Air 5的发布和iPhone SE 3的评审可能是苹果本月最大的新闻&#xff0c;但该公司也悄悄发布了一项功能&#xff0c;自2010年发布第一款以来&#xff0c;iPad用户一直在等待&#xff1a;音量按钮现在在横向模式下很有意义。让我们解释一下。 每台iPad侧面的音量按钮在人…