Linux网络:TCP UDP socket

news/2024/9/20 7:08:46/ 标签: 网络, linux, tcp/ip

Linux网络:TCP & UDP socket

    • socket 套接字
      • sockaddr
      • 网络字节序
      • IP地址转换
      • bzero
    • UDP socket
      • socket
      • bind
      • recvfrom
      • sendto
    • TCP socket
      • socket
      • bind
      • listen
      • connect
      • accept
      • send
      • recv


本博客讲解 Linux 下的 TCP 和 UDP 套接字编程。无论是创建套接字、绑定地址,还是发送和接收数据,都会详细讲解。希望这篇博客能帮你更轻松地理解这些概念,并在实践中得心应手。

socket 套接字

套接字是网络通信的基础,它提供了一种标准化的方式,使得程序能够通过网络发送和接收数据。

套接字的种类非常多样,比如:

  • unix socket域套接字:用于本地通信
  • inet socket网络套接字:用于网络通信
  • raw socket原始套接字:用于网络管理

sockaddr

sockaddr 是一个在网络编程中用于表示“套接字地址”的通用结构体。它的作用是存储网络地址信息,供套接字函数使用,此时套接字函数就知道要对哪一台主机进行网络操作。sockaddr包含在头文件<arpa/inet.h>中。

sockaddr 结构体不能直接存储 IPv4 或 IPv6 的地址信息,在实际使用中,通常会用到它的具体子类型,如 sockaddr_in(用于 IPv4)和 sockaddr_in6(用于 IPv6),sockaddr_un(用于域套接)。

如图:

在这里插入图片描述

为了管理多种套接字,所有套接字的头部都是一个16位的地址类型,用于辨别这个结构体表示哪一个套接字。当操作sockaddr的时候,读取前16位就知道这个sockaddr具体是哪一种套接字。随后再进行类型转化,变成对应套接字类型的结构体,此时就能对具体的套接字做操作了。

sockaddr的定义如下:

struct sockaddr {unsigned short sa_family;   char sa_data[14];           
};
  • sa_family:表示协议族,例如 IPv4 使用 AF_INET,IPv6 使用 AF_INET6,域套接使用AF_UNIX

其中最常用的就是AF_INET进行IPv4通信。其对应的具体结构体为struct sockaddr_in,定义如下:

struct sockaddr_in {sa_family_t		sin_family;__be16		sin_port;struct in_addr	sin_addr;unsigned char		__pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)];
};
  • sin_family:前16位,表示套接字类型
  • sin_port:表示端口号
  • sin_addr:表示IPv4地址

此处有一个小细节,sin_addr的类型是struct in_addr,按理来说IPv4的地址占32位,用一个int类型即可存储,这里的结构体又是啥?

其实是Linux对其进行了额外的一层封装:

struct in_addr {__be32	s_addr;
};

此处的__be32就是一个32位的整型,也就是说存储地址的时候,要用sockaddr_in.sin_addr.s_addr,此处嵌套了两层结构体

基于IP地址和端口号,此时就可以定位到全世界的一个主机上的一个具体进程,此时就可以进行后续的网络通信了!


网络字节序

在不同主机内存中,字节数据分为大端字节序和小端字节序,假设一个大端主机和一个小端主机进行通信,此时就会发生错误,因为两别解析数据的方式不同,于是网络字节序出现了。

TCP/IP 规定,大端字节序为网络字节序,在网络中通信必须使用网络字节序

也就是说,如果当前主机是大端主机,那么收发数据时不做处理。如果是小端主机,那么收发数据时要把数据转化为小端字节序。

在此处讲解这个问题,就是因为在填写sockaddr_in内部的IP和端口号时,内部数据的字节序要使用网络字节序

假设我们现在要往sockaddr_in 内部填入端口号22:

struct sockaddr_in sock;
sock.sin_port = 22;

以上就是一个错误示例,因为不清楚代码的运行环境是大端还是小端,此时存入的数据22就有可能不是网络字节序,所以要先将22转为网络字节序,Linux为此提供了专门的接口。

以下接口用于序列转化,需要头文件<arpa/inet.,h>

uint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort);

上面接口的用法非常好记,比如htonl拆解为host to net long,也就是将long类型从主机的字节序转化为网络字节序。同理htns就是将short类型从主机的字节序转化为网络字节序。

反过来,ntohl就是net to host long,将long类型从网络字节序转化为主机的字节序。

