【网络】socket——预备知识 | 套接字 | UDP网络通信

news2024/11/26 18:31:58

🐱作者:一只大喵咪1201
🐱专栏:《网络》
🔥格言:你只管努力,剩下的交给时间!
图

在前面本喵对网络的整体轮廓做了一个大概的介绍,比如分层,协议等等内容,现在我们直接进入socket(套接字)编程,先来感受到网络编程。

UDP套接字

  • 🍚预备知识
    • 🥣源IP地址和目的IP地址
    • 🥣端口号port
      • 网络通信的本质
    • 🥣认识TCP/UDP协议
    • 🥣网络字节序
      • 主机和网络的字节序转换函数
  • 🍚socket套接字
    • 🥣socket常见系统调用
    • 🥣sockaddr结构体
  • 🍚UDP网络编程
    • 🥣服务端实现
    • 🥣客户端实现
  • 🍚用户层与网络的解耦
  • 🍚总结

🍚预备知识

🥣源IP地址和目的IP地址

我们知道,在网络通信中,存在两套地址,一套是IP地址,另一套是MAC地址。

  • IP地址:标识计算机在网络中的唯一性。

而IP地址又分为源IP地址和目的IP地址:

  • 源IP地址:标识网络通信发起方。
  • 目的IP地址:标识网络通信的接收方。

在网络通信的报文中,其中报头包含着源IP和目的IP

🥣端口号port

图
如上图所示,报文从用户A的计算机传送到了用户B的计算机,但是网络通信的目的就是将报文从一台计算机传送到另一台计算机吗?

  • 将数据从计算机A传送到计算机B是手段,并不是网络通信的目的。
  • 真正进行通信的是用户A和用户B,也就是计算机A上的某个应用程序和计算机B上的某个应用程序之间在通信

网络通信的目的就是让两台计算机上的两个进程在进行通信。

IP地址可以标识两台计算机的唯一性,但是每台计算机上会存在大量的进程,如何保证计算机A某个进程发送的数据能让计算机B指定的进程接收到呢?

换句话说,如何标识一台计算机上进程的唯一性呢?

采用端口号port来标识计算机上进程的唯一性

  • 端口号是一个2字节16位的整数。
  • 端口号用来标识一个进程,告诉操作系统要把数据交给哪一个进程。
  • 一个端口号只能被一个进程占用。

现在我们有用来标识计算机在网络中唯一性的IP地址,又有用来标识进程在计算机中唯一性的端口号port。

  • 全网唯一进程 = IP地址(全网主机唯一性) + 端口号port(该主机上进程唯一性)
  • socket(套接字) = IP地址 + 端口号port。

所以要想两个进程间实现通信,必须各自有各自的套接字。

网络通信的本质

网络通信实际上是两台计算机或者多台计算机上的进程之间在通信,和我们之前Linux学习的进程间通信的区别在于进程位于不同的计算机上。

  • 网络通信的本质:进程间通信。
  • 要实现进程间通信,必须有共享资源,而网络通信中的网络就是共享资源。
  • 网络通信其实就是在做IO,我们上网的所有行为就两种:a. 把数据发出去。 b. 把数据读回来。
  • Linux下一切皆文件,所以网络在系统中也是一个"文件",也有struct结构体,也有文件描述符。

我们知道,每个进程都有一个pid来标识它在当前计算机上的唯一性,为什么网络中还需要一个端口号port来标识进程的唯一性呢?不能用pid吗?

在技术实现上是完全可以用pid的,所以就需要考虑为什么偏偏就用了端口号port

  • 系统是系统,网络是网络,系统使用pid,网络使用port来标识进程的唯一性,实现了系统与网络的解耦
  • 不是所有进程都提供网络服务或者网络请求的,但是所有的进程都需要pid,只有需要网络的进程才会分配一个port
  • 客户端需要能够直接找到服务器的进程,服务器进程的唯一性不能做任何改变。

