Linux网络:网络编程套接字

news2024/9/29 7:39:40

socket 套接字

socket常见API

创建套接字:(TCP/UDP,客户端+服务器)

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

绑定端口号:(TCP/UDP,服务器)

int listen(int sockfd, int backlog);

监听套接字:(TCP,服务器)

int listen(int sockfd, int backlog);

接收请求:(TCP,服务器)

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

建立连接:(TCP,客户端)

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

sockaddr结构

sockaddr结构的出现

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

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

此时当我们在传递在传参时,就不用传入sockeaddr_in或sockeaddr_un这样的结构体,而统一传入sockeaddr这样的结构体。在设置参数时就可以通过设置协议家族这个字段,来表明我们是要进行网络通信还是本地通信,在这些API内部就可以提取sockeaddr结构头部的16位进行识别,进而得出我们是要进行网络通信还是本地通信,然后执行对应的操作。此时我们就通过通用sockaddr结构,将套接字网络通信和本地通信的参数类型进行了统一。


UDP网络程序

服务端创建套接字

把服务器封装成一个类,当我们定义出一个服务器对象后需要马上初始化服务器,而初始化服务器需要做的第一件事就是创建套接字。

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函数属于什么类型的接口?

网络协议栈是分层的,按照TCP/IP四层模型来说,自顶向下依次是应用层、传输层、网络层和数据链路层。而我们现在所写的代码都叫做用户级代码,也就是说我们是在应用层编写代码,因此我们调用的实际是下三层的接口,而传输层和网络层都是在操作系统内完成的,也就意味着我们在应用层调用的接口都叫做系统调用接口。

socket函数是被谁调用的?

socket这个函数是被程序调用的,但并不是被程序在编码上直接调用的,而是程序编码形成的可执行程序运行起来变成进程,当这个进程被CPU调度执行到socket函数时,然后才会执行创建套接字的代码,也就是说socket函数是被进程所调用的。

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结构体来维护的。而文件缓冲区对于打开的普通文件来说对应的一般是磁盘,但对于现在打开的“网络文件”来说,这里的文件缓冲区对应的就是网卡。

对于一般的普通文件来说,当用户通过文件描述符将数据写到文件缓冲区,然后再把数据刷到磁盘上就完成了数据的写入操作。而对于现在socket函数打开的“网络文件”来说,当用户将数据写到文件缓冲区后,操作系统会定期将数据刷到网卡里面,而网卡则是负责数据发送的,因此数据最终就发送到了网络当中。

服务端创建套接字

当我们在进行初始化服务器创建套接字时,就是调用socket函数创建套接字,创建套接字时我们需要填入的协议家族就是AF_INET,因为我们要进行的是网络通信,而我们需要的服务类型就是SOCK_DGRAM,因为我们现在编写的UDP服务器是面向数据报的,而第三个参数之间设置为0即可。

class UdpServer
{
public:
	bool InitServer()
	{
		//创建套接字
		_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
		if (_sockfd < 0){ //创建套接字失败
			std::cerr << "socket error" << std::endl;
			return false;
		}

		std::cout << "socket create success, sockfd: " << _sockfd << std::endl;
		
		return true;
	}
	~UdpServer()
	{
		if (_sockfd >= 0){
			close(_sockfd);
		}
	};
private:
	int _sockfd; //文件描述符
};
int main()
{
	UdpServer* svr = new UdpServer();
	svr->InitServer();
	return 0;
}

运行程序后可以看到套接字是创建成功的,对应获取到的文件描述符就是3,这也很好理解,因为0、1、2默认被标准输入流、标准输出流和标准错误流占用了,此时最小的、未被利用的文件描述符就是3。

服务端绑定

现在套接字已经创建成功了,但作为一款服务器来讲,如果只是把套接字创建好了,那我们也只是在系统层面上打开了一个文件,操作系统将来并不知道是要将数据写入到磁盘还是刷到网卡,此时该文件还没有与网络关联起来。

由于现在编写的是不面向连接的UDP服务器,所以初始化服务器要做的第二件事就是绑定。

bind函数

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。
  • addr:网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:传入的addr结构体的长度。

返回值说明:

  • 绑定成功返回0,绑定失败返回-1,同时错误码会被设置。

struct sockaddr_in结构体

在绑定时需要将网络相关的属性信息填充到一个结构体当中,然后将该结构体作为bind函数的第二个参数进行传入,这实际就是struct sockaddr_in结构体。