如果主机是大端,那么这些函数对数据不做任何处理。如果主机是小端,那么就会进行大小端的转化。

由于sin_port 的类型是int16,所以在写入端口前要用htons进行转化:

struct sockaddr_in sock;
sock.sin_port = htons(22);

IP地址转换

如果想要给一个sockaddr_in结构体填入数据,那么第一个问题就是IP地址的格式问题。

IP地址有两种基本格式,4字节序列,以及点分十进制,如果拿到的IP地址格式与自己所需的类型不符,此时就要考虑两种格式之间转化的问题了。但是不必担心,这个问题也有对应的接口解决。

以下函数需要头文件:<sys/socket.h><netinet/in.h><arpa/inet.h>

inet_addr用于将点分十进制转化为四字节序列:

in_addr_t inet_addr(const char *cp);
  • cp:指向点分十进制IP地址字符串的指针

如果转化错误,返回INADDR_NONE,本质上是数字-1

示例:

struct sockaddr_in sock;
sock.sin_addr.s_addr = inet_addr("127.0.0.1");

此处要注意,实际存储IP序列的是.sin_addr.s_addr,这里有两层结构体嵌套。

那么有人就有疑问了,都说了存入sockaddr_in 中的数据必须是网络字节序,此处将点分十进制转化为四字节序列后,是不是要再转化为网络字节序?

比如这样:

struct sockaddr_in sock;
sock.sin_addr.s_addr = htons(inet_addr("127.0.0.1"));

其实不用,inet_addr会完成两个任务:

  1. 将点分十进制转化为四字节序列
  2. 将四字节序列转化为网络字节序

也就是说inet_addr内部已经顺带完成了转化网络字节序的工作

inet_ntoa用于将四字节序列转化为点分十进制:

char *inet_ntoa(struct in_addr in);

bzero

目前初始化一个sockaddr_in的代码如下:

struct sockaddr_in sock;
sock.sin_addr.s_addr = inet_addr("127.0.0.1");
sock.sin_port = htons(22);

一个要点是IP地址格式的转化,另一个要点是网络字节序。

但是这样还不完整,还有一个成员sin_family没有初始化,对于IPv4通信,此处填入AF_INET,这个在之前已经说过。

struct sockaddr_in sock;
sock.sin_family = AF_INET;
sock.sin_addr.s_addr = inet_addr("127.0.0.1");
sock.sin_port = htons(22);

目前为止已经初始化了一个比较完整的sockaddr_in了。但是创建结构体时,分配到的内存原先有可能存储了其他数据,因为有填充部分我们不初始化,为了保证之前的数据不会影响,还要把整个结构体的内存全部置为0

bzero用于初始化一段内存为0,需要头文件<strings.h>,定义如下:

void bzero(void* s, size_t n);
  • s:要初始化内存的地址
  • n:要初始化的字节数

在初始化整个sockaddr_in 之前,先用bzero将内存清零:

struct sockaddr_in sock;
bzero(&sock, sizeof(sock));

现在我们就有一个比较完整的sockaddr_in 初始化过程了:

struct sockaddr_in sock;
bzero(&sock, sizeof(sock));
sock.sin_family = AF_INET;
sock.sin_addr.s_addr = inet_addr("127.0.0.1");
sock.sin_port = htons(22);

上述所有内容,都只是在初始化一个套接字所需的地址,还没有真正的创建套接字,接下来就了解操作真正的套接字的接口。


UDP socket

socket

socket函数用于创建一个新的套接字,需要头文件<sys/types.h><sys/socket.h>,函数原型如下:

int socket(int domain, int type, int protocol);

参数:

  • domain:指定协议族
  • type:指定套接字类型
  • protocol:通常设为 0,表示使用默认协议

返回值:

  • 成功:返回新创建的套接字的文件描述符
  • 失败:返回 -1

如果要创建UDP套接字,那么参数应该填为:

int fd = socket(AF_INET, SOCK_DGRAM, 0);
  • AF_INET:表示IPv4
  • SOCK_DGRAM:表示UDP套接字,DGRAMdatagram缩写,即数据报

socket的返回值是一个整型,本质是一个文件描述符,Linux一切皆文件,后续对网络的操作就是对这个文件的操作。比如向网络中发送消息,其实就是向文件中写入数据


bind

当创建完套接字后,这个套接字还没有指定和哪一个主机通信,此时就需要IP地址和端口号,之前讲的sockaddr_in就派上用场了!

