网卡与Linux网络结构(中)

news2024/9/29 23:26:08

1 socket数据结构

socket源码

socket结构体是操作系统网络编程接口的一个核心组件。在操作系统中,socket结构体定义了一个网络连接的抽象,包括文件描述符、地址信息、连接状态等。

socket结构体代码位于<include/linux/net.h>

struct socket {

socket_state state; // 套接字的当前状态

short type; // 套接字类型(如SOCK_STREAM、SOCK_DGRAM等)

unsigned long flags; // 套接字标志位

struct socket_wq *wq; // 等待队列,处理异步操作

struct file *file; // 相关文件结构体,文件系统的抽象

struct sock *sk; // 指向具体协议栈的结构体指针(如TCP、UDP等)

const struct proto_ops *ops; // 协议相关操作函数集合

......

};


另外 socket源码中,还引用了sock结构体,sock结构体是socket的具体实现部分,它包含了具体协议的状态信息、操作方法等。proto_ops结构体定义了套接字的操作函数,如socket、bind、listen、accept等。不同的协议(如TCP、UDP)有各自的proto_ops实现。

socket和sock有什么区别?

sock源码

<include/net/sock.h>

sock结构体的源码在200行不到,struct sock&nbsp;是 Linux 内核中网络套接字的核心数据结构,包含了大量用于管理和操作网络连接的字段和函数指针。这些字段涵盖了从接收和发送数据包到状态管理、回调函数、安全和 CGroup 管理等各个方面。这个结构体的定义非常复杂,但它提供了强大的功能以支持高效和灵活的网络通信。

struct sock {

&nbsp; &nbsp; struct sock_common &nbsp;__sk_common;

......

//套接字锁

&nbsp; &nbsp; socket_lock_t &nbsp; &nbsp; &nbsp; sk_lock;

//丢包计数

&nbsp; &nbsp; atomic_t &nbsp; &nbsp; &nbsp; &nbsp;sk_drops;

&nbsp; &nbsp; int &nbsp; &nbsp; &nbsp; &nbsp; sk_rcvlowat;

&nbsp; &nbsp; struct sk_buff_head sk_error_queue;

&nbsp; &nbsp; struct sk_buff &nbsp; &nbsp; &nbsp;*sk_rx_skb_cache;

&nbsp; &nbsp; struct sk_buff_head sk_receive_queue;//指向协议栈已经处理完、待进程接收的数据包队列

&nbsp; &nbsp; ......

};


结构体socket和sock都是用于标识连接且一一对应。两者的区别在于结构体socket是用于负责向上为用户提供接口,并且和文件系统关联;而sock是负责向下对接内核网络协议栈

e4b3ff9158fc9cbc6ac9c2f5a8a7c1ab.jpeg

2 建立连接

2.1 调用函数socket()创建socket

在连接之前,客户端和服务端都要调用函数socket()创建与结构体socket相关的资源。

创建socket的主要入口函数为sys_socket, 其定义在<net/socket.c>文件中。sys_socket调用了多个内部函数来完成套接字的创建。

sys_socket

从源码看,其中最重要的两个函数为sock_create()和sock_map_fd,前者主要负责创建结构体socket,后者主要负责创建文件描述符。

调用关系:socket -->__sys_socket

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)

{

&nbsp; &nbsp; return __sys_socket(family, type, protocol);

}

int __sys_socket(int family, int type, int protocol)

{

&nbsp; &nbsp; int retval;

&nbsp; &nbsp; struct socket *sock;

&nbsp; &nbsp; int flags;

......

&nbsp; &nbsp; retval = sock_create(family, type, protocol, &sock);

&nbsp; &nbsp; if (retval < 0)

&nbsp; &nbsp; &nbsp; &nbsp; return retval;

&nbsp; &nbsp; return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));

}


sock_create

sock_create函数负责实际的套接字创建

int __sock_create(struct net *net, int family, int type, int protocol,

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;struct socket **res, int kern)

