套接字socket编程的基础知识点

目录

前言(必读)

网络字节序

网络中的大小端问题

为什么网络字节序采用的是大端而不是小端?

网络字节序与主机字节序之间的转换

字符串IP和整数IP

整数IP存在的意义

字符串IP和整数IP相互转换的方式 

inet_addr函数(会自动将转化出的整数IP从主机字节序变为网络字节序)

inet_ntoa函数(会自动先把从网络中读取到的整数IP从网络字节序转化成主机字节序)

sockaddr、sockaddr_in、sockaddr_un结构体

对sockaddr_in的补充说明

socket编程的常见函数

socket函数

sendto函数

recvfrom函数

bind函数


前言(必读)

注意本文中说明的是套接字socket编程的基础知识点,关于这些知识点的更深入的使用方式和场景,还是得在笔者关于【基于UDP协议的网络服务器的模拟实现】和【基于TCP协议的网络服务器的模拟实现】的文章中才能体现出来

网络字节序

网络中的大小端问题

计算机在存储数据时是有大小端的概念的:

  • 大端模式: 数据的高字节内容保存在内存的低地址处,数据的低字节内容保存在内存的高地址处。
  • 小端模式: 数据的高字节内容保存在内存的高地址处,数据的低字节内容保存在内存的低地址处。

如果编写的程序只在本地机器上运行,那么是不需要考虑大小端问题的,因为同一台机器上的数据采用的存储方式都是一样的,要么采用的都是大端存储模式,要么采用的都是小端存储模式。但如果涉及网络通信,那就必须考虑大小端的问题,否则对端主机识别出来的数据可能与发送端想要发送的数据是不一致的。如下图,现在两台主机之间在进行网络通信,其中发送端是小端机,而接收端是大端机。发送端将发送缓冲区中的数据按内存地址从低到高的顺序发出后,接收端从网络中获取数据依次保存在接收缓冲区时,也是按内存地址从低到高的顺序保存的。

但由于发送端和接收端采用的分别是小端存储和大端存储,此时对于内存地址从低到高为44332211的序列,发送端按小端的方式识别出来是0x11223344,而接收端按大端的方式识别出来是0x44332211,此时接收端识别到的数据与发送端原本想要发送的数据就不一样了,这就是由于大小端的偏差导致数据识别出现了错误。

由于我们不能保证通信双方存储数据的方式是一样的,因此网络当中传输的数据必须考虑大小端问题。因此TCP/IP协议规定,网络数据流采用大端字节序,即低地址高字节。无论是大端机还是小端机,都必须按照TCP/IP协议规定的网络字节序来发送和接收数据。

  • 如果发送端是小端,需要先将数据转成大端,然后再发送到网络当中。
  • 如果发送端是大端,则可以直接进行发送。
  • 如果接收端是小端,需要先将接收到数据转成小端后再进行数据识别。
  • 如果接收端是大端,则可以直接进行数据识别。

在这个例子中,由于发送端是小端机,因此在发送数据前需要先将数据转成大端,然后再发送到网络当中,而由于接收端是大端机,因此接收端接收到数据后可以直接进行数据识别,此时接收端识别出来的数据就与发送端原本想要发送的数据相同了。

需要注意的是,所有的大小端的转化工作是由操作系统来完成的,因为该操作属于通信细节,不过也有部分的信息需要我们自行进行处理,比如端口号和IP地址。

为什么网络字节序采用的是大端而不是小端?

问题:网络字节序采用的是大端,而主机字节序一般采用的是小端,那为什么网络字节序不采用小端呢,毕竟如果网络字节序采用小端的话,发送端和接收端在发生和接收数据时就不用进行大小端的转换了。

答案:该问题有很多不同说法,下面列举了两种说法:

说法一: TCP在Unix时代就有了,以前Unix机器都是大端机,因此网络字节序也就采用的是大端,但之后人们发现用小端能简化硬件设计,所以现在主流的都是小端机,但协议已经不好改了。

说法二: 大端序更符合现代人的读写习惯。