bind函数用于给套接字绑定IP地址和端口号,指定和哪一台主机通信,需要头文件<sys/types.h><sys/socket.h>,函数原型如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:

  • sockfd:套接字对应的文件描述符
  • addr:指向sockaddr的指针
  • addrlensockaddr的真实类型的长度

返回值:

  • 成功:返回0
  • 失败:返回-1,并设置错误码

之前讲解的sockaddr_insockaddr的一种,此处注意传入的是struct sockaddr *,也就是说sockaddr_in类型的变量传入的时候要进行类型转化。

由于不知道struct sockaddr *具体指向哪一种套接字地址,所以第三个参数要传入真实类型结构体大小,防止越界访问。

示例:

// 1.创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);// 2.初始化套接字要通信的目标主机地址
struct sockaddr_in sock;
bzero(&sock, sizeof(sock));
sock.sin_family = AF_INET;
sock.sin_addr.s_addr = inet_addr("1.2.3.4");
sock.sin_port = htons(8888);// 3.绑定地址到套接字
bind(sockfd, (struct sockaddr*)&sock, sizeof(sock));

比如以上示例中,绑定的地址为1.2.3.4,端口为8888,这表明只有1.2.3.4可以通过端口8888和这个套接字与本主机进行通信

此处不代表1.2.3.4以外的地址不能与当前主机通信了,只是对于被绑定套接字而言。就好像一个学校有好几个大门,其中某个门叫做“校长专用通道”,那么只有校长可以通过这个门进入校园,其他学生不能通过这个门进入校园,但是其他学生也可以通过其它的门进入。


recvfrom

绑定好主机后,就可以开始进行网络通信了,UDP基于报文通信,所以发送的数据也是报文形式。

recvfrom函数用于接收数据报,需要头文件<sys.types.h><sys/socket.h>,函数原型如下:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

参数:

  • sockfd:套接字的文件描述符
  • buf:指向接收缓冲区
  • len:最多可以读取的字节数,一般防止接收缓冲区越界
  • flags:控制发送行为的标志,通常设为 0
  • src_addr:输出型参数,得到消息发送方的sockaddr,即IP地址和端口号
  • addrlen:输出型参数,得到src_addr 真实结构的大小

返回值:

  • 成功时返回实际读取的字节数
  • 失败时返回 -1,并设置错误码

示例:

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);struct sockaddr_in src_addr;
bzero(&src_addr, sizeof(src_addr));
src_addr.sin_family = AF_INET;
src_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
src_addr.sin_port = htons(8888);bind(sockfd, (struct sockaddr*)&src_addr, sizeof(src_addr));// 输出形参数
struct sockaddr_in peer;
socklen_t peer_len;// 接收消息
char* buf[1024];
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&peer, &peer_len);

此处的peer用于接收发送方的sockaddr,也就是IP地址和端口号。发送方发送的报文被存储在buf中。


sendto

sendto函数用于发送数据报,需要头文件<sys.types.h><sys/socket.h>,函数原型如下:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

参数:

  • sockfd:套接字的文件描述符
  • buf:指向要发送的数据缓冲区的指针
  • len:要发送的数据的字节数
  • flags:控制发送行为的标志,通常设为 0
  • dest_addr:指向消息接收方目标地址sockaddr的指针
  • addrlendest_addr 真实结构的大小。

返回值:

  • 成功时返回实际发送的字节数
  • 失败时返回 -1,并设置错误码
  • 返回0表示没有读取到数据

示例:

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);struct sockaddr_in dest_addr;
bzero(&dest_addr, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_addr.s_addr = inet_addr("192.168.1.100");
dest_addr.sin_port = htons(8888);const char *message = "Hello, UDP!";
sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));

此处给192.168.1.100地址,通过端口8888发送了一个报文,内容是”Hello, UDP!“

不知你有没有发现这一份代码有一点蹊跷,这个套接字没有bind。此处程序作为客户端向主机主动发起报文,那么操作系统会给这个进程分配一个端口。在还没有发送数据前,我们并不知道这个随机分配的端口是多少,无法进行bind,这该咋办?

在发送数据时,有两种情况:

  1. 如果套接字没有绑定端口号,Linux会自动为其分配端口号,并完成绑定,随后通过随机分配的端口发送数据,这种行为称为隐式绑定
  2. 如果套接字已经绑定了端口号,Linux则直接通过指定端口发送数据