{

&nbsp; &nbsp; int err;

&nbsp; &nbsp; struct socket *sock;

......

//sock_alloc()&nbsp;函数用于分配并初始化一个新的&nbsp;socket&nbsp;结构体。

//如果分配失败,返回&nbsp;-ENFILE&nbsp;错误码,并打印警告信息。

&nbsp; &nbsp; sock = sock_alloc();

&nbsp; &nbsp; if (!sock) {

&nbsp; &nbsp; &nbsp; &nbsp; net_warn_ratelimited("socket: no more sockets\n");

&nbsp; &nbsp; &nbsp; &nbsp; return -ENFILE;

......

*res = sock;

return 0;

......

&nbsp; &nbsp; }


sock_alloc()

sock_alloc函数负责在Linux内核中分配并初始化一个新的socket结构体。它通过创建一个伪文件系统的inode并将其转换为socket结构体来实现这一点。

sock_alloc函数通过以下步骤创建并初始化一个新的socket结构体:

调用new_inode_pseudo创建一个伪文件系统的inode。

初始化inode的字段,包括inode号、模式、用户ID、组ID和操作集合。

返回分配并初始化的socket结构体指针。

struct socket *sock_alloc(void)

{

&nbsp; &nbsp; struct inode *inode;

&nbsp; &nbsp; struct socket *sock;

&nbsp; &nbsp; //调用new_inode_pseudo函数创建一个伪文件系统的inode。

&nbsp; &nbsp; //这个inode不对应实际的磁盘文件,

&nbsp; &nbsp; //而是用于网络套接字的虚拟文件系统。

&nbsp; &nbsp; inode = new_inode_pseudo(sock_mnt->mnt_sb);

&nbsp; &nbsp; if (!inode)

&nbsp; &nbsp; &nbsp; &nbsp; return NULL;

&nbsp; &nbsp; //将inode转换为socket结构体

&nbsp; &nbsp; sock = SOCKET_I(inode);

&nbsp; &nbsp; //初始化inode的字段

&nbsp; &nbsp; //为inode分配一个唯一的inode号

&nbsp; &nbsp; inode->i_ino = get_next_ino();

&nbsp; &nbsp; //设置inode的模式,表示这是一个套接字,并设置读写权限。

&nbsp; &nbsp; inode->i_mode = S_IFSOCK | S_IRWXUGO;

&nbsp; &nbsp; //设置inode的用户ID为当前进程的文件系统用户ID

&nbsp; &nbsp; inode->i_uid = current_fsuid();

&nbsp; &nbsp; //设置inode的组ID为当前进程的文件系统组ID

&nbsp; &nbsp; inode->i_gid = current_fsgid();

&nbsp; &nbsp; //设置inode的操作集合,这里指向sockfs_inode_ops

&nbsp; &nbsp; //表示这是一个与套接字相关的inode操作集合

&nbsp; &nbsp; inode->i_op = &sockfs_inode_ops;

&nbsp; &nbsp; return sock;

}

EXPORT_SYMBOL(sock_alloc);


sock_map_fd()

sock_map_fd函数用于将一个socket结构体映射到一个文件描述符,并将其插入到当前进程的文件描述符表中

static int sock_map_fd(struct socket *sock, int flags)

{

&nbsp; &nbsp; struct file *newfile;

&nbsp; &nbsp; //get_unused_fd_flags(flags) 函数用于分配一个新的文件描述符。

&nbsp; &nbsp; //flags 参数指定文件描述符的标志。

&nbsp; &nbsp; int fd = get_unused_fd_flags(flags);

&nbsp; &nbsp; if (unlikely(fd < 0)) {

&nbsp; &nbsp; &nbsp; &nbsp; sock_release(sock);

&nbsp; &nbsp; &nbsp; &nbsp; return fd;

&nbsp; &nbsp; }

&nbsp; &nbsp; //创建一个与 socket 结构体关联的文件结构体。

&nbsp; &nbsp; newfile = sock_alloc_file(sock, flags, NULL);

&nbsp; &nbsp; if (!IS_ERR(newfile)) {

&nbsp; &nbsp; &nbsp; &nbsp; fd_install(fd, newfile);

&nbsp; &nbsp; &nbsp; &nbsp; return fd;

&nbsp; &nbsp; }

&nbsp; &nbsp; //如果sock_alloc_file返回的是错误指针

&nbsp; &nbsp; //调用put_unused_fd(fd)释放之前分配的文件描述符。

&nbsp; &nbsp; put_unused_fd(fd);

&nbsp; &nbsp; //将错误指针转换为错误码并返回

&nbsp; &nbsp; return PTR_ERR(newfile);

}


2.2 调用函数listen()进入监听状态

主要入口函数为sys_listen(), 其定义在<net/socket.c>文件中

SYSCALL_DEFINE2(listen, int, fd, int, backlog)

{

&nbsp; &nbsp; return __sys_listen(fd, backlog);

}


通过syscall系统调用,从sys_listen调用__sys_listen函数,并传入用户空间带入的backlog参数

该函数的主要功能是将一个已绑定的socket&nbsp;设置为监听状态,以便接受传入的连接请求。代码中包含了查找socket、检查并设置backlog、进行安全性检查、设置监听状态等步骤。

int __sys_listen(int fd, int backlog)

{

struct socket *sock; // 定义一个 socket 结构指针

int err, fput_needed; // 定义两个整数变量 err 和 fput_needed

int somaxconn; // 定义一个整数变量 somaxconn

// 查找文件描述符 fd 对应的 socket 结构

sock = sockfd_lookup_light(fd, &err, &fput_needed);

if (sock) { // 如果成功找到对应的 socket

// 读取系统参数 sysctl_somaxconn,表示系统允许的最大连接数

somaxconn = READ_ONCE(sock_net(sock->sk)->core.sysctl_somaxconn);

// 如果传入的 backlog 大于系统允许的最大连接数,则将其设为系统允许的最大连接数

if ((unsigned int)backlog > somaxconn)

backlog = somaxconn;

// 执行安全性检查,判断是否允许监听

err = security_socket_listen(sock, backlog);

// 如果安全性检查通过

if (!err)

// 调用 socket 操作的 listen 方法,设置 socket 为监听状态

err = sock->ops->listen(sock, backlog);

// 减少 socket 的引用计数,释放资源

fput_light(sock->file, fput_needed);

}

// 返回错误码或成功码

return err;

}


代码中涉及到的somaxconn值,通过代码内的静态变量定义,位于<include/linux/socket.h>。somaxconn代表了TCP连接中的全队列(即accept队列)中的数量,如果存在一些特定的应用,比如瞬间有1-2w个短连接访问请求过来,进程如果来不及相应,就会存在全连接队列里面。而此时,这个值就显得非常重要了。(原理上在全连接队列里面的连接应该很快就会被进程取走消费)

/* Maximum queue length specifiable by listen. &nbsp;*/

#define SOMAXCONN &nbsp; 4096


在5.4内核版本后为4096,之前则为128。在配置文件的描述如下,可以通过在/etc/sysctl.conf中配置tcp_max_syn_backlog来实现

somaxconn - INTEGER

&nbsp; &nbsp; Limit of socket listen() backlog, known in userspace as SOMAXCONN.

&nbsp; &nbsp; Defaults to 4096. (Was 128 before linux-5.4)

&nbsp; &nbsp; See also tcp_max_syn_backlog for additional tuning for TCP sockets.


2.3 调用函数accept()等待与客户端连接

服务端会调用accept函数等待与客户端建立连接,期间主要完成的内容有:

1)创建新的资源服务和新的连接。主要通过__sys_accept4 --> __sys_accept4_file --> do_accept实现。