我们可以用grep命令在/usr/include目录下查找该结构,此时就可以找到定义该结构的文件。

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

剩下的字段一般不做处理,当然你也可以进行初始化。

其中sin_addr的类型是struct in_addr,实际该结构体当中就只有一个成员,该成员就是一个32位的整数,IP地址实际就是存储在这个整数当中的。

在进行绑定的时候需要将IP地址和端口号告诉对应的网络文件,此时就可以改变网络文件当中文件操作函数的指向,将对应的操作函数改为对应网卡的操作方法,此时读数据和写数据对应的操作对象就是网卡了,所以绑定实际上就是将文件和网络关联起来。

增加IP地址和端口号

由于绑定时需要用到IP地址和端口号,因此我们需要在服务器类当中引入IP地址和端口号,在创建服务器对象时需要传入对应的IP地址和端口号,此时我们就可以根据传入的IP地址和端口号对对应的成员进行初始化。

class UdpServer
{
public:
	UdpServer(std::string ip, int port)
		:_sockfd(-1)
		,_port(port)
		,_ip(ip)
	{};
	~UdpServer()
	{
		if (_sockfd >= 0){
			close(_sockfd);
		}
	};
private:
	int _sockfd; //文件描述符
	int _port; //端口号
	std::string _ip; //IP地址
};

服务端绑定

套接字创建完毕后我们就需要进行绑定了,但在绑定之前我们需要先定义一个struct sockaddr_in结构,将对应的网络属性信息填充到该结构当中。由于该结构体当中还有部分选填字段,因此我们最好在填充之前对该结构体变量里面的内容进行清空,然后再将协议家族、端口号、IP地址等信息填充到该结构体变量当中。

需要注意的是,在发送到网络之前需要将端口号设置为网络序列,由于端口号是16位的,因此我们需要使用前面说到的htons函数将端口号转为网络序列。此外,由于网络当中传输的是整数IP,我们需要调用inet_addr函数将字符串IP转换成整数IP,然后再将转换后的整数IP进行设置。

当网络属性信息填充完毕后,由于bind函数提供的是通用参数类型,因此在传入结构体地址时还需要将struct sockaddr_in*强转为struct sockaddr*类型后再进行传入。

class UdpServer
{
public:
	UdpServer(std::string ip, int port)
		:_sockfd(-1)
		,_port(port)
		,_ip(ip)
	{};
	bool InitServer()
	{
		//创建套接字
		_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
		if (_sockfd < 0){ //创建套接字失败
			std::cerr << "socket error" << std::endl;
			return false;
		}

		std::cout << "socket create success, sockfd: " << _sockfd << std::endl;

		//填充网络通信相关信息
		struct sockaddr_in local;
		memset(&local, '\0', sizeof(local));
		local.sin_family = AF_INET;
		local.sin_port = htons(_port);
		local.sin_addr.s_addr = inet_addr(_ip.c_str());                                                                                                                                                                                                                                                                                                                                                                                                        

		//绑定
		if (bind(_sockfd, (struct sockaddr*)&local, sizeof(sockaddr)) < 0){ //绑定失败
			std::cerr << "bind error" << std::endl;
			return false;
		}

		std::cout << "bind success" << std::endl;

		return true;
	}
	~UdpServer()
	{
		if (_sockfd >= 0){
			close(_sockfd);
		}
	};
private:
	int _sockfd; //文件描述符
	int _port; //端口号
	std::string _ip; //IP地址
};

字符串IP VS 整数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相互转换的方式

转换的方式有很多,比如我们可以定义一个位段A,位段A当中有四个成员,每个成员的大小都是8个比特位,这四个成员就依次表示IP地址的四个区域,一共32个比特位。

然后我们再定义一个联合体IP,该联合体当中有两个成员,其中一个是32位的整数,其代表的就是整数IP,还有一个就是位段A类型的成员,其代表的就是字符串IP。

由于联合体的空间是成员共享的,因此我们设置IP和读取IP的方式如下:

  • 当我们想以整数IP的形式设置IP时,直接将其赋值给联合体的第一个成员就行了。
  • 当我们想以字符串IP的形式设置IP时,先将字符串分成对应的四部分,然后将每部分转换成对应的二进制序列依次设置到联合体中第二个成员当中的p1、p2、p3和p4就行了。
  • 当我们想取出整数IP时,直接读取联合体的第一个成员就行了。
  • 当我们想取出字符串IP时,依次获取联合体中第二个成员当中的p1、p2、p3和p4,然后将每一部分转换成字符串后拼接到一起就行了。