因此以上代码中没有bind这个过程。


TCP socket

socket

与UDP一样,TCP也要通过socket函数创建套接字,只是参数略有不同。

int socket(int domain, int type, int protocol);
  • domain:对于IPv4,使用AF_INET
  • type:填入SOCK_STREAM,即面向字节流的TCP服务
  • protocol:填0即可

创建TCP套接字代码如下:

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

后续就可以通过文件描述符sockfd操作TCP连接了。


bind

同样的,TCP服务端要通过bind进行绑定,表明自己连接的主机以及端口号。而客户端不用,因为发送数据时操作系统会隐式绑定。


listen

当TCP服务端启动后,此时就要等待别人来连接自己,此时就处于listen状态。而进入该状态,需要调用函数listen,包含在头文件<sys/types.h><sys/socket.h>,函数原型如下:

int listen(int sockfd, int backlog);

参数:

  • sockfd:进行监听的套接字
  • backlog:TCP全连接队列的大小,此处暂时不管,设为一个适中的值,比如10

返回值:

  • 成功:返回0
  • 失败:返回-1

示例:

struct sockaddr_in sock;
bzero(&sock, sizeof(sock));
sock.sin_family = AF_INET;
sock.sin_addr.s_addr = inet_addr("0.0.0.0");
sock.sin_port = htons(8888);int sockfd = socket(AF_INET, SOCK_STREAM, 0);bind(sockfd, (struct sockaddr*)&sock, sizeof(sock));
listen(sockfd, 10);while (true);

假设该进程为test.exe,执行该程序后,通过netstat指令查看:

在这里插入图片描述

此时进程就处于LISTEN状态,即等待连接。


connect

对于客户端来说,需要向服务端发起连接请求,此时需要函数connect,包含在头文件<sys/types.h><sys/socket.h>中,函数原型如下:

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:

  • sockfd:发起连接的套接字
  • addr:发起连接对象的信息,即IP地址何端口号
  • addrlenaddr的真实类型的大小

返回值:

  • 成功:返回0
  • 失败:返回-1并设置错误码

以下是一个客户端的示例:

// 构建目标主机信息
struct sockaddr_in sock;
bzero(&sock, sizeof(sock));
sock.sin_family = AF_INET;
sock.sin_addr.s_addr = inet_addr("127.0.0.1");
sock.sin_port = htons(8888);// 发起TCP连接
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
connect(sockfd, (struct sockaddr*)&sock, sizeof(sock));

accept

当服务端在listen状态下,接收到来自客户端的连接,就可以选择同意这个连接,此时需要函数accept,包含在头文件<sys/types.h><sys/socket.h>中,函数原型如下:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:

  • sockfd:之前进行listen的套接字
  • sockaddr:输出客户端的信息,即IP地址和端口号
  • addrlensockaddr的真实大小

返回值:

  • 成功:返回一个文件描述符
  • 失败:返回-1并设置错误码

奇怪的事情来了,为什么accept会返回一个文件描述符?

此处的文件描述符,其实就是一个套接字socket,先前说过套接字是通过文件描述符来操作的。此处返回的套接字,是专门用于和客户端通信的套接字。也就是说后续与客户端通信,使用这个新的套接字完成。

对于TCP服务端来说,有两种套接字,一种是用于listen的套接字,其负责监听指定端口,查看有没有到来的连接。一旦连接建立成功,此时与客户端的通信过程由新的套接字完成。

示例:

struct sockaddr_in sock;
bzero(&sock, sizeof(sock));
sock.sin_family = AF_INET;
sock.sin_addr.s_addr = inet_addr("0.0.0.0");
sock.sin_port = htons(8888);int listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);bind(listen_sockfd, (struct sockaddr*)&sock, sizeof(sock));
listen(listen_sockfd, 10);// peer用于接收客户端信息
struct sockaddr_in peer;
socklen_t peer_len;// 接收来自客户端的连接
int sockfd = accept(listen_sockfd, (struct sockaddr*)&peer, &peer_len);

以上是一个完整的TCP服务端启动过程,一般而言accpet之后,会使用多进程/多线程完成后续的通信,而主进程继续listen其它的连接,本博客不展示该过程了。

此处的listen_sockfd就是专门用于连接的套接字,而最后的sockfd 是与客户端通信的套接字。


send

当连接建立成功后,就可以开始收发消息了,TCP是面向字节流的,与UDP不同,TCP可以把sockfd文件描述符完全当作一个文件,完成消息的读写。