比如我们平时使用的QQ,我们手机上的QQ都是客户端,我们打开QQ使用都是在向服务器上的QQ进程发起网络请求,而这个服务器位腾讯公司,服务进程根据用户的网络请求再做出对应的反馈交给用户。

  • 我们下载了某个应用程序以后,该程序里就绑定了服务端对应进程的IP地址和端口号。
  • 所以使用应用程序的时候,就能精准的和服务端上对应的进程进行网络通信。

服务器的IP地址并不会随意变化,为了保证客户端每次都能找到服务端的进程,服务端的port也不能变化。

  • 如果使用pid来代替端口号的话,服务器每重启一次,服务进程的pid值就会改变,客户端就无法找到服务进程了。

绑定了port的进程PCB会被维护在一个哈希表中,port就是key值,操作系统能够根据key值找到对应的PCB,然后再执行它。

🥣认识TCP/UDP协议

这两个协议的具体原理和细节在后面本喵会详细讲解,这里仅需要大概了解一下它两的特定即可。

TCP协议:(Transmission Control Protocol 传输控制协议)。

  • 传输层协议。
  • 需要通信双方建立连接。
  • 是一种可靠传输,不会发生丢包等问题。
  • 面向字节流。

UDP协议:(User Datagram Protocol 用户数据报协议)。

  • 传输层协议。
  • 不需要通信双方建立连接,直接发生即可。
  • 不可靠传输,可能会发生丢包等问题。
  • 描写数据报。

具体这些特点是什么意思,以后会讲解到,这里只需要记住以上内容即可。

🥣网络字节序

我们的计算机分为大端机和小端机,不同的电脑型号就不一样,两台计算机大小端不同,接收到的数据解释出来意义也不同。

  • 规定:网络中的字节序都采用大端

如果你的计算机是大端机,那么就可以直接向网络中发数据和从网络中接收数据,不用做转换。

如果你的计算机是小端机,那么在向网络中发送数据时,需要先将数据转换成大端,再发送到网络中。从网络中接收下来的数据,需要先转换成小端再使用。

此时就存在两个问题:

  • 自己的电脑是大端还是小端?还需要自己去判断一下。
  • 如果自己的电脑是小端,需要自己去将数据转换成大端。

这两个问题虽然我们自己能解决,但是比较繁琐,而且很容易出错,所以操作系统提供了相应的接口来进行大小端转换:

主机和网络的字节序转换函数

#include <arpa/inet.h>//必须包含的头文件
// 主机序列转网络序列
uint32_t htonl(uint32_t hostlong);//将主机上unsigned int类型的数据转换成对应网络字节序
uint16_t htons(uint16_t hostshort);//将主机上unsigned short类型的数据转换成对应网络字节序
// 网络序列转主机序列
uint32_t ntohl(uint32_t netlong);//将从网络中读取的unsigned int类型的数据转换成当前计算机字节序
uint16_t ntohs(uint16_t netshort);//将从网络中读取的unsigned short类型的数据转换成当前计算机字节序
  • 这些函数名很好记,h表示host,代表着主机,n表示network,代表着网络,s表示unit16_t,l表示uint32_t
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回。
  • 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

🍚socket套接字

🥣socket常见系统调用

  1. socket:

图
socket系统调用专门用来创建套接字,在创建的时候指定使用哪种通信协议。

参数解释:

  • int domain

这是地址族,用来指定创建的套接字进行的是网络通信还是本地通信。

图
该参数可以填上图所示中的任何一个,经常使用的是AF_INET表示使用IPv4的网络套接字进行网络通信

  • int type

这是用来指定socket提供的能力类型,比如是面向字节流还是面向用户数据报。

图
该参数可以使用上图中的任何一个,其中画红色框的是面向字节流和面向用户数据报,也就是TCP和UDP。

  • int protocol