inet_addr函数

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

in_addr_t inet_addr(const char *cp);

该函数使用起来非常简单,我们只需传入待转换的字符串IP,该函数返回的就是转换后的整数IP。除此之外,inet_aton函数也可以将字符串IP转换成整数IP,不过该函数使用起来没有inet_addr简单。

inet_ntoa函数

char *inet_ntoa(struct in_addr in);

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

运行服务器

UDP服务器的初始化就只需要创建套接字和绑定就行了,当服务器初始化完毕后我们就可以启动服务器了。

服务器实际上就是在周而复始的为我们提供某种服务,服务器之所以称为服务器,是因为服务器运行起来后就永远不会退出,因此服务器实际执行的是一个死循环代码。由于UDP服务器是不面向连接的,因此只要UDP服务器启动后,就可以直接读取客户端发来的数据。

recvfrom函数

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:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:调用时传入期望读取的src_addr结构体的长度,返回时代表实际读取到的src_addr结构体的长度,这是一个输入输出型参数。

返回值说明:

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

启动服务器函数

现在服务端通过recvfrom函数读取客户端数据,我们可以先将读取到的数据当作字符串看待,将读取到的数据的最后一个位置设置为'\0',此时我们就可以将读取到的数据进行输出,同时我们也可以将获取到的客户端的IP地址和端口号也一并进行输出。

需要注意的是,我们获取到的客户端的端口号此时是网络序列,我们需要调用ntohs函数将其转为主机序列再进行打印输出。同时,我们获取到的客户端的IP地址是整数IP,我们需要通过调用inet_ntoa函数将其转为字符串IP再进行打印输出。

class UdpServer
{
public:
	void Start()
	{
#define SIZE 128
		char buffer[SIZE];
		for (;;){
			struct sockaddr_in peer;
			socklen_t len = sizeof(peer);
			ssize_t size = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
			if (size > 0){
				buffer[size] = '\0';
				int port = ntohs(peer.sin_port);
				std::string ip = inet_ntoa(peer.sin_addr);
				std::cout << ip << ":" << port << "# " << buffer << std::endl;
			}
			else{
				std::cerr << "recvfrom error" << std::endl;
			}
		}
	}
private:
	int _sockfd; //文件描述符
	int _port; //端口号
	std::string _ip; //IP地址
};

引入命令行参数

鉴于构造服务器时需要传入IP地址和端口号,我们这里可以引入命令行参数。此时当我们运行服务器时在后面跟上对应的IP地址和端口号即可。

由于云服务器的原因,后面实际不需要传入IP地址,因此在运行服务器的时候我们只需要传入端口号即可,目前我们就手动将IP地址设置为127.0.0.1。IP地址为127.0.0.1实际上等价于localhost表示本地主机,我们将它称之为本地环回,相当于我们一会先在本地测试一下能否正常通信,然后再进行网络通信的测试。

netstat常用选项说明:

  • -n:直接使用IP地址,而不通过域名服务器。
  • -l:显示监控中的服务器的Socket。
  • -t:显示TCP传输协议的连线状况。
  • -u:显示UDP传输协议的连线状况。
  • -p:显示正在使用Socket的程序识别码和程序名称。

可以尝试去掉-n选项再查看,此时原本显示IP地址的地方就变成了对应的域名服务器。

其中netstat命令显示的信息中,Proto表示协议的类型,Recv-Q表示网络接收队列,Send-Q表示网络发送队列,Local Address表示本地地址,Foreign Address表示外部地址,State表示当前的状态,PID表示该进程的进程ID,Program name表示该进程的程序名称。

其中Foreign Address写成0.0.0.0:*表示任意IP地址、任意的端口号的程序都可以访问当前进程。

客户端创建套接字

同样的,我们把客户端也封装成一个类,当我们定义出一个客户端对象后也是需要对其进行初始化,而客户端在初始化时也需要创建套接字,之后客户端发送数据或接收数据也就是对这个套接字进行操作。

客户端创建套接字时选择的协议家族也是AF_INET,需要的服务类型也是SOCK_DGRAM,当客户端被析构时也可以选择关闭对应的套接字。与服务端不同的是,客户端在初始化时只需要创建套接字就行了,而不需要进行绑定操作。

class UdpClient
{
public:
	bool InitClient()
	{
		//创建套接字
		_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
		if (_sockfd < 0){
			std::cerr << "socket create error" << std::endl;
			return false;
		}
		return true;
	}
	~UdpClient()
	{
		if (_sockfd >= 0){
			close(_sockfd);
		}
	}
private:
	int _sockfd; //文件描述符
};