int __sys_accept4_file(struct file *file, unsigned file_flags,

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;struct sockaddr __user *upeer_sockaddr,

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;int __user *upeer_addrlen, int flags,

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;unsigned long nofile)

{


//为新连接分配新的文件描述符

newfd = __get_unused_fd_flags(flags, nofile);

//为新链接分配新的结构体socket和file

&nbsp; &nbsp; newfile = do_accept(file, file_flags, upeer_sockaddr, upeer_addrlen,

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flags);

struct file *do_accept(struct file *file, unsigned file_flags,

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;struct sockaddr __user *upeer_sockaddr,

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;int __user *upeer_addrlen, int flags)

{

&nbsp; &nbsp; struct socket *sock, *newsock;

&nbsp; &nbsp; struct file *newfile;

//为新连接分配新的结构体socket

newsock = sock_alloc();

//为新连接分配新的结构体file

&nbsp; &nbsp; newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);


2)检查全连接队列中是否存在已经建立连接的socket,如果存在,就将其移除队列发送给应用进行处理。如果不存在,则调用函数inet_csk_wait_for_connect()让进程进入阻塞状态,等待与客户端建立新的连接。

struct sock *inet_csk_accept(struct sock *sk, int flags, int *err, bool kern)

{

&nbsp; &nbsp; //判断当前队列是否有socket

//如果没有,则调用inet_csk_wait_for_connect阻塞等待,

&nbsp; &nbsp; if (reqsk_queue_empty(queue)) {

&nbsp; &nbsp; &nbsp; &nbsp; error = inet_csk_wait_for_connect(sk, timeo);

&nbsp; &nbsp; }

//将可用的sokcet从accept队列(全连接队列))中出队)

&nbsp; &nbsp; req = reqsk_queue_remove(queue, sk);

...

}