该参数是用来指定具体的协议名的,比如指定TCP或者DUP,但是根据前两个参数就可以确定使用哪个协议了,这个一般设置为0即可。

  • 返回值 int
  • 成功返回一个int类型的值,其实就是一个文件描述符sockfd
  • 失败返回-1,并且设置错误码errno

  1. bind

图
bind用来将IP地址和端口号port创建的socket套接字绑定,也就是将IP地址和端口号port和系统绑定。

参数解释:

  • int sockfd

使用socket()返回的文件描述符sockfd,用来指定绑定哪个套接字。

  • const struct sockaddr * addr

struct sockaddr是一个结构体。

  • socklen_t addrlen

这个参数是表示sockaddr结构体大小的,单位是字节,socklen_t本质是unsigned int类型的32位变量。

  • 返回值int

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


  1. 其他几个接口
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

这几个接口的是TCP协议才会用到,后面本喵再详细讲解。

🥣sockaddr结构体

套接字有很多种类型,常见的有三种:

  • 网络套接字:用户跨主机之间的通信,也能支持本地通信。
  • 原始套接字:可以跨过传输层(TCP/UDP)访问底层的数据。
  • 域间套接字:只能在本地通信。

这些套接字的应用场景完全不同,所以不同种类的套接字就对应一套系统调用接口,所以三套就会对应三套不同的接口。

网络套接字:

struct sockaddr_in {
    short int sin_family;           // 地址族,一般为AF_INET
    unsigned short int sin_port;    // 端口号,网络字节序
    struct in_addr sin_addr;        // IP地址
    unsigned char sin_zero[8];      // 用于填充,使sizeof(sockaddr_in)等于16
};

通过sockaddr_in结构体,将IP地址,端口号,以及网络通信AF_INET通过系统调用bind与系统绑定,从而进行网络通信。

域间套接字:

struct sockaddr_un {
    sa_family_t sun_family;       /* AF_UNIX */
    char sun_path[108];    /* 带有路径的文件名 */
};

sockaddr_un只有域间通信方式AF_UNIX以及域间通信的路径名。


而设计者为了方便使用,无论是网络通信还是域间通信,都使用一套接口,通过设置不同参数来解决所有通信场景。

图
sockaddr_insockaddr_un是用于网络通信和域间通信两个不同的通信场景,它们的区别就在于结构体起始处的16位地址类型不同,网络通信使用AF_INET,域间通信使用AF_UNIX

  • 但由于要使用一套接口,所以此时无论哪种通信,都使用sockaddr结构体。
  • 在填充IP地址,端口号,以及地址类型的时候,仍然是对sockaddr_in进行填充。
  • 在使用bind系统调用时,将sockaddr_in强转成sockaddr类型,在函数内部它会根据前两个字节自行判断是什么类型的通信,然后再强制转回去。

可以将sockaddr看成是基类,把sockaddr_insockaddr_un看出是派生类,此时就构成了多态体系。


🍚UDP网络编程

网络通信一定是双方的,一端是服务端(Server)接收数据,另一端是客户端(Client)发送数据。

🥣服务端实现

网络通信首先就是要有套接字,也就是要有IP地址和端口号port,所以我创建的服务器中必须有这两项。

class udpSever
{
private:
	uint16_t _port;//服务进程端口号
	string _ip;//服务器ip地址
	int _sockfd;//socket返回值
};

使用系统调用socket()时,相当于打开了服务器的网卡,在系统中会创建一个struct file类型的结构体,来描述网卡的信息,和文件操作类型,所以会返回一个sockfd,这其实就是一个文件描述符。所以服务器的成员变量有上面代码所示的三个。

图

在构造函数构造服务器对象的时候,需要传入端口号port和IP地址。

  • 端口号port是指定的,一旦指定就不能再更改,这里本喵指定为8080。
  • IP地址是服务器的IP地址。

图
shell上使用指令ifconfig,可以看到服务器的IP信息,如上图所示,红色框中的inet:127.0.0.1是本地环回,使用这个IP地址不会经过物理层将数据发出去,而且从链路层又返回到了当前计算机。