发送消息使用send函数,包含在头文件<sys/types.h><sys/socket.h>中,函数原型如下:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数:

  • sockfd:用于通信的套接字
  • buf:要发送的数据缓冲区
  • len:要发送的数据的长度
  • flags:设为0即可

不知道你有没有发现,它和文件写入函数write几乎没有差别:

ssize_t write(int fd, const void *buf, size_t count);

前三个参数没有区别,而send的第四个参数固定为0。其实sendwrite一样,都是直接向文件中写入字符串的,这符合TCP面向字节流的特性,在发送数据时,也可以用write代替send


recv

接收消息使用recv函数,包含在头文件<sys/types.h><sys/socket.h>中,函数原型如下:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数:

  • sockfd:用于通信的套接字
  • buf:数据的接收缓冲区
  • len:最大接收数据的长度
  • flags:设为0即可

同样,其实recvread函数差不多:

ssize_t read(int fd, void *buf, size_t count);

在读取TCP连接的数据的时可以使用两者的任何一个。



http://www.ppmy.cn/news/1519107.html

相关文章

【算法基础实验】图论-Dijkstra最短路径

理论知识 边的放松 边的放松&#xff08;Edge Relaxation&#xff09;是图算法中的一个关键操作&#xff0c;主要用于解决最短路径问题。它的核心思想是在遍历图的过程中&#xff0c;通过比较和更新路径的长度&#xff0c;逐步找到从起点到每个顶点的最短路径。 边的放松过程…

使用 Pandas 进行数据可视化:全面指南(六)

在数据分析的过程中,数据的可视化是一个至关重要的环节。通过图形展示数据,不仅能够帮助我们直观地理解数据,还能够揭示数据背后的规律和趋势。Pandas 作为 Python 生态系统中强大的数据分析库,不仅提供了数据处理和分析的功能,还内置了方便易用的可视化方法。本文将详细介…

AD19基础应用技巧:捕捉对象功能的讲解鼠标”绿色十字”大光标、小光标切换

AD PCB 中心点捕捉功能&#xff1a; 线段、圆、边框中心点捕捉。 有时候不想要鼠标自动捕捉中心点怎么办&#xff1f; 关于Altium Designer 20 的捕抓功能的讲解&#xff08;https://blog.csdn.net/weixin_44599693/article/details/126177841&#xff09; ——- AD PCB画板…

服务器上部署Wordpress:Docker技术教程

今天在三丰云免费服务器上进行部署测试&#xff0c;这款不错的免费服务器配置为1核CPU、1G内存、10G硬盘、5M带宽&#xff0c;给人惊喜。三丰云免费服务器的性能稳定&#xff0c;让我可以尽情发挥技术的魔力。 Docker是一种轻量级容器技术&#xff0c;而Wordpress则是广受欢迎…

C++国密SM2算法加解密的使用

目录 效果 在线校验 代码实现参考 项目 下载 效果 加密字符串:lxw 123abcD 2024-09-01:12:00加密后信息:042E82EE8ACE2BD56FA71DC6A0C34190627AA365F8EEE6261903BEE327A85EB5E1D6E78F2D79AD6F6DC9E45C0829625DC3165BB78BD897F99044A640F930653747939CF9D5A10C8216F945A559…

【Leetcode 2357 】 使数组中所有元素都等于零 —— 哈希表

给你一个非负整数数组 nums 。在一步操作中&#xff0c;你必须&#xff1a; 选出一个正整数 x &#xff0c;x 需要小于或等于 nums 中 最小 的 非零 元素。nums 中的每个正整数都减去 x。 返回使 nums 中所有元素都等于 0 需要的 最少 操作数。 示例 1&#xff1a; 输入&am…

【手撕数据结构】二叉树oj题

目录 单值二叉树题目描述题目思路及代码 相同的树题目描述题目思路及代码 对称二叉树题目描述题目思路及代码 另一棵树的子树题目描述题目思路及代码 二叉树的前序遍历题目描述题目思路及代码 二叉树的构建与遍历题目描述题目思路及代码 单值二叉树 题目描述 题目思路及代码 …

10、Flink 动态表之表到流的转换详解

表到流的转换 动态表可以像普通数据库表一样通过 INSERT、UPDATE 和 DELETE 来不断修改,它可能是一个只有一行、不断更新的表,也可能是一个 insert-only 的表,没有 UPDATE 和 DELETE 修改,或者介于两者之间的其他表。 在将动态表转换为流或将其写入外部系统时,需要对这些…