从函数accept()返回后,就意味着服务端与客户端已经经历了三次握手并建立了新连接。

整体的三个函数调用关系可参照下图。

afba7ea6cfc6e7969f9c89937fe42f91.jpeg

2.4 三次握手

在建立连接过程中,涉及到TCP三次握手,具体内容可参见

https://mp.weixin.qq.com/s?__biz=MzU0MjYxMjIxMg==&mid=2247484912&idx=1&sn=535ff01c6e4247df20da5fbc8ca8c6d3&chksm=fb194a6bcc6ec37de995f1cffb99ad63d16b7cd99efb335147f83fae22b9d0d58532c2a43c11#rd

3 数据传输

进程间通过socket进行通信,涉及到阻塞IO、非阻塞IO。进程在使用socket接收数据的时候,需要从内核空间缓冲区将数据复制到进程的用户空间。由于进程自身无法知道数据何时被接收,因此当进程尝试读取数据时可能会面临内核缓冲区无数据的情况。对此,socket提供了阻塞和非阻塞两种IO模型。

3.1 阻塞IO

阻塞 I/O(Blocking I/O)是指在进程在进行数据读写操作时,进程会被挂起,直到操作完成为止。换句话说,当一个进程发起网络 I/O 操作(如读取数据)时,如果数据未准备好,进程会被阻塞,直到数据可用或者操作完成为止。这种模式下,进程无法执行其他任务,直到当前 I/O 操作完成。一般使用传统的编程模型,例如使用 read() 和 write() 系统调用,这些调用会阻塞直到数据准备好或者操作完成。此外实现起来相对简单,因为操作系统会自动处理阻塞和唤醒。但是缺点也非常明显,因为进程必须等待 I/O 操作完成才能继续执行,整个过程是同步,当有多个 I/O 操作时,就会导致性能瓶颈,因为每个操作都可能导致进程挂起,浪费了 CPU 时间的。

具体过程可参见下图。

2fc77962e3f998bd1d72b3e237480a52.jpeg

3.2 非阻塞IO

非阻塞IO(non-Blocking I/O)是指进程在读取内核数据时,如果数据未就绪,进程不会挂起,而是立即返回一个状态码或错误码,表示操作无法完成。进程可以继续执行其他任务,随后可以再次尝试进行 I/O 操作,如此循环往复,直到进程读取到所需的数据为止。进程的这种尝试循环读取缓冲区的做法叫做轮询(polling)。轮询的有点在于进程不会因为某个IO对应数据未就绪而被阻塞,因此可以同时处理多个IO。但是带来的缺点就是会浪费大量的CPU进行周期性查询开销。

具体过程可参加下图:

c966c09b40cf1a835c44f13576d02a20.jpeg

函数recvfrom是socket用来接收数据的函数,其核心函数是__skb_recv_udp()。

函数__skb_recv_udp首先调用sock_rcvtimeo来获取超时时间并赋值给timeo。 timeo的值用于判断阻塞和非阻塞IO。

如果timeo的值为0,则表示当前连接是非阻塞的,那么只运行一次__skb_try_recv_from_queue()尝试从接收队列获取数据,随后退出do-while循环。

如果timeo的值不为0,则表示当前连接是阻塞的,在阻塞期间,当内核接收到此连接上的数据包后会唤醒进程,随后进程再继续尝试获取数据,最后在接收到数据或者超时后再返回。

struct sk_buff *__skb_recv_udp(struct sock *sk, unsigned int flags,

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;int noblock, int *off, int *err)

{

&nbsp; &nbsp; //获取超时时间timeo, 如果timeo=0,则当前连接是非阻塞的

&nbsp; &nbsp; //如果不为0,则当前连接是阻塞的,阻塞时间为timeo

&nbsp; &nbsp; timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);