关于客户端的绑定问题

首先,由于是网络通信,通信双方都需要找到对方,因此服务端和客户端都需要有各自的IP地址和端口号,只不过服务端需要进行端口号的绑定,而客户端不需要。

因为服务器就是为了给别人提供服务的,因此服务器必须要让别人知道自己的IP地址和端口号,IP地址一般对应的就是域名,而端口号一般没有显示指明过,因此服务端的端口号一定要是一个众所周知的端口号,并且选定后不能轻易改变,否则客户端是无法知道服务端的端口号的,这就是服务端要进行绑定的原因,只有绑定之后这个端口号才真正属于自己,因为一个端口只能被一个进程所绑定,服务器绑定一个端口就是为了独占这个端口。

而客户端在通信时虽然也需要端口号,但客户端一般是不进行绑定的,客户端访问服务端的时候,端口号只要是唯一的就行了,不需要和特定客户端进程强相关。

如果客户端绑定了某个端口号,那么以后这个端口号就只能给这一个客户端使用,就是这个客户端没有启动,这个端口号也无法分配给别人,并且如果这个端口号被别人使用了,那么这个客户端就无法启动了。所以客户端的端口只要保证唯一性就行了,因此客户端端口可以动态的进行设置,并且客户端的端口号不需要我们来设置,当我们调用类似于sendto这样的接口时,操作系统会自动给当前客户端获取一个唯一的端口号。

也就是说,客户端每次启动时使用的端口号可能是变化的,此时只要我们的端口号没有被耗尽,客户端就永远可以启动。

启动客户端

增加服务端IP地址和端口号

作为一个客户端,它必须知道它要访问的服务端的IP地址和端口号,因此在客户端类当中需要引入服务端的IP地址和端口号,此时我们就可以根据传入的服务端的IP地址和端口号对对应的成员进行初始化。

class UdpClient
{
public:
	UdpClient(std::string server_ip, int server_port)
		:_sockfd(-1)
		,_server_port(server_port)
		,_server_ip(server_ip)
	{}
	~UdpClient()
	{
		if (_sockfd >= 0){
			close(_sockfd);
		}
	}
private:
	int _sockfd; //文件描述符
	int _server_port; //服务端端口号
	std::string _server_ip; //服务端IP地址
};

当客户端初始化完毕后我们就可以将客户端运行起来,由于客户端和服务端在功能上是相互补充的,既然服务器是在读取客户端发来的数据,那么客户端就应该想服务端发送数据。

sendto函数

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:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。 addrlen:传入dest_addr结构体的长度。

返回值说明:

  • 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置。

启动客户端函数

现在客户端要发送数据给服务端,我们可以让客户端获取用户输入,不断将用户输入的数据发送给服务端。

需要注意的是,客户端中存储的服务端的端口号此时是主机序列,我们需要调用htons函数将其转为网络序列后再设置进struct sockaddr_in结构体。同时,客户端中存储的服务端的IP地址是字符串IP,我们需要通过调用inet_addr函数将其转为整数IP后再设置进struct sockaddr_in结构体。

class UdpClient
{
public:
	void Start()
	{
		std::string msg;
		struct sockaddr_in peer;
		memset(&peer, '\0', sizeof(peer));
		peer.sin_family = AF_INET;
		peer.sin_port = htons(_server_port);
		peer.sin_addr.s_addr = inet_addr(_server_ip.c_str());

		for (;;){
			std::cout << "Please Enter# ";
			getline(std::cin, msg);
			sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)&peer, sizeof(peer));
		}
	}
private:
	int _sockfd; //文件描述符
	int _server_port; //服务端端口号
	std::string _server_ip; //服务端IP地址
};

引入命令行参数

鉴于构造客户端时需要传入对应服务端的IP地址和端口号,我们这里也可以引入命令行参数。当我们运行客户端时直接在后面跟上对应服务端的IP地址和端口号即可。

int main(int argc, char* argv[])
{
	if (argc != 3){
		std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
		return 1;
	}
	std::string server_ip = argv[1];
	int server_port = atoi(argv[2]);
	UdpClient* clt = new UdpClient(server_ip, server_port);
	clt->InitClient();
	clt->Start();
	return 0;
}