图

如上图所示,数据从当前计算机上的客户端进程流向了当前计算机上的服务的进程。所以可以使用本地环回IP地址来进行测试。

但是我们知道,每台计算机在网络中都有一个公网IP,这个IP和本地回环不一样,还有局域网IP等等。存在这么多IP,都标识着服务器这一台计算机。

如果服务器仅仅绑定本地换行的IP地址,当另一台计算机的服务端想要通过公网IP地址发起申请的时候,由于两个IP地址不一样,所以就会忽略客户端的申请。

  • 为了能够让服务端接收所有的网络请求,将服务器的IP地址绑定为0.0.0.0,转换成uint32_t类型就是0。

其实每台计算机绑定的都不止一个IP,当使用bind绑定的IP地址是0.0.0.0的时候,这台计算机收到的所有网络请求都会接收,并且根据相应的端口进行处理。

所以本喵将udpSever中构造函数IP地址形参设为缺省值:

static const string defaultIP = "0.0.0.0";
//构造函数
udpSever(const uint16_t &port, const string &ip = defaultIP)
    : _port(port), _ip(ip), _sockfd(-1)
{}

initSever()

在这个初始化函数中创建socket,并且和操作系统进行绑定bind

图

  • 使用socket()系统调用创建套接字,并且进行判断,创建失败打印相应错误信息并且退出进程。
  • 填充sockaddr_in结构体:

首先需要对该结构体进行初始化,也就是清0和填充,使用函数bzero进行初始化:

图
这是一个库函数,需要包含头文件<strings.h>,该函数的作用和memset一样,不同之处在于bzero只能清零,第一个参数是目标地址,第二个参数是要清零的字节数。

在填充sockaddr_in结构体的时候,将地址类型sin_family填充为AF_INET表示网络通信。

在填充端口号sin_port的时候,需要使用htons()函数,将主机字节序转换成网络字节序,然后再进行填充。


我们平时看到的IP地址是都是点分十进制的,如127.0.0.1,这是一个字符串,这是为了方便用户阅读,其实在网络中,IP地址是一个uint32_t类型的数据。

所以在填充IP地址地址的时候,需要先将点分十进制的IP地址,使用函数inet_addr()转换成网络类型的uint32_t类型,然后再进行填充。

图
形参是const char* cp,也就是点分十进制的IP地址,返回类型是in_addr_t

typedef uint32_t in_addr_t;

其实就是uint32_t类型,只是重命名成了in_addr_t


再使用bind将IP地址和端口号和系统绑定,在绑定之前,sockaddr_in是在栈区上的,操作系统根本不知道设的值是什么,只有使用bind之后,才真正将IP地址和端口号绑定到了操作系统中。

在绑定完成后进行判断,如果失败,打印相应错误信息,并且退出进程。

图
这里使用枚举来列举错误码,在创建套接字和绑定的时候,如果发生了错误,在进程退出时给与相关的错误码。

至此,UDP服务器网络通信的预备工作全部完成,此时就可以进行网络通信了。


start()

在预备工作做好以后,还需启动服务器,服务器进程是一个常驻内存的进程,也就是一个while(1)的死循环,在这个循环中进行网络数据的接收,处理,以及发送。

图
上图所示的系统调用recvfrom()用来接收网络中发过来的数据,也就是从套接字中接收。

  • 第一个参数是sockfd,是创建套接字时返回的文件描述符fd
  • 第二个参数buf是用来存储从网络中读取下来的数据的。
  • 第三个参数是buf缓冲区的大小。
  • 第四个参数flags是读取的方式,一般设置为0,即阻塞读取数据。
  • 第五个参数sockaddr* src_addr是一个输出型参数,同样传参sockaddr_in结构体,系统会自动对这个结构体进行填充,可以获取数据的来源,包括发送方的地址类型,端口号port以及IP地址。
  • 第六个参数addrlen是第五个输出型结构体变量的大小所在的地址,注意类型是socklen_t*的,和bind的时候不一样。
  • 返回值ssize_t,返回读取到的数据个数,单位是字节,如果读取失败则返回-1。