&nbsp; &nbsp; &nbsp; &nbsp; do {

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; spin_lock_bh(&queue->lock);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //从接收队列中获取数据包

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; skb = __skb_try_recv_from_queue(sk, queue, flags, off, err, &last);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //如果接收到数据包,则直接返回。

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (skb) {

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!(flags & MSG_PEEK))

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; udp_skb_destructor(sk, skb);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; spin_unlock_bh(&queue->lock);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return skb;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

//如果当前连接是阻塞的,则会循环等待timeo的事件接收数据包

&nbsp; &nbsp; } while (timeo &&&nbsp;!__skb_wait_for_more_packets());

}


3.3 IO多路复用

当前的阻塞和非阻塞模型在处理IO的时候都有明显的缺点。阻塞模型一次只能处理单个IO,而非阻塞模型会浪费CPU周期。由于服务端进程通常同时与多个客户端进程维持不同的连接,为了克服上述两种IO模型的缺点,服务端可以采用IO多路复用的模型来高效的管理多个连接。

IO复用(Input/Output Multiplexing)是一种在计算机系统中处理多个IO操作的技术。这种技术使得程序可以在一个线程或进程中同时处理多个输入输出操作,提高了效率和响应能力。

在传统的编程中,每当程序需要执行IO操作时,比如读写文件或网络通信,程序通常会阻塞,直到操作完成。这种方式在处理大量IO操作时可能效率很低,因为线程会一直等待IO操作完成,无法处理其他任务。

IO复用的核心思想是利用系统提供的机制,让程序可以在等待IO操作完成的同时,去处理其他任务。这样可以显著提高程序的并发性能,减少等待时间。常见的IO复用机制包括:select、poll、epoll

3.3.1 select

select是一个传统的IO复用方法,它允许程序监控多个文件描述符(如网络连接)的状态变化。程序可以等待这些描述符中的一个或多个变为可读、可写或出现异常。适用于处理较小数量的文件描述符。然而,在高并发情况下,其性能和可扩展性可能会受到限制,随着文件描述符数量的增加,select&nbsp;的性能会下降,当某个连接的数据就绪时,操作系统可能要遍历扫描所有的socket描述符,才能找到具体就绪的socket描述符,从而带来比较高的开销。此外select支持的socket描述符集数量的上限通过FD_SETSIZE控制,一般为1024。

select实现细节:

1)文件描述符集合转换:将用户空间传入的 fd_set 结构转换成内核中的数据结构,如 poll_table.

2)注册监听事件:注册文件描述符的事件到内核的事件表中,可能会使用 poll 或类似机制来监控文件描述符的状态变化。

3)等待事件:如果没有事件发生,内核会等待,直到超时或有事件发生。

4)更新用户空间数据:事件发生后,将状态更新到用户空间的 fd_set 结构中,然后返回。

3.3.2 poll

poll 是一种改进的 IO 多路复用技术,相比于 select 提供了更高的灵活性和更好的扩展性,poll 允许监控任意数量的文件描述符,不再受 select 的文件描述符数量限制。poll 没有类似 FD_SETSIZE 的固定限制,适合处理大量文件描述符。oll 使用 pollfd 结构体数组,避免了 select 中的位图操作,更易于处理和扩展。尽管 poll 可以处理更多的文件描述符,但它仍然需要线性扫描整个 pollfd 数组,这在大量文件描述符的情况下可能会导致性能下降。

poll的实现大致分为五个环节

1)复制数据结构:用户空间的 pollfd 数组被复制到内核空间。这通常涉及内核的 copy_from_user 函数。

2)事件检测:内核检查每个 pollfd 结构体中的文件描述符,判断其状态是否满足 events 字段中的要求。内核使用不同的机制来监控文件描述符的状态,可能包括轮询、事件驱动等。

3)等待事件:如果没有事件发生,内核会将进程挂起,直到超时或文件描述符状态发生变化。内核会将进程添加到等待队列中,并在事件发生时唤醒进程。

4)更新结果:当事件发生时,内核会更新 pollfd 数组中的 revents 字段,指示哪些事件已经发生。

5)返回用户空间:内核将更新后的 pollfd 数组返回给用户空间,并通知用户哪些文件描述符已经准备好进行 IO 操作。

3.3.3 epoll