JVM GC 调优

文章目录 引言I 调整JVM的默认堆内存配置1.1 java命令启动jar包时配置JVM 的内存参数1.2 基于Tomcat服务器部署的java应用,配置JVM 的内存参数II JVM GC 调优基本概念: 应用程序的响应时间(RT)和吞吐量(QPS)JVM调优原理调优思路调优方法JVM调优技巧建议引言 内存参数:ht…

为Ubuntu换颗“心”

对于现在的Linux发行版操作系统,都默认配置好相应的Kernel,但其版本远比最新的要旧,而最新的Kernel除了会修复已发现的BUG,有时还会更新部分框架以及新增功能模块代码,为了确保系统的稳定,还有体验下新功能,我们只好对操作系统的进行换“心”手术,这手术可不简单,首先…

Go 语言版本管理——Goenv

Go 语言版本管理——Goenv 命令安装 goenv安装和切换 Go 版本 goenv 是一个专门管理 Go 语言版本的工具。 命令 安装 goenv github-goenv git clone https://github.com/go-nv/goenv.git ~/.goenv echo export GOENV_ROOT"$HOME/.goenv" >> ~/.bash_profile…

字符编码简介

目录 1. ASCLL 2. GB2312 3. GBK/gbk 4. GB18030 5. Unicode 6. 总结 1. ASCLL 在计算机刚开始被美国人发明的时候&#xff0c;需要将字符存储到计算机进行运算或打印&#xff0c;于是选取了95 个可见字符&#xff08;数字0-9&#xff0c;英文字母&#xff0c;标点符号&…

超详细Git基本命令使用(二)

&#x1f600;前言 本篇博文是关于 Git基本命令的使用&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动力&#x1f6…

SprinBoot+Vue实验室考勤管理小程序的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue3.6 uniapp代码 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平…

【0-1背包变种】力扣2787. 将一个数字表示成幂的和的方案数

给你两个 正 整数 n 和 x 。 请你返回将 n 表示成一些 互不相同 正整数的 x 次幂之和的方案数。换句话说&#xff0c;你需要返回互不相同整数 [n1, n2, …, nk] 的集合数目&#xff0c;满足 n n1x n2x … nkx 。 由于答案可能非常大&#xff0c;请你将它对 109 7 取余后…

深度学习100问35:如何避免梯度爆炸发生

嘿&#xff0c;想避免梯度爆炸这个麻烦家伙&#xff0c;有好多招儿呢。 首先说说权重初始化&#xff0c;这就好比给游戏里的角色分配初始能力值。得合理安排神经网络的权重初始化哦&#xff0c;不然一开始就可能出问题。可以用像 Xavier 初始化或者 He 初始化这些方法&#x…

Http的get请求中的URL中的占位符参数和查询参数有什么区别

Http的GET请求中的URL中的占位符参数和查询参数在功能、位置和用途上存在明显的区别。 占位符参数&#xff08;Path Variables&#xff09; 定义与位置&#xff1a;占位符参数是通过URL模板中的{}定义的&#xff0c;它们位于URL的路径&#xff08;path&#xff09;部分。例如…

Python实时聊天室架构与API实战应用

尊敬的各位读者&#xff0c;欢迎参与本次共享研讨项目——利用Python构建实时聊天室。在本项目中&#xff0c;我们将引进一款前沿工具——发布订阅频道API&#xff0c;以实现聊天室内的实时交互功能。 在当今信息泛滥的社会环境下&#xff0c;实时交流已成为人们日常生活中不可…

算法训练营|图论第7天 prim算法 kruskal算法

题目&#xff1a;prim算法 题目链接&#xff1a; 53. 寻宝&#xff08;第七期模拟笔试&#xff09; (kamacoder.com) 代码&#xff1a; #include<bits/stdc.h> #include<unordered_map> #include<unordered_set> using namespace std; int main() {int v…

人工智能 | 实现定制化 AutoGPT 实战

简介 在前面的学习过程中&#xff0c;已经了解到了 AutoGPT 基本的环境安装操作。接下来就可以基于 AutoGPT 完成一些有趣的任务。通过 AutoGPT 实现我们的需求 环境准备 在正式使用 AutoGPT 之前&#xff0c;确认以下环境没有任何问题&#xff1a; 稳定的科学上网环境。配…