需要注意的是,argv数组里面存储的是字符串,而端口号是一个整数,因此需要使用atoi函数将字符串转换成整数。然后我们就可以用这个IP地址和端口号来构造客户端了,客户端构造完成并初始化后就可以调用Start函数启动客户端了。

本地测试

现在服务端和客户端的代码都已经编写完毕,我们可以先进行本地测试,此时服务器没有绑定外网,绑定的是本地环回。现在我们运行服务器时指明端口号为8080,再运行客户端,此时客户端要访问的服务器的IP地址就是本地环回127.0.0.1,服务端的端口号就是8080。

客户端运行之后提示我们进行输入,当我们在客户端输入数据后,客户端将数据发送给服务端,此时服务端再将收到的数据打印输出,这时我们在服务端的窗口也看到我们输入的内容。

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

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

相关文章

Python 算法交易实验89 QTV200日常推进-模式思考

说明 过去几天大A的表现还是比较戏剧化的。 让我想到了&#xff1a; 1 价稳量缩模式。之前很长一段时间都是这种状态&#xff0c;最终还是大爆发了&#xff0c;这个可取。2 周期模式。假设价格是一个周期为T(T可变)的正弦波&#xff0c;所以最终还是回到了几个月前的位置&…

Java 常用运算符简单总结

目录 1. 运算符​编辑 1.1 算术运算符​编辑 1.1.1 除法运算符​编辑 1.1.2 取模&#xff08;取余&#xff09;运算符​编辑 1.1.3 自增/减运算符​编辑​编辑 1.2 关系运算符 1.3 逻辑运算符 1.3.1 逻辑/短路与 1.3.2 逻辑/短路或 1.4 取反操作 1.5 逻辑异或 1.4 …

第三节-类与对象(中)

1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类&#xff08;空类大小为1&#xff09;。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;…

828华为云征文|华为云弹性云服务器FlexusX实例下的Nginx性能测试

本文写的是华为云弹性云服务器FlexusX实例下的Nginx性能测试 目录 一、华为云弹性云服务器FlexusX实例简介二、测试环境三、测试工具四、测试方法五、测试结果 下面是华为云弹性云服务器FlexusX实例下的Nginx性能测试。 一、华为云弹性云服务器FlexusX实例简介 华为云弹性云服…

Woocommerce怎么分类显示产品?如何将Shopify的产品导入到Woocommerce?

WooCommerce作为WordPress的一个电子商务插件&#xff0c;功能强大、使用简洁&#xff0c;能够轻松集成到WordPress网站中&#xff0c;为用户提供了一个完整的在线商店解决方案&#xff0c;在国外还是挺受欢迎的。 Woocommerce怎么分类显示产品&#xff1f; 在Woocommerce中&a…

数据资产新范式,URP城市焕新平台东博会首发!

城市数据资产蕴藏着巨大的宝藏。今年1月&#xff0c;国家数据局印发《“数据要素”三年行动计划&#xff08;2024—2026年&#xff09;》&#xff0c;将“数据要素智慧城市”上升为“数据要素”计划的重要部分&#xff0c;加速释放城市数据资产价值。 高质量发展以数据要素驱动…

聚星文社最新风格图库角色

聚星文社最新风格图库角色涵盖了各种不同的风格和类型。以下是一些可能的角色风格&#xff1a; Docs聚星文社https://iimenvrieak.feishu.cn/docx/ZhRNdEWT6oGdCwxdhOPcdds7nof 现代都市风格角色&#xff1a;这种角色通常穿着时尚的衣服&#xff0c;有时尚的发型和化妆。他们可…

【Python】Jet Bridge:快速构建内部工具和管理面板的高效解决方案

Jet Bridge 是一个开源的后台管理工具构建框架&#xff0c;专门用于帮助开发者快速创建内部工具、管理面板和仪表板。它允许用户通过现有的数据库结构快速生成强大的 CRUD&#xff08;创建、读取、更新、删除&#xff09;接口&#xff0c;并提供了直观的可视化界面。Jet Bridge…

反思式思维链大模型 o1 有啥用?

&#xff08;注&#xff1a;本文为小报童精选文章。已订阅小报童或加入知识星球「玉树芝兰」用户请勿重复付费&#xff09; 失望 OpenAI o1 刚出来的时候&#xff0c;我其实对这种 reflection 模型有点儿免疫了。因为刚刚被 reflection 70B 模型诳过一回。 第一时间&#xff0c…

漏洞挖掘 | 某系统中少见的前端登录校验