epoll 是一种高效的 IO 多路复用技术,特别适用于处理大量并发连接的场景。它通过使用事件通知机制和高效的数据结构来提高性能,克服了 select 和 poll 在处理大规模文件描述符时的性能瓶颈。虽然 epoll 的复杂性较高,但其高效的性能和扩展性使其成为处理高并发应用程序的首选技术。

epoll的实现大致分为四个环节

epoll 的高效性主要来源于内核中的数据结构和事件通知机制,涉及到事件表和就绪列表:

事件表:内核维护一个事件表,用于记录所有注册的文件描述符及其感兴趣的事件。这个表是一个红黑树或哈希表,以支持高效的查找和更新操作。

就绪列表:当文件描述符的状态发生变化时,内核将其添加到就绪列表中。epoll_wait 函数会从这个列表中获取文件描述符的状态信息。

3.3.4 对比分析

select&nbsp;适用于文件描述符数量较少的简单场景。

poll&nbsp;改进了&nbsp;select&nbsp;的文件描述符限制,但在大规模场景下仍有性能瓶颈。

epoll&nbsp;适合处理大规模的并发连接,提供更高效的事件通知机制和灵活的事件触发模式,适用于高性能要求的应用程序。


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

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

相关文章

android13 串口编号修改 串口名修改

总纲 android13 rom 开发总纲说明 目录 1.前言 2.技术分析 别名定义的语法规则 3.修改示例 使用别名 注意事项 4.不生效分析 5.编译查看 6.其他方法 7.彩蛋 1.前言 更改Android设备的串口编号涉及对系统深层次的配置进行修改,通常是为了解决硬件兼容性问题或满足特…

MySQL:约束

目录 1、约束 1.1 约束类型 2、非空约束 2.1 没有指定not null 2.2 指定not null 3、唯一约束 3.1 没有指定unique 3.2 指定unique 4、默认约束 5、主键约束 5.1 主键 5.2 主键自增类型 5.2.1 主键自增全列插入 5.2.2 主键自增指定列插入 5.3 复合主键 6、外键约…

探秘充电桩测试负载箱

随着电动汽车的普及&#xff0c;充电桩已经成为了城市中不可或缺的一部分。为了保证充电桩的质量和安全性&#xff0c;对其进行严格的测试是必不可少的。而在这个过程中&#xff0c;充电桩测试负载箱扮演着至关重要的角色。那么&#xff0c;什么是充电桩测试负载箱&#xff1f;…

【问题记录】Python中用yield实现打字机效果

用yield实现打字机效果 前言示例注意点 前言 在人工智能&#xff08;AI&#xff09;领域&#xff0c;yield 函数在处理大数据集和实现异步编程时非常重要。 异步编程是一种常见的编程模式&#xff0c;它可以提高程序的执行效率。yield 可以用于实现协程&#xff0c;使得程序在…

未来已来:人工智能如何重塑Facebook的用户体验?

在数字化时代的浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;正成为推动技术进步和用户体验优化的核心力量。Facebook&#xff08;现Meta Platforms&#xff09;作为全球领先的社交媒体平台&#xff0c;正在充分利用人工智能技术&#xff0c;以重塑用户体验&#xff0…

文档翻译免费软件有哪些?不容错过的5个翻译利器

大学生暑期生活日常都丰富多彩&#xff0c;充满了挑战与收获。 他们不仅沉浸在外语学习的海洋中&#xff0c;努力提升语言能力&#xff0c;还深入钻研各类论文文献&#xff0c;积极探索学术的无限可能。然而&#xff0c;在这个过程中&#xff0c;不同语言的资料往往成为他们求…

spring boot3.x快速入门

下一篇&#xff1a;Spring Boot 3.x gradle脚手架工程build.gradle详解 本教程将基于gradle项目构建工具来快速构建一个spring boot 3.x的最简单的web应用&#xff0c;其中涉及各种构建技巧和细节&#xff0c;希望能帮到初学者~ 文章目录 先决条件JDK17gradle全局配置 gradle项…

RAPTOR模型在长文档知识检索中的应用

人工智能咨询培训老师叶梓 转载标明出处 传统的检索增强型语言模型&#xff08;RALMs&#xff09;在检索时往往只关注短且连续的文本块&#xff0c;这限制了它们对整体文档上下文的全面理解。例如&#xff0c;在需要整合文本多个部分知识的主题性问题上&#xff0c;如理解整本书…

哈佛大学单细胞课程|笔记汇总 (六)