网络字节序与主机字节序之间的转换

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,系统提供了四个函数,可以通过调用以下库函数实现网络字节序和主机字节序之间的转换:

(头文件都是#include <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);

函数名当中的h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位长整数从主机字节序转换为网络字节序。如果主机是小端字节序,则这些函数将参数做相应的大小端转换然后返回。如果主机是大端字节序,则这些函数不做任何转换,将参数原封不动地返回。

字符串IP和整数IP

IP地址的表现形式有两种:

  • 字符串IP:类似于192.168.233.123这种字符串形式的IP地址,叫做基于字符串的点分十进制IP地址。
  • 整数IP:IP地址在进行网络传输时所用的形式,用一个32位的整数来表示IP地址。

整数IP存在的意义

网络传输数据时是寸土寸金的,如果我们在网络传输时直接以基于字符串的点分十进制IP的形式进行IP地址的传送,那么此时一个IP地址至少就需要15个字节,但实际并不需要耗费这么多字节。

IP地址实际可以划分为四个区域,其中每一个区域的取值都是0~255,而这个范围的数字只需要用8个比特位就能表示,因此我们实际只需要32个比特位就能够表示一个IP地址。其中这个32位的整数的每一个字节对应的就是IP地址中的某个区域,我们将IP地址的这种表示方法称之为整数IP,此时表示一个IP地址只需要4个字节。

因为采用整数IP的方案表示一个IP地址只需要4个字节,并且在网络通信也能表示同样的含义,因此在网络通信时就没有用字符串IP而用的是整数IP,因为这样能够减少网络通信时数据的传送。

字符串IP和整数IP相互转换的方式 

inet_addr函数(会自动将转化出的整数IP从主机字节序变为网络字节序)

实际在进行字符串IP和整数IP的转换时,我们不需要自己编写转换逻辑,系统已经为我们提供了相应的转换函数,我们直接调用即可。

函数用于【先将字符串IP转化成整数IP,然后把整数IP从主机字节序转化成网络字节序】,该函数的函数原型如下:

in_addr_t inet_addr(const char *cp);

该函数使用起来非常简单,我们只需传入待转换的字符串IP,该函数返回的就是转换后的整数IP。除此之外,inet_aton函数也可以将字符串IP转换成整数IP,不过该函数使用起来没有inet_addr简单。再次强调,inet_addr会做两个操作,1、将点分十进制字符串变为整数后,2、还会将整数从主机字节序变为网络字节序。

inet_ntoa函数(会自动先把从网络中读取到的整数IP从网络字节序转化成主机字节序)

函数用于【先将整数IP从网络字节序转化成主机字节序,然后将主机字节序的整数IP转换成字符串IP】,该函数的函数原型如下:

char *inet_ntoa(struct in_addr in);

需要注意的是,传入inet_ntoa函数的参数类型是in_addr,因此我们在传参时不需要选中in_addr结构当中的32位的成员传入,直接传入in_addr结构体即可。

sockaddr、sockaddr_in、sockaddr_un结构体

套接字不仅支持跨网络的进程间通信,还支持本地的进程间通信(域间套接字)。在进行跨网络通信时我们需要传递的端口号和IP地址,而本地通信则不需要,因此套接字提供了sockaddr_in结构体和sockaddr_un结构体,其中sockaddr_in结构体是用于跨网络通信的,而sockaddr_un结构体是用于本地通信的。

为了让套接字的网络通信和本地通信能够使用同一套函数接口,于是就出现了sockeaddr结构体,该结构体与sockaddr_in和sockaddr_un的结构都不相同,但这三个结构体头部的16个比特位都是一样的,这个字段叫做协议家族。

此时当我们在调用sendto、recvfrom或者其他函数需要传参时,就不用传入sockeaddr_in或sockeaddr_un这样的结构体,而统一传入sockeaddr这样的结构体。我们在设置参数时就可以通过设置协议家族这个字段,来表明我们是要进行网络通信还是本地通信,在这些API(即sendto、recvfrom或者其他函数)内部就可以提取sockeaddr结构头部的16位进行识别,进而得出我们是要进行网络通信还是本地通信,然后执行对应的操作。此时我们就通过通用sockaddr结构,将套接字网络通信和本地通信的参数类型进行了统一。(注意实际我们在进行网络通信时,定义的还是sockaddr_in这样的结构体,只不过在调用sendto、recvfrom或者其他函数时,在传参时需要将该结构体的地址类型进行强转为sockaddr*)

问题:读了上一段我们可能会有一个疑问,即为什么没有用void*代替struct sockaddr*类型?我们可以将这些函数的struct sockaddr*参数类型改为void*,此时在函数内部也可以直接指定提取头部的16个比特位进行识别,最终也能够判断是需要进行网络通信还是本地通信,那为什么还要设计出sockaddr这样的结构呢?

答案:实际在设计这一套网络接口的时候C语言还不支持void*,于是就设计出了sockaddr这样的解决方案。并且在C语言支持了void*之后也没有将它改回来,因为这些接口是系统接口,系统接口是所有上层软件接口的基石,系统接口是不能轻易更改的,否则引发的后果是不可想的,这也就是为什么现在依旧保留sockaddr结构的原因。

对sockaddr_in的补充说明

sockaddr_in结构体的定义如下图右半部分,可以看到struct sockaddr_in中的成员有:

  • sin_family:表示协议家族。
  • sin_port:表示端口号,是一个16位的整数。
  • sin_addr:表示IP地址,是一个32位的整数。

剩下的字段一般不做处理,当然你也可以进行初始化。其中sin_addr的类型是struct in_addr,实际该结构体当中就只有一个成员(如上图左半部分),该成员就是一个32位的整数,IP地址实际就是存储在这个整数当中的。

socket编程的常见函数

socket函数

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

参数说明:

  • domain:创建套接字的域或者叫做协议家族,也就是创建套接字的类型。该参数就相当于struct sockaddr结构的前16个位。如果是本地通信就设置为AF_UNIX,如果是网络通信就设置为AF_INET(IPv4)或AF_INET6(IPv6)。
  • type:创建套接字时所需的服务类型。其中最常见的服务类型是SOCK_STREAM和SOCK_DGRAM,如果是基于UDP的网络通信,我们采用的就是SOCK_DGRAM,叫做用户数据报服务,如果是基于TCP的网络通信,我们采用的就是SOCK_STREAM,叫做流式套接字,提供的是流式服务。
  • protocol:创建套接字的协议类别。你可以指明为TCP或UDP,但该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。

返回值说明:

  • 套接字创建成功返回一个文件描述符,创建失败返回-1,同时错误码会被设置。

功能说明:

  • 说简单点就是创建了一个文件,并返回了一个指向该文件的文件描述符,之后我们就可以在当前进程中向这个文件里写入数据并向网络中发送,或者从网络中读取数据到这个文件里并再将数据从文件中读到当前进程里。

问题:socket为什么可以具备这样的功能呢?它的底层干了什么?

答案:(结合下图思考)socket函数是被进程所调用的,而每一个进程在系统层面上都有一个进程地址空间PCB(task_struct)、文件描述符表(files_struct)以及对应打开的各种文件。而文件描述符表里面包含了一个数组fd_array成员,其中数组中的0、1、2下标依次对应的就是标准输入、标准输出以及标准错误。

(结合下图思考)当我们调用socket函数创建套接字时,实际相当于我们打开了一个“网络文件”,打开后在内核层面上就形成了一个对应的struct file结构体,同时该结构体被连入到了该进程对应的文件双链表,并将该结构体的首地址填入到了fd_array数组当中下标为3的位置,此时fd_array数组中下标为3的指针就指向了这个打开的“网络文件”,最后3号文件描述符作为socket函数的返回值返回给了用户。

其中每一个struct file结构体中包含的就是对应打开文件各种信息,比如文件的属性信息、操作方法以及文件缓冲区等。其中文件对应的属性在内核当中是由struct inode结构体来维护的;而文件对应的操作方法实际就是一堆的函数指针(比如read*和write*),在内核当中就是由struct file_operations结构体来维护的。

而对于文件缓冲区,OS会为不同的文件都分配一块内存,用于暂时在内存中保存属于文件的数据:

  • 比如在当前情景下,网络文件的文件缓冲区就是OS为网卡文件分配的一块内存,用于在内存中暂时保存属于网卡设备(或者说网卡文件)的数据,之后会根据属于网卡文件的文件缓冲区的刷新策略,将文件缓冲区中的数据刷新到内核,再由内核刷新到网卡设备上,网卡就可以根据自己的刷新策略向网络中发送信息了。
  • 再比如普通磁盘文件的文件缓冲区就是OS为磁盘文件分配的一块内存,用于在内存中暂时保存属于磁盘设备(或者说磁盘文件)的数据,之后会根据属于磁盘文件的文件缓冲区的刷新策略,将文件缓冲区中的数据刷新到内核,再由内核刷新到磁盘设备(或者说磁盘文件)上,这就完成了一次写入磁盘的操作。

sendto函数

如上图红框处。

参数说明:

  • int sockfd,sendto函数是向某台机器上的某个进程发信息,发信息需要一个通信通道,这个通道为【当前进程--->sockfd指向的文件的文件缓冲区(即分配给该文件的某块内存)--->内核缓冲区--->网卡--->网络--->对方的网卡--->对方的内核缓冲区--->对方的sockfd指向的文件的文件缓冲区--->对方的进程】,所以也就能够理解sockfd这个参数的作用了,即给sendto函数提供文件描述符,以找到其指向的文件的缓冲区,提供通信的媒介。
  • void *buf,sendto函数是向某台机器上的某个进程发信息,那么需要发出的信息是什么呢?buf指针指向的数据就是这个待发的信息。buf的类型是void*,方便sendto发送不同种类的信息。
  • size_t len,sendto函数是向某台机器上的某个进程发信息,len就用于指定发送多大长度的信息。注意这个len不一定是实际发送信息的长度,只是用户指定并期望发这么多,如果用户指定发送的长度远远大于了buf指针指向数据的长度,那实际只会发送buf指针指向数据的长度个数据。实际发送的数据的长度可以通过sendto的返回值获取。
  • int flags,设置为0即可,不必关心。
  • const struct sockaddr *dest_addr,sendto函数是向某台机器上的某个进程发信息,向哪台机器和哪个进程发送就是通过dest_addr指针(dest即destination,翻译为目的地)指向的sockaddr对象决定的,sockaddr对象里包含了标识目标主机的ip地址和标识目标进程的端口号port。
  • socklen_t addrlen就是上一个指针参数指向的sockaddr对象所占的空间大小,传入sizeof(sockaddr对象即可)。

返回值说明:

  • 在参数中已经隐含了该信息,即表示当前进程实际发送给对方进程的数据的长度。如果发生错误,返回值为-1。

recvfrom函数

如上图红框处。

参数说明:

  • int sockfd。recvfrom函数是用于接收某台机器上的某个进程发过来的信息,接收信息需要一个通信通道,这个通道为【对方进程--->对方进程的sockfd指向的文件的文件缓冲区(即分配给该文件的某块内存)--->对方的内核缓冲区--->对方的网卡--->网络--->当前主机的网卡--->当前主机的内核缓冲区--->当前进程的sockfd指向的文件的文件缓冲区--->当前的进程】,所以也就能够理解sockfd这个参数的作用了,即给recvfrom函数提供文件描述符,以找到其指向的文件的缓冲区,提供通信的媒介。
  • void *buf。recvfrom函数是用于接收某台机器上的某个进程发过来的信息,那么需要接收的信息需要存在哪里呢?buf指针指向的这块空间就用于存放这个接收到的信息。buf的类型是void*,方便recvfrom接收不同种类的信息。
  • size_t len。recvfrom函数是用于接收某台机器上的某个进程发过来的信息,len就用于指定接收多大长度的信息。注意这个len不一定是实际接收信息的长度,只是用户指定并期望接收这么多,如果用户指定接收的长度远远大于了buf指针指向空间所能容纳的最大长度,那实际只会接收buf指针指向空间的最大长度个数据。实际接收的数据的长度可以通过recvfrom的返回值获取。
  • int flags。设置为0即可,不必关心。
  • const struct sockaddr *src_addr。src即sourcere,翻译为来源。recvfrom函数是用于接收某台机器上的某个进程发过来的信息的,那是哪台机器上的哪个进程给我发的信息呢?我们可以通过src_addr指针指向的sockaddr对象得知。说一下,src_addr是个输出型参数,我们需要先设置一个sockaddr的对象,无所谓是否初始化它,然后把该sockaddr对象的地址传入recvfrom函数,函数调用结束后,src_addr指针指向的这个sockaddr对象中就包含了【是哪台机器的哪个进程给我发信息】的信息,即sockaddr对象里包含了标识【给我发信息的主机】的ip地址和标识【给我发信息的进程】的端口号port。
  • socklen_t *addrlen。其是个输入输出型参数,在调用recvfrom函数前,addrlen指针指向【表示上一个参数src_addr指向对象大小】的socklen_t对象,所以调用recvfrom函数时给addrlen传入一个值初始化成了sizeof(scr_addr指向的sockaddr对象)的socklen_t对象的地址即可;调用recvfrom函数结束后,addrlen指针指向【表示实际填充进上一个参数scr_addr指向的sockaddr对象的数据的大小】的socklen_t对象。既然addrlen是个输入输出型参数,那么使用它的方式为:调用recvfrom函数前,我们得先设置一个socklen_t的对象,然后将它初始化成sizeof(scr_addr指向的sockaddr对象),然后将该socklen_t对象的地址传给recvfrom函数的形参addrlen,recvfrom函数调用完毕后,addrlen指向的socklen_t对象的值就变成了实际填充进上一个参数scr_addr指向的sockaddr对象的数据的大小。

返回值说明:

  • 在参数中已经隐含了该信息,即表示实际接收到的对方进程发过来的数据的长度。如果发生错误,返回值为-1。

bind函数

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

调用完socket函数成功创建了套接字文件后,需要调用bind函数将【当前进程】和【某个ip与某个port】进行绑定,原因为:

  • (结合下图思考)现在套接字已经创建成功了,但作为一款服务器来讲,如果只是把套接字创建好了,那我们也只是在系统层面上打开了一个文件,操作系统将来并不知道是要将数据写入到磁盘还是刷到网卡,此时该文件还没有与网卡或者说网络关联起来。
  • 套接字socket文件用于通信,首先,如果想要网络通信,则必须通过网卡,所以你必须得指定从哪个网卡(ip)读取数据送到socket文件,这就是bind ip的原因,数据读取完毕后,送到哪个端口(进程)呢?所以你必须指定一个端口号。

参数说明:

  • int sockfd。发信息需要一个通信通道,这个通道为【当前进程--->sockfd指向的文件的文件缓冲区(即分配给该文件的某块内存)--->内核缓冲区--->网卡--->网络--->对方的网卡--->对方的内核缓冲区--->对方的sockfd指向的文件的文件缓冲区--->对方的进程】。接收信息需要一个通信通道,这个通道为【对方进程--->对方进程的sockfd指向的文件的文件缓冲区(即分配给该文件的某块内存)--->对方的内核缓冲区--->对方的网卡--->网络--->当前主机的网卡--->当前主机的内核缓冲区--->当前进程的sockfd指向的文件的文件缓冲区--->当前的进程】,可以看到在收或者发信息时,sockfd指向的文件是作为通信通道的一环的,所以调用bind函数需要sockfd就是在告诉bind函数,我需要将哪个文件设置成通信通道的一环。 
  • const struct sockaddr *addr。bind函数用于将【当前进程】和【某个ip与某个port】进行绑定,ip和port信息就包含在addr指向的sockaddr对象中。
  • socklen_t addrlen。为上一个参数addr指针指向的sockaddr对象的大小,传入sizeof(上一个参数addr指针指向的sockaddr对象)即可。

返回值说明:

  • 如果bind函数成功执行,它将返回 0。这表示套接字成功绑定到指定的地址和端口。

  • 如果bind函数执行失败,它将返回 -1。这表示绑定操作未成功,并且通常会伴随着设置全局变量errno来指示错误的原因。通过检查bind函数的返回值和检查errno变量的值,你可以确定bind失败的原因,以便进行适当的错误处理。一些常见的bind失败原因包括:1、端口已经被占用:如果指定的端口已经被其他程序占用,bind将失败,并且errno可能会被设置为 EADDRINUSE。2、无效的地址或端口:如果指定的地址或端口无效,bind也会失败,并且errno的值会指示具体的错误类型。3、权限不足:有些系统可能要求程序拥有特定的权限才能绑定到某些端口,如果权限不足,bind也会失败,并且errno的值可能会指示权限相关的错误。因此,当你调用bind函数时,应该检查其返回值,如果返回值是-1,则通过查看errno的值来确定失败的原因,并根据错误原因进行适当的错误处理。

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

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

相关文章

相机One Shot标定

1 原理说明 原理部分网上其他文章[1][2]也已经说的比较明白了&#xff0c;这里不再赘述。 2 总体流程 参考论文作者开源的Matlab代码[3]和github上的C代码[4]进行说明&#xff08;不得不说还是Matlab代码更优雅&#xff09; 论文方法总体分两部&#xff0c;第一部是在画面中找…

封装一个高级查询组件

封装一个高级查询组件 背景一&#xff0c;前端相关代码二&#xff0c;后端相关代码三&#xff0c;呈现效果总结 背景 业务有个按照自定义选择组合查询条件&#xff0c;保存下来每次查询的时候使用的需求。查了一下项目里的代码没有现成的组件可以用&#xff0c;于是封装了一个 …

Python实战实例代码-网络爬虫-数据分析-机器学习-图像处理

Python实战实例代码-网络爬虫-数据分析-机器学习-图像处理 Python实战实例代码1. 网络爬虫1.1 爬取网页数据1.2 爬取图片1.3 爬取动态数据&#xff08;使用Selenium&#xff09; 2. 数据分析2.1 数据清洗2.2 数据变换2.3 数据聚合 3. 机器学习3.1 线性回归3.2 随机森林3.3 K-Me…

【数据结构】C++实现哈希表

闭散列哈希表 哈希表的结构 在闭散列的哈希表中&#xff0c;哈希表每个位置除了存储所给数据之外&#xff0c;还应该存储该位置当前的状态&#xff0c;哈希表中每个位置的可能状态如下&#xff1a; EMPTY&#xff08;无数据的空位置&#xff09;。EXIST&#xff08;已存储数…

Linux Day18 TCP_UDP协议及相关知识

一、网络基础概念 1.1 网络 网络是由若干结点和连接这些结点的链路组成&#xff0c;网络中的结点可以是计算机&#xff0c;交换机、 路由器等设备。 1.2 互联网 把多个网络连接起来就构成了互联网。目前最大的互联网就是因特网。 网络设备有&#xff1a;交换机、路由器、…

图层混合算法(一)

常见混合结果展示 图层混合后变暗 正常模式&#xff08;normal&#xff09; 混合色*不透明度&#xff08;100%-混合色不透明度&#xff09; void layerblend_normal(Mat &base,Mat &blend,Mat &dst,float opacity) {if (base.rows ! blend.rows ||base.cols ! b…

测试C#图像文本识别模块Tesseract的基本用法

微信公众号“dotNET跨平台”的文章《c#实现图片文体提取》&#xff08;参考文献3&#xff09;介绍了C#图像文本识别模块Tesseract&#xff0c;后者是tesseract-ocr&#xff08;参考文献2&#xff09; 的C#封装版本&#xff0c;目前版本为5.2&#xff0c;关于Tesseract的详细介绍…

使用Python+Flask/Moco框架/Fiddler搭建简单的接口Mock服务

一、Mock测试 1、介绍 mock&#xff1a;就是对于一些难以构造的对象&#xff0c;使用虚拟的技术来实现测试的过程mock测试&#xff1a;在测试过程中&#xff0c;对于某些不容易构造或者不容易获取的对象&#xff0c;可以用一个虚拟的对象来代替的测试方法接口mock测试&#x…

多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测(SE注意力机制)

多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09; 目录 多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基…

stc8H驱动并控制三相无刷电机综合项目技术资料综合篇

stc8H驱动并控制三相无刷电机综合项目技术资料综合篇 🌿相关项目介绍《基于stc8H驱动三相无刷电机开源项目技术专题概要》 🔨停机状态,才能进入设置状态,可以设置调速模式,以及转动方向。 ✨所有的功能基本已经完成调试,目前所想到的功能基本已经都添加和实现。引脚利…

SpringSecurity 认证流程

文章目录 前言认证入口&#xff08;过滤器&#xff09;认证管理器认证器说明默认认证器的实现 总结 前言 通过上文了解SpringSecurity核心组件后&#xff0c;就可以进一步了解其认证的实现流程了。 认证入口&#xff08;过滤器&#xff09; 在SpringSecurity中处理认证逻辑是…

CMU15-445 format\clang-format\clang-tidy 失败

CMU15-445 format\clang-format\clang-tidy 失败 问题修改 问题 -- Setting build type to Debug as none was specified. -- Youre using Clang 14.0.0 CMake Warning at CMakeLists.txt:67 (message):BusTub/main couldnt find clang-format.CMake Warning at CMakeLists.tx…

虚幻4学习笔记(15)读档 和存档 的实现

虚幻4学习笔记 读档存档 B站UP谌嘉诚课程&#xff1a;https://www.bilibili.com/video/BV164411Y732 读档 添加UI蓝图 SaveGame_UMG 添加Scroll Box 修改Scrollbar Thickness滚动条厚度 15 15 勾选 is variable 添加text 读档界面 添加背景模糊 添加UI蓝图 SaveGame_Slot …

Rowset Class

本节介绍 This chapter provides an overview of Rowset class and discusses the following topics: Shortcut considerations. Rowset object declaration. Scope of a Rowset object. Rowset class built-in functions. Rowset class methods. Rowset class propertie…

计算机毕业设计 智慧养老中心管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

golang实现远程控制主机

文章目录 ssh原理使用golang远程下发命令使用golang远程传输文件 ssh原理 说到ssh原理个人觉得解释最全的一张图是这张华为画的 Connection establishment 这一步就是建立tcp连接 version negotiation 这一步是ssh客户端(连接者)和被ssh服务端(连接者)进行协议的交换&#xf…

字符函数和字符串函数(1)

前言 C语言中对字符和字符串的处理很是频繁&#xff0c;但是C语言本身是没有字符串类型的&#xff0c;字符串通常放在 常量字符串 中或者 字符数组 中。 字符串常量 适用于那些对它不做修改的字符串函数. 1.求字符串长度 strlen 1.1 strlen size_t strlen ( const char * s…

Windows安装cuda和cudnn教程最新版(2023年9月)

文章目录 cudacudnn cuda 查看电脑的cuda最高驱动版本&#xff08;适用于N卡电脑-Nvidia&#xff09; winR打开命令行&#xff0c;输入nvidia-smi 右上角cuda -version就是目前支持的最高cuda版本&#xff0c;目前是12.2 nvidia官网下载cuda 下载地址&#xff1a;https://d…

华为NFC设置教程(门禁卡/公交卡/校园卡等)

今天把华为NFC设置教程分享给大家 出门带门禁卡、校园卡、银行卡、身份证……东西又多&#xff0c;携带又麻烦&#xff0c;还容易搞丢&#xff0c;有没有一种方法可以把它们都装下&#xff1f;有&#xff01;只要一部手机&#xff0c;出门不带卡包&#xff0c;各种证件&#x…