图
如上图所示代码,服务端启动后便常驻内存,网络中有数据便读,没有数据便阻塞等待。

读取数据时,除了数据本身,还有发送方的数据,都在sockaddr_in类型的结构体peer中。

从网络中获取到发送方的IP地址是uint32_t类型的,所以需要使用inet_ntoa()函数将其转换成点分十进制的,方便用户阅读。

图
红色框中所示就是inet_ntoa的函数声明,以字符串的形式返回点分十进制的IP地址,参数类型是struct in_addr in

图
in_addr结构体中的变量其实就是一个uint32_t类型的,但是这里传参的时候,不需要具体到s_addr,直接传结构体就行,和绑定IP地址时填充sockaddr_in不一样。

此时我们就可以创建服务器对象并且开始使用他了。


udpSever.cpp

图
在运行可执行程序的时候,需要输入命令行参数,由于IP地址设置了缺省值,所以命令行参数只需要一个端口号即可。

如果执行时,命令行参数没有输对,进程就会退出,并且打印使用信息,如果命令行参数输入正确,就向下执行。

图
使用一个智能指针unique_ptr来管理服务器对象,然后再初始化initSever,之后再开始执行服务器进程start

图
此时服务器进程就开始运行了。那么我们怎么知道它是否运行了呢?

图
使用指令sudo netstat -nuap可以查看当前服务器上的网络进程,如上图所示。

  • sudo提权,如果不加,打印的信息不完整。

可以看到,存在一个网络进程,IP地址是0.0.0.0,端口号是8080,进程pid是376,进程名字是udpSever,和我们设定的一样。

至此服务端的工作就做完了,只有客户端发送数据,服务端就可以收到。

🥣客户端实现

class udpClient
{
public:
	//构造函数,初始化服务端IP地址和端口号
	udpClient(const string& severip, const uint16_t& port)
		:_severip(severip),_severport(severport),_sockfd(-1)
	{}
private:
	int _socketfd;//套接字文件描述符
	string _severip;//服务器IP地址
	uint16_t _severport;//服务器端口号
};

对于客户端,只需要知道服务端计算机的IP地址和端口号port即可,不需要知道自己的,在使用构造函数构建客户端对象的时候,需要传入服务端的IP地址和端口号。

  • 客户端有无数个,但是服务端只有一个。

initClient

图
客户端的初始化中,只需要创建套接字即可,不需要显式bind

  • 对于客户端而言,指定唯一的IP地址和端口号没有意义,除了服务器以外,没有其他用户会向客户端发起网络请求。

run()

tu
在发送数据之前,需要先告诉操作系统要把数据发送到哪里,所以需要指定服务器的IP地址和端口号port,所以同样需要填充sockaddr_in结构体,创建一个sever变量。

  • 指定地址类型为网络通信,将点分十进制的IP地址转换成uint32_t类型,再将端口号转换成网络字节序。

向网络中发送数据的时候使用到的系统调用是sendto()

图

  • 第一个参数sockfd是创建的套接字的文件描述符。
  • 第二个参数buf是要发送的数据所在的缓冲区。
  • 第三个参数len是要发生的数据个数,以字节为单位。
  • 第四个参数flags是发送方式,一般设置为0,表示阻塞发送。
  • 第五个参数dest_addr是存放服务器IP地址和端口号port的sockaddr_in结构体变量,在传参的时候需要强转为struct sockaddr*
  • 第六个参数,是第五个参数中结构体变量的大小,以字节为单位。

在调用sendto向网络中发送数据的时候,操作系统会自动将客户端的IP地址和端口号绑定,并且一起发送出去。

  • 由于客户端不需要指定端口号,IP地址也是固定的,所以操作系统会随机给客户端分配一个端口号使用。