哈佛大学单细胞课程|笔记汇总 &#xff08;五&#xff09; 哈佛大学单细胞课程|笔记汇总 &#xff08;四&#xff09; &#xff08;六&#xff09;Single-cell RNA-seq clustering analysis: aligning cells across conditions 我们的数据集包含来自两个不同条件&#xff08…

Java JDK 国内外下载镜像地址及安装

Java JDK 国内下载镜像地址及安装 一、Java JDK 国内下载镜像地址及安装二、国外快速下载jdk 一、Java JDK 国内下载镜像地址及安装 各种JAVA JDK的镜像分发&#xff1a;https://www.injdk.cn/ 华为oracle jdk镜像&#xff1a;https://repo.huaweicloud.com/java/jdk/ 华为op…

python:VOC格式数据集转换为YOLO数据集格式

作者&#xff1a;CSDN _养乐多_ 本文将介绍如何将目标检测中常用的VOC格式数据集转换为YOLO数据集&#xff0c;并进行数据集比例划分&#xff0c;从而方便的进行YOLO目标检测。 如果不想分两步&#xff0c;可以直接看第三节代码。 文章目录 一、将VOC格式数据集转换为YOLO格…

司美格鲁肽,又名索玛鲁肽;Semaglutide;CAS号:910463-68-2

司美格鲁肽&#xff0c;又名索玛鲁肽&#xff1b;Semaglutide&#xff1b; CAS号&#xff1a;910463-68-2 分子量&#xff1a;4113.58 结构图&#xff1a; 司美格鲁肽&#xff0c;又名Semaglutide (上海楚肽生物科技有限提供&#xff09; 分子式&#xff1a;C187H291N45O59 …

nginx和php工具的使用

一、本地主机通过域名访问自己写的网页 1、开启phpstudy 2、找到phpstudy目录下的www文件夹&#xff0c;创建less01文件夹、index.html、web.php文件&#xff0c;进行配置&#xff0c;如下图&#xff1a; 3、重启一下phpstudy&#xff0c;然后访问网页 4、上面只能通过文件目录…

MIMO系统中差分空间调制解调matlab误码率仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 空间调制原理 4.2 发送端模型 4.3 接收端模型 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2022a 3.部分核心程序 &#xff08;完…

打开法学著作的AI新方式:元典问达「知识+」正式上线!

号外&#xff01;号外&#xff01; 元典问达正式上新 「知识」 法律人现在可以在「知识」板块&#xff0c;直接与《刑法注释书》、《刑事诉讼法注释书》“对话”&#xff0c;通过提问&#xff0c;获得权威法学出版物总结而成的刑事法律解读和案例分析&#xff01; 信息爆炸时…

esp32使用数码管显示数字

前言&#xff1a;本文参考视频链接放在最后&#xff0c;同时本文为学习笔记&#xff0c;为了自己好复习。 #数码管概念 现在显示流行有 LCD OLED 但是数码管也能用来作为显示&#xff0c;相对于前两者&#xff0c;数码管稳定价格低廉&#xff0c;到现在也在被应用&#xff0…

UE4材质基础---方形渐变、中心渐变

1、方形渐变 效果图&#xff1a; 代码&#xff1a; 解析&#xff1a;TexCoord的纹理坐标的R通道&#xff08;0&#xff0c;0.5&#xff0c;1&#xff09;减0.5> &#xff08;-0.5&#xff0c;0&#xff0c;0.5&#xff09;取abs> (0.5&#xff0c;0&#xff0c;0.5)乘…

b站ip地址怎么改到别的城市去

对于B站的忠实用户而言&#xff0c;每当在个人主页、发布动态或评论等&#xff0c;都会注意到IP属地的显示&#xff0c;但在某些情况下&#xff0c;出于对个人隐私的保护、跨越地域限制或其他个性化需求&#xff0c;我们可能会想要改变B站显示的IP地址到另一个城市。那么&#…

有没有值得推荐的加密软件

1. 金刚钻信息数据防泄密系统 特点&#xff1a;专为企业设计&#xff0c;提供全面的信息安全管理功能&#xff0c;支持透明加密技术&#xff0c;确保敏感文件在创建、编辑和保存过程中自动加密&#xff0c;不影响正常办公流程。 功能&#xff1a;提供文档权限管理、数据备份与…