0 前言 我也是第一次碰到前端登录校验的站点&#xff0c;那所谓前端校验&#xff0c;就是不走后端&#xff0c;这种情况大概率会在前端存着登录的账号和密码&#xff0c;除此之外&#xff0c;一些验证码也可能会在前端校验。 1 测试 如下图&#xff0c;点普通的功能点均显示…

Deep Learning for Video Anomaly Detection: A Review 深度学习视频异常检测综述阅读

Deep Learning for Video Anomaly Detection: A Review 深度学习视频异常检测综述阅读 AbstractI. INTRODUCTIONII. BACKGROUNDA. Notation and TaxonomyB. Datasets and Metrics III. SEMI-SUPERVISED VIDEO ANOMALY DETECTIONA. Model InputB. MethodologyC. Network Archite…

栏目一:使用echarts绘制简单图形

栏目一&#xff1a;使用echarts绘制简单图形 前言1. 在线编辑图形1.1 折线图1.2 柱状图1.3 扇形图 2. 本地绘制图表2.1 下载echarts.min.js2.2 创建一个简单的图形 前言 Echarts是一款基于JavaScript的可视化图表库。它提供了丰富的图表类型和交互功能&#xff0c;可以用于在网…

Golang | Leetcode Golang题解之第445题两数相加II

题目&#xff1a; 题解&#xff1a; func reverseList(head *ListNode) *ListNode {if head nil || head.Next nil {return head}newHead : reverseList(head.Next)head.Next.Next head // 把下一个节点指向自己head.Next nil // 断开指向下一个节点的连接&#xff0c;保证…

Study-Oracle-10-ORALCE19C-RAC集群搭建(一)

一、硬件信息及配套软件 1、硬件设置 RAC集群虚拟机:CPU:2C、内存:10G、操作系统:50G Openfile数据存储:200G (10G*2) 2、网络设置 主机名公有地址私有地址VIP共享存储(SAN)rac1192.168.49.13110.10.10.20192.168.49.141192.168.49.130rac2192.168.49.13210.10.10.3…

使用dockerfile来构建一个包含Jdk17的centos7镜像(构建镜像:centos7-jdk17)

文章目录 1、dockerfile简介2、入门案例2.1、创建目录 /opt/dockerfilejdk172.2、上传 jdk-17_linux-x64_bin.tar.gz 到 /opt/dockerfilejdk172.3、在/opt/dockerfilejdk17目录下创建dockerfile文件2.4、执行命令构建镜像 centos7-jdk17 : 不要忘了后面的那个 .2.5、查看镜像是…

Mixture-of-Experts (MoE): 条件计算的诞生与崛起【上篇】

大型语言模型&#xff08;LLM&#xff09;的现代进步主要是缩放定律的产物[6]。 假设模型是在足够大的数据集上训练出来的&#xff0c;那么随着底层模型规模的增加&#xff0c;我们会看到性能的平滑提升。 这种扩展规律最终促使我们创建了 GPT-3 以及随后的其他&#xff08;更强…

力扣高频 SQL 50 题(基础版)|分析、题解

注意一些语法 1、group by出现在having前面&#xff0c;但是having中所使用的聚合必须是select中的 2、date类型之间的比较&#xff1a;datediff&#xff08;&#xff09; 差的绝对值 or 用字符框起来比较边界 3、算日期长度需要相减之后加一 4、round(, n)n默认是0&#x…

【Java】内存分析 —— 栈内存、堆内存与垃圾对象的形成

图1 内存分析 从图1可以看出&#xff0c;在创建Person对象时&#xff0c;程序会占用两块内存区域&#xff0c;分别是栈内存和堆内存。其中Person类型的变量p被存放在栈内存中&#xff0c;它是一个引用&#xff0c;会指向真正的对象&#xff1b;通过new Person()创建的对象则放…

UDP校验和计算及网络中的校验和机制

UDP (User Datagram Protocol) 是一种无连接的传输层协议&#xff0c;它不像 TCP 那样提供可靠的传输保证。虽然 UDP 不保证数据可靠性&#xff0c;但它仍然提供了一个可选的校验和机制来检测数据在传输过程中出现的错误。 理解UDP校验和的计算过程和其在网络中的作用至关重要。…

学习C语言(21)

整理今天的学习内容 1.结构体实现位段 &#xff08;1&#xff09;位段的声明 位段的成员必须是 int、unsigned int 或signed int &#xff0c;在C99中位段成员的类型也可以选择其他类型 例&#xff1a; &#xff08;2&#xff09;位段的内存分配 位段的空间上是按照需要以…