在服务端收到客户端的报文中,就包含着客户端的IP地址和端口号,通过从recvfrom填充的sockaddr_in结构体中可以提取客户端操作系统随机分配的那个端口号。

服务端同样可以根据这个IP地址和端口号再给客户端发送消息。

udpClient.cpp

图
在运行udpClient的时候,需要传入两个命令行参数,一个是服务器的IP地址,一个是服务器的端口号port。

同样使用unique_ptr智能指针来管理客户端对象,创建好后进行初始化,然后开始运行。

图
由于本喵是在一台服务器上测试客户端和服务端,所以使用的是本地环回,本地环回的IP地址就是127.0.0.1,所以第一个命令行参数就是这个,第二个命令行参数是服务器指定的端口号,前面本喵指定的是8080

  • IP地址使用公网IP,局域网IP,还有本地环回IP,服务端都可以收到客户端的请求,因为服务端绑定的是0.0.0.0

可以看到,在客户端运行起来以后,打印信息socket success: 3,打印的这个数字就是套接字的文件描述符,之所以是3,是因为0,1,2被标准输入输出以及标准错误占用了。

  • 更进一步说明了,套接字创建好后,在操作系统中就是一个文件,也是一个结构体。

此时客户端和服务端都执行起来了,双方就可以进行通信了:

图
运行客户端进程,并且输入要发送的数据内容,如上图所示。

图
可以看到,客户端发送的内容,服务端收到了,并且还有客户端的IP地址和端口号。

图
重新运行客户端程序,发送数据,可以看到,客户端新收到的数据中,端口号变了,从45839变成了47530,这是因为客户端的端口号是由操作系统分配的,并不是自己指定的,所以每次运行时端口号都不一样。

🍚用户层与网络的解耦

上面我们成功的让客户端和服务端建立起了连接,并且能正常进行网络通信,现在我们让服务端用户层和网络进行解耦。

图
在服务端中增加回调函数_callback,使用function包装,回调函数的类型是void(int, string, uint16_t, string)

现在我们实现一个英汉互译的词典,客户端输入英文单词,服务端查它对应的汉语,并且返回给客户端。

图

  • 先在当前路径下创建一个dict.txt文件,里面放入英文和对应的汉语。
  • main函数中,加载词典,调用initDict()函数。
  • 在初始化函数中,使用ifstream类打开dict.txt词典文本,再调用cutString()将文本中的英文和中文分开。
  • 将分开的英文和中文以键值对的形式插入全局变量unordered_map中。

此时我们的词典就建成了,本喵只是简单的写了几个英文单词。

服务器用户层的回调函数:

图
在回调函数中,使用从网络中接收到的客户端请求,也就是英文单词,在unordered_map中查找对应的中文意思,如果找到了,将中文赋值给response_message,如果没有找到,则将unkonwn赋值给response_message

然后将查询的结构response_message通过网络发送给客户端。

  • 可以看到,此时就实现了用户层和网络的解耦,用户要进行的操作完全由用户自己在回调函数中实现即可。
  • 网络底层的配置不用做任何改变。

由于客户端还要接收服务端的反馈信息,所以客户端的网络底层需要做一些修改:

图
在客户端发送完数据以后,便开始等待服务端的反馈,如果服务端发送来信息则接收,并且打印在屏幕上。

这个例子中,打印的就是客户端发送英文单词对应的中文意思。

tu
客户端程序运行起来后,输入英文单词,如上图所示红色框,就会得到对应的中文,如上图绿色框所示。

  • 翻译的过程是由服务器完成的,客户端只需要将英文单词交给服务器即可。

图
服务端可以看到客户端发来的英文单词,如上图绿色框中所示。具体的翻译逻辑是在回调函数中实现的。

如此一来我们英汉互译词典的功能就实现了。

还可以做一些相关的改进,此时的客户端,发送数据和读取数据都是阻塞的,在发送完毕后只能等数据到来再去读取,程序不会执行下去。可以将发送和读取弄成两个线程去执行,此时发送和读取就不不干涉了。

🍚总结

UDP协议的网络通信非常简单,只需要使用socket()创建套接字,服务器的话需要显式bind,客户端不需要显式bind,还有就是需要使用recvfrom从网络中(套接字)中读取数据,以及使用sendto向网络中(套接字)中发送数据。

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

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

相关文章

UFS 14 - UFS RPMB安全读写命令

UFS 14 - UFS RPMB安全读写命令 1 SECURITY PROTOCOL IN/OUT Commands1.1 CDB format of SECURITY PROTOCOL IN/OUT commands1.2 Supported security protocols list description1.3 Certificate data description 2 CDB format of SECURITY PROTOCOL IN/OUT commands demo2.1 …

Spring Boot原理分析(一):项目启动流程、自动装配

文章目录 一、项目启动流程二、SpringBootApplication.java源码解析1.准备工作2.源码3.自定义注解4.组合注解5.注解ComponentScan过滤器 6.注解SpringBootConfigurationConfiguration 7.注解EnableAutoConfiguration&#xff08;1&#xff09;Spring手动装配使用XML配置文件使用…

Nerf论文阅读笔记Neuralangelo: High-Fidelity Neural Surface Reconstruction

Neuralangelo&#xff1a;高保真神经表面重建 公众号&#xff1a;AI知识物语&#xff1b;B站暂定&#xff1b;知乎同名 视频入门介绍可以参考 B站——CVPR 2023最新工作&#xff01;Neuralangelo&#xff1a;高保真Nerf表面重建 https://www.bilibili.com/video/BV1Ju411W7…

杨氏矩阵,字符串左旋,字符串旋转结果题目解析

杨氏矩阵 题目要求:有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 示例 分析:我们仔细分析&#xff0c;不难发现&#xff0c;对于杨氏矩阵老说&#xff0c;右上角和左下…

leetcode1020. 飞地的数量

https://leetcode.cn/classic/problems/number-of-enclaves/description/ 给你一个大小为 m x n 的二进制矩阵 grid &#xff0c;其中 0 表示一个海洋单元格、1 表示一个陆地单元格。 一次 移动 是指从一个陆地单元格走到另一个相邻&#xff08;上、下、左、右&#xff09;的…

哈希的应用->位图

ps&#xff1a;左移位并不是向左移动位&#xff0c;而是低数据位向高数据位挪动 位图&#xff08;主要接口&#xff0c;set(size_t)标识、reset(size_t)取消、test(size_t) 查看 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一…

做软件测试到底要不要学编程?

乔布斯曾经说过“每个人都应该学习编程&#xff0c;因为它会教你如何思考”&#xff0c;看&#xff0c;乔帮主都觉得所有人都应该学编程&#xff0c;那你说做测试的要不要学&#xff1f;当然要。 作为测试人员&#xff0c;除了上面这个原因&#xff0c;我觉得如果会编程&#x…

Android架构之MVC,MVP,MVVM解析

MVC架构 View&#xff1a;Acitivity(View)、Fragment(View)视图&#xff0c;在android里xml布局转成View后&#xff0c;加载到了Activity/Fragment里了。 Controller&#xff1a;Controller对应着Activity/Fragment&#xff0c;绑定UI&#xff0c;处理各种业务。 Model&#xf…

python接口自动化(三十)--html测试报告通过邮件发出去——中(详解)

简介 上一篇&#xff0c;我们虽然已经将生成的最新的测试报告发出去了&#xff0c;但是MIMEText 只能发送正文&#xff0c;无法带附件&#xff0c;因此我还需要继续改造我们的代码&#xff0c;实现可以发送带有附件的邮件。发送带附件的需要导入另外一个模块 MIMEMultipart。还…

java版电子招标采购系统源码之电子招标采购实践与展望-招标采购管理系统

统一供应商门户 便捷动态、呈现丰富 供应商门户具备内外协同的能力&#xff0c;为外部供应商集中推送展示与其相关的所有采购业务信息&#xff08;历史合作、考察整改&#xff0c;绩效评价等&#xff09;&#xff0c;支持供应商信息的自助维护&#xff0c;实时风险自动提示。…

springboot+MySQL实现4S店车辆管理系统

本系统为了数据库结构的灵活性所以打算采用MySQL来设计数据库&#xff0c;而java技术&#xff0c;B/S架构则保证了较高的平台适应性。本文主要介绍了本系统的开发背景&#xff0c;所要完成的功能和开发的过程&#xff0c;主要说明了系统设计的重点、设计思想。

计算机体系结构基础知识介绍之使用多问题和静态调度来利用 流水线

为了提高处理器的性能&#xff0c;我们需要让每个时钟周期内发出多条指令&#xff0c;而不是只发出一条。这种多发射处理器有三种主要类型&#xff1a; 1. 静态调度的超标量处理器 2. VLIW&#xff08;非常长指令字&#xff09;处理器 3. 动态调度的超标量处理器。 这三种类型的…

lua 请求ftp服务器数据,下载文件

1、装入ftp库 2、调用ftp的get()方法 3、get()方法参数格式&#xff1a; 4、将返回到的数据写入文件中 例如&#xff0c;本次获取专利数据系统 http://patdata1.cnipa.gov.cn/ 的ftp站点数据 local ftp require("socket.ftp")--此处我没填端口号 file,err ftp.g…

findfont: Font family ‘Times New Roman‘ not found.

问题 Linux 使用 matplotlib.pyplot 画图时为了使字体和英文论文中的 Times of Roman 一致&#xff0c;通常会用到如下文本格式 font1 {family: Times New Roman, # x and y labelsweight: normal,size: 16}但在实际使用时会出现如下报警信息&#xff1a; findfont: Font …

element ui 导入模块的封装

导入组件的封装 <template><Modal :visible"visible" title"导入" onSave"onSave" onCancal"closeDialog"><template #default><el-upload ref"upload" class"upload-demo"action"ht…

Scala中使用 break 和 continue

Scala中没有 break 和 continue 关键字&#xff0c;但是我们可以用 Breaks 类提供的相应方法来实现对应功能。 在Java中&#xff0c;break continue return的区别 1、break&#xff1a;break不仅可以结束其所在的循环&#xff0c;还可结束其外层循环&#xff0c;但一次只能结束…

Vulnhub: Hackable:II靶机

kali&#xff1a;192.168.111.111 靶机&#xff1a;192.168.111.142 信息收集 端口扫描 nmap -A -sC -v -sV -T5 -p- --scripthttp-enum 192.168.111.142 网站的files目录 ftp存在匿名登录&#xff0c;所在目录为网站的files目录 ftp上传反弹shell 提权 目标根目录下的.ru…

预付费智能水表远程控制系统

预付费智能水表远程控制系统是一种基于物联网技术的智能水表管理系统&#xff0c;它通过远程通信技术和云计算平台&#xff0c;实现了对水表的实时监控、数据采集、费用计算、远程控制等功能。该系统不仅可以提高水务公司的管理效率&#xff0c;还可以为用户提供更加便捷、可靠…

[疑难杂症2023-004]停止服务器自动启动的服务,解决端口占用的问题

本文由Markdown语法编辑器编辑完成。 1. 背景 前段时间&#xff0c;在linux上启动一个目录下的docker-compose.yml中的服务时&#xff0c;遇到了一个3000端口被占用的问题. 凭借经验&#xff0c;一般可能是之前的服务没被正常的停止掉&#xff0c;导致该服务占用的端口未被释…

阿里云——网站建设:动态网站建设(知识点)

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 目录 写在前面 课程目标 一.简单搭建动态网站 1.网站搭建类型 &#xff08;1&#xff0…