Linux基础-socket详解、TCP/UDP

文章目录

  • 一、Socket 介绍
  • 二、Socket 通信模型
  • 三、Socket 常用函数
    • 1 创建套接字
    • 2 绑定套接字
    • 3、监听连接
    • 4、接受连接
    • 5、接收和发送数据
      • 接收数据
      • 发送数据
    • 6、关闭套接字
  • 四、Socket编程试验
    • 1、源码
      • server.c
      • client.c
    • 2、编译:
    • 3、执行结果
  • 五、补充TCP和UDP协议的Socket流程对比
    • 1、TCP 和 和 UDP区别
    • 2、TCP工作流程图
    • 3、UDP工作流程图


一、Socket 介绍

所谓socket通常也称作 “套接字”,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过 “套接字” 向网络发出请求或者应答网络请求 。
Socket 是计算机网络编程中一个重要的概念,它是在应用层和传输层之间提供的一种抽象接口,用于实现应用程序之间的数据交换。Socket 允许程序员使用一种通用的接口来访问底层传输协议,如 TCP 和 UDP,以便进行网络通信。
Socket 是一种编程接口,它提供了一种标准化的方式来创建网络连接,并允许应用程序在网络上发送和接收数据。Socket API 提供了一组函数,这些函数可以用于创建和配置套接字,建立连接,发送和接收数据,以及关闭连接等操作。
Socket 包括流式 Socket(基于 TCP)数据报式 Socket(基于 UDP),其中,TCP面向连接,可靠性较高,可实现有序、零差错的传输;而 UDP 不能确保传输的数据按序、及时到达,因此,UDP 主要出现在数据可靠性要求较低的场合。常见的 Socket 编程相关术语进行介绍:

相关术语介绍
IP 地址唯一标识了网络上的一台主机
端口号是一个 16 位数字,用于标识一个应用程序在主机上的具体位置
套接字用于标识一条网络连接的两端,包含 IP 地址和端口号
服务器在网络上提供服务的主机
客户端与服务器进行通信的主机

一 个Socket 主要由5个信息所构成,分别为:协议,本地地址,本地端口号,远地地址,远地端口号。

  • 协议指定了socket所使用的的通讯协议,一般有TCP或者UDP等。
  • 本地IP地址即本地主机的地址。
  • 本地端口号用以和本地运行的其他程序所区分。
  • 远地IP地址即依照网络协议分配给远程主机的网络地址。
  • 远地端口号用以和远程主机运行的其他程序所区分。

二、Socket 通信模型

Socket 编程通常分为两个部分:服务器端和客户端
服务器端监听一个指定的端口,等待客户端连接。一旦客户端连接到服务器端,服务器端将创建一个新的套接字来处理该客户端的请求。服务器可以同时处理多个客户端请求,每个客户端都会有自己的套接字连接。
客户端首先创建一个套接字,然后连接到服务器端的指定 IP 地址和端口号。一旦连接建立,客户端可以通过套接字发送和接收数据。客户端通常是一次性连接,一旦任务完成就关闭套接字。
在这里插入图片描述
Socket 编程可以用于各种不同的应用程序,例如聊天程序、文件传输、在线游戏等。Socket编程还可以用于创建网络服务器,提供 Web 服务、FTP 服务、邮件服务等。

三、Socket 常用函数

socket 编程的系统调用常用函数如下所示:

  • 创建套接字 socket(domain, type, protocol)
  • 绑定套接字 bind(sockfd, addr, addrlen)
  • 监听连接 listen(sockfd, backlog)
  • 接受连接 accept(sockfd, addr, addrlen)
  • 接收和发送数据 recv(sockfd, buf, len, flags)、send(sockfd, buf, len, flags)
  • 关闭套接字 close(sockfd)

1 创建套接字

在 Linux socket 编程中,创建套接字是构建网络应用程序的第一步。套接字可以理解为应用程序和网络之间的桥梁,用于在网络上进行数据的收发和处理。在 Linux 中,可以使用 socket 系统调用创建套接字。该系统调用的原型和所需头文件如下所示:

#include <sys/types.h>
#include <sys/socket.h>
int socket( (int domain, int type, int protocol );

其中,domain 参数指定了套接字的协议族,type 参数指定了套接字的类型,protocol 参数指定了套接字所使用的具体协议。下面分别介绍这三个参数的含义:

  • 协议族
    协议族指定了套接字所使用的协议类型,常用的协议族包括 AF_INET、AF_INET6、AF_UNIX等。其中,AF_INET 表示 IPv4 协议族,AF_INET6 表示 IPv6 协议族,AF_UNIX 表示 Unix 域协议族。
  • 套接字类型
    套接字类型指定了套接字的数据传输方式,常用的套接字类型包括 SOCK_STREAM、SOCK_DGRAM、SOCK_RAW 等。其中,SOCK_STREAM 表示面向连接的流套接字,主要用于可靠传输数据,例如 TCP 协议。SOCK_DGRAM 表示无连接的数据报套接字,主要用于不可靠传输数据,例如 UDP 协议。SOCK_RAW 表示原始套接字,可以直接访问底层网络协议。
  • 协议类型
    协议类型指定了套接字所使用的具体协议类型,常用的协议类型包括 IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP 等。其中,IPPROTO_TCP 表示 TCP 协议,IPPROTO_UDP 表示 UDP协议,IPPROTO_ICMP 表示 ICMP 协议,通常使用 0 表示由系统自动选择适合的协议。
    例如可以使用以下代码创建一个新的套接字:
int sockfd = = socket(AF_INET, SOCK_STREAM, 0);

2 绑定套接字

创建套接字后,需要将其与一个网络地址绑定,以便其他计算机可以访问该套接字。在 Linux系统下,可以使用 bind()系统调用绑定套接字和地址。该系统调用的原型和所需头文件如下所示:

#include <sys/types.h>
#include <sys/socket.h>
int bind( (int sockfd, const struct sockaddr *addr, socklen_t addrlen);

其中,sockfd 参数指定了需要绑定的套接字描述符,addr 参数指定了需要绑定的地址信息,可以是 struct sockaddr_in 或 struct sockaddr_in6 等结构体类型,addrlen 参数指定了地址信息的长度。使用以下代码将套接字和地址绑定:

// bind port 
// 创建一个 sockaddr_in 结构体类型的 servaddr 变量用于存储服务器的地址信息,并将其清零。
struct sockaddr_in bindaddr;
memset (&bindaddr, 0, sizeof(bindaddr);
bindaddr.sin_family = AF_INET;//指定使用 IPv4 协议(AF_INET)
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地任意可用的 IP 地址(INADDR_ANY)
bindaddr.sin_port = htons(3000);//使用指定的端口号(port)
// 将套接字 listenfd 绑定到指定的地址 bindaddr上,bind() 函数返回值为 0 表示绑定成功
if(0 != bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr))) {
	printf("bind error\n");
	return -1;
}

在初始化 sockaddr_in 结构体时,用到了以下函数:

  • memset() :用来将 sockaddr_in 结构体的各个成员变量初始化为 0,这是一个常用的初始化方式。其函数原型如下:
 #include <string.h> 
 // s 参数是需要被初始化的内存区域指针,c 参数是填充字符,n 参数是需要填充的内存字节数。
 void *memset( void *s, int c, size_t n ); 
  • htonsl()和 和 htons():用于将本机字节序的端口号转换为网络字节序的端口号。其函数原型如下:
 #include <arpa/inet.h> 
uint32_t htonl(uint32_t hostlong );
uint16_t htons(uint16_t hostshort);

其中,hostshort 和 hostlong 参数是本机字节序的端口号,函数返回值是网络字节序的端口号。

3、监听连接

绑定套接字后,需要开始监听连接请求,以便其他网络上的客户端能够与该套接字建立连接。这一步骤通常在服务器端完成。在 Linux 系统下,可以使用 listen()系统调用监听套接字。该系统调用的原型如下:

#include <sys/socket.h> 
int listen(int socket, int backlog);

其中,sockfd 参数指定了需要监听的套接字描述符,backlog 参数指定了连接队列的长度,即等待接受的连接数。例如可以使用以下代码开始监听连接.

// start listen
if (listen(listenfd, 2) != 0) {
	printf("listem error\n");
	return -1;
}

4、接受连接

当有客户端请求连接时,需要接受该连接并进行处理。在 Linux 系统下,可以使用 accept(),系统调用接受连接请求。该系统调用的原型和所需头文件如下所示:

#include <sys/socket.h>
int accept (int __fd, __SOCKADDR_ARG __addr, socklen_t *__restrict __addr_len);

其中,sockfd 参数指定了需要接受连接的套接字描述符,addr 参数用于存储客户端的地址信息,addrlen 参数用于存储地址信息的长度。使用以下代码接受连接请求:

// 定义一个 sockaddr_in 结构体,用于存储客户端的 IP 地址和端口号
struct sockaddr_in clientaddr;
// 定义一个 socklen_t 类型的变量 clientaddrlen ,用于存储客户端地址结构体的长度
socklen_t clientaddrlen = sizeof(clientaddr);

// 调用 accept() 系统调用,接受客户端的连接请求,并返回一个新的套接字描述符 connfd,用于与客户端进行通信。
// accept() 函数会阻塞程序,直到有客户端连接到服务器端。
// listenfd 是服务端的监听套接字, clientaddr 是指向 sockaddr_in 结构体的指针,用于存储客户端的地址信息。
// clientaddrlen 是客户端地址结构体的长度,accept() 函数会将实际接受到的客户端地址长度存储到该变量中。
int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
if (clientfd >= 0) { // 判断 accept() 函数的返回值,判断客户端连接是否失败
	// 处理接收数据
	
} else{
	printf("accept error\n");
}

5、接收和发送数据

在 Linux socket 编程中,接收和发送数据是套接字编程的核心步骤之一。当套接字绑定并且处于监听状态,已经成功接受了客户端的连接请求后,接下来就是进行数据的收发。下面将详细介绍如何在 Linux 系统中实现接收和发送数据的过程。

接收数据

在接收数据之前,需要先了解数据在网络传输中的一些基本概念。在 TCP 协议中,发送方发送的数据被分割成一个个 TCP 报文段,每个报文段都包含一个 TCP 首部和数据部分。TCP首部中包含了一些控制信息,如序号、确认号、窗口大小等,用来保证数据的可靠传输。而数据部分则是发送方发送的应用层数据,如 HTTP 报文、FTP 文件等。
在 Linux socket 编程中,接收数据的过程分为两步:先接收 TCP 首部,再接收数据部分。具体步骤如下:
(1)创建一个缓冲区用于接收数据。缓冲区的大小一般为数据部分的大小。
(2)调用 recv() 系统调用接收数据。其函数原型和所需头文件如下:

#include <sys/socket.h> 
ssize_t recv(int sockfd, void *buffer, size_t length, int flags );

其中,sockfd 参数是需要接收数据的套接字描述符,buf 参数是用于存储接收数据的缓冲区指针,len 参数是需要接收的数据的最大长度,flags 参数是接收标志,通常为 0。函数返回值为实际接收到的字节数,如果返回值为 0,表示对端已经关闭连接。
(3)在调用 recv() 系统调用时,会先接收 TCP 首部,然后再接收数据部分。如果数据部分比较大,可能需要多次调用 recv() 系统调用才能接收完整的数据。因此,需要使用一个循环来不断接收数据,直到接收到全部数据或者出现错误为止。另外,需要注意的是,recv() 系统调用是一个阻塞调用,即程序会一直等待直到接收到数据或者出现错误才会返回。如果不希望阻塞调用,可以使用非阻塞 I/O 或者多路复用技术。
(4) 处理接收到的数据。在数据接收完成后,需要对接收到的数据进行处理。具体处理方式根据具体的应用场景而定,如将接收到的数据显示在终端上、将数据写入文件等。

发送数据

发送数据的步骤如下:
(1)定义数据缓冲区:需要定义一个用于存储待发送数据的缓冲区,比如 char buf[MAXLINE]。
(2)将数据拷贝到缓冲区:将需要发送的数据拷贝到缓冲区中,可以使用 strcpy()、memcpy() 等函数进行拷贝操作。
(3)使用 send() 函数发送数据:send() 函数用于向已经建立连接的套接字发送数据,其函数原型如下:

#include <sys/socket.h> 
ssize_t send( int sockfd, const void *buf, size_t len, int flags);

函数调用成功将返回发送数据的字节数(返回值为非负数),返回-1 表示发送失败。sockfd 需要发送数据的套接字描述符;buf 待发送数据的缓冲区指针;len 待发送数据的长度;flags 传输标志,通常为 0。

// send data
int ret = send(clientfd, buf, strlen(buf), 0);
if (ret != strlen(buf)){
	printf("send data error\n");
	return -1;
}

需要注意的是,send() 函数并不保证一次能够将所有数据都发送出去,如果数据量比较大,可能需要多次调用 send() 函数才能将所有数据发送出去。
(4)判断是否发送完毕:可以通过判断 send() 函数的返回值和待发送数据的长度是否相等来确定是否发送完毕。

6、关闭套接字

关闭套接字是一个非常重要的步骤。当套接字不再需要使用时,应该立即关闭以释放系统资源和避免资源浪费。关闭套接字的步骤非常简单,只需要调用 close() 系统调用即可。close()
系统调用的函数原型如下:

int close( (int sockfd );

其中,sockfd 参数是需要关闭的套接字描述符。函数返回值为 0 表示成功,返回值为 -1表示失败。

四、Socket编程试验

1、源码

server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
// 自定义传输的数据
typedef struct  msgbuf{
	int id;
	char str[32];
} msgbuf;

int main() {
	
	// create socket
	int listenfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == listenfd) {
		printf("create socket error");
		return -1;
	}
    printf("server create socket ok!\n");
	
	// bind port 
	// 创建一个 sockaddr_in 结构体类型的 servaddr 变量用于存储服务器的地址信息,并将其清零。
	struct sockaddr_in bindaddr;
	memset (&bindaddr, 0, sizeof(bindaddr));
	bindaddr.sin_family = AF_INET;//指定使用 IPv4 协议(AF_INET)
	bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地任意可用的 IP 地址(INADDR_ANY)
	bindaddr.sin_port = htons(3000);//使用指定的端口号(port)
	// 将套接字 listenfd 绑定到指定的地址 bindaddr上,bind() 函数返回值为 0 表示绑定成功
	if(0 != bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr))) {
		printf("bind error\n");
		return -1;
	}
	
	// start listen
	if (listen(listenfd, 2) != 0) {
		printf("listem error\n");
		return -1;
	}

    msgbuf socmsg;
	// 定义一个 sockaddr_in 结构体,用于存储客户端的 IP 地址和端口号
	struct sockaddr_in clientaddr;
	// 定义一个 socklen_t 类型的变量 clientaddrlen ,用于存储客户端地址结构体的长度
	socklen_t clientaddrlen = sizeof(clientaddr);

	// accept connection
	// 调用 accept() 系统调用,接受客户端的连接请求,并返回一个新的套接字描述符 connfd,用于与客户端进行通信。
	// accept() 函数会阻塞程序,直到有客户端连接到服务器端。
	// listenfd 是服务端的监听套接字, clientaddr 是指向 sockaddr_in 结构体的指针,用于存储客户端的地址信息。
	// clientaddrlen 是客户端地址结构体的长度,accept() 函数会将实际接受到的客户端地址长度存储到该变量中。
	int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
	if (clientfd >= 0) { // 判断 accept() 函数的返回值,判断客户端连接是否失败
		while (1) {        	
			socmsg.id = 0;
			memset(socmsg.str, ' ',sizeof(socmsg.str));
			
			// receive data
			int ret = recv(clientfd, &socmsg, sizeof(socmsg), 0);
			if (ret > 0) {
				printf("recv data from client, data: %d %s\n", socmsg.id, socmsg.str);
				
				printf("send data ...\n");
				// send data
				ret = send(clientfd, &socmsg, sizeof(socmsg), 0);
				if (ret != sizeof(socmsg)) {
					printf("send data error.\n");
				}
				
			} else {
				// printf("recv data error.\n");
				sleep(1);
			}
		}		
		close(clientfd);
	} else{
		printf("accept error\n");
	}

	
	//close socket
	close(listenfd);
	return 0;
}

client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

typedef struct  msgbuf{
	int id;
	char str[32];
} msgbuf;

int main(int argc, const char *argv[]) {
	
	char *ip = "192.168.1.100"; // 服务器默认的IP,可以改成服务器实际IP

	// create socket
	int clientfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == clientfd) {
		printf("create socket error");
		return -1;
	}
    printf("client create socket ok!\n");
	
	// connect server 
	struct sockaddr_in serveraddr;
	serveraddr.sin_family = AF_INET;

	if (argc == 2) { // 如果有输入IP参数,使用输入的IP参数
		printf("%s\r\n", argv[1]);
		serveraddr.sin_addr.s_addr = inet_addr(argv[1]);  // 从命令行参数中获取服务器 IP 地址
	} else {
    	serveraddr.sin_addr.s_addr = inet_addr(ip);
	}

        serveraddr.sin_port = htons(3000);
	
	if(-1 == connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) {
		printf("connect error\n");
		return -1;
	}
	printf("connect OK\n");

	msgbuf socmsg;
	int i =0;

	while(1) {
		sleep(1);
		socmsg.id = i++;

		memcpy(socmsg.str,"this is my test msg", strlen("this is my test msg"));
		
		// send data
		int ret = send(clientfd, &socmsg, sizeof(socmsg), 0);
		if (ret != sizeof(socmsg)){
			printf("send data error\n");
			return -1;
		}
		printf("send data ok, %d %s\n",socmsg.id, socmsg.str);
		
		// receive data
		socmsg.id = 0;
		// socmsg.str[128] = {0};
		memset(socmsg.str, ' ',sizeof(socmsg.str));

		ret = recv(clientfd, &socmsg, sizeof(socmsg), 0);
		if (ret > 0) {
			printf("receive data from server: %d %s\n", socmsg.id, socmsg.str);
		} else {
			printf("receive data error\n");
		}
	}
	// close socket
	close(clientfd);

    return 0;
}

2、编译:

gcc -o server server.c
gcc -o client client.c

如果client是在板子上执行,需要使用板子系统对应的交叉编译器来编译。例如:aarch64-linux-gnu-gcc -o client client.c

3、执行结果

在这里插入图片描述
在这里插入图片描述
本例程只作为一个简单的demo,体验下socket通讯。

五、补充TCP和UDP协议的Socket流程对比

在一般的网络书籍中,网络协议被分为 5 层:
在这里插入图片描述
⚫应用层:它是体系结构中的最高层,直接为用户的应用进程(例如电子邮件、文件传输和终端仿真)提供服务。在因特网中的应用层协议很多,如支持万维网应用的 HTTP 协议,支持电子邮件的 SMTP 协议,支持文件传送的 FTP , DNS,POP3, SNMP, Telnet 等等。
⚫运输层:负责向两个主机中进程之间的通信提供服务。
运输层主要使用以下两种协议:
① 传输控制协议 TCP(Transmission Control Protocol):面向连接的,数据传输的单位是报文段,能够提供可靠的交付。
② 用户数据包协议 UDP(User Datagram Protocol):无连接的,数据传输的单位是用户数据报,不保证提供可靠的交付,只能提供“尽最大努力交付”。
⚫ 网络层:负责将被称为数据包(datagram)的网络层分组从一台主机移动到另一台主机。
⚫ 链路层:因特网的网络层通过源和目的地之间的一系列路由器路由数据报。
⚫ 物理层:在物理层上所传数据的单位是比特。物理层的任务就是透明地传送比特流。
这些层对于初学者来说很难理解,我们只需要知道:我们需要使用“运输层”编写应用程序,我们的应用程序位于“应用层”。使用“运输层”时,可以选择 TCP 协议,也可以选择 UDP 协议。

1、TCP 和 和 UDP区别

TCP 向它的应用程序提供了面向连接的服务。这种服务有 2 个特点:可靠传输、流量控制(即发送方/接收方速率匹配)。它包括了应用层报文划分为短报文,并提供拥塞控制机制。
UDP 协议向它的应用程序提供无连接服务。它没有可靠性,没有流量控制,也没有拥塞控制。
在这里插入图片描述

既然 TCP 提供了可靠数据传输服务,而 UDP 不能提供,那么 TCP 是否总是首选呢?

答案是否定的,因为有许多应用更适合用 UDP,举个例子:视频通话时,使用 UDP,偶尔的丢包、偶尔的花屏时可以忍受的;如果使用 TCP,每个数据包都要确保可靠传输,当它出错时就重传,这会导致后续的数据包被阻滞,视频效果反而不好。使用 UDP 时,有如下特点:
① 关于何时发送什么数据控制的更为精细
采用 UDP 时只要应用进程将数据传递给 UDP,UDP 就会立即将其传递给网络层。而 TCP 有重传机制,而不管可靠交付需要多长时间。但是实时应用通常不希望过分的延迟报文段的传送,且能容忍一部分数据丢失。
② 无需建立连接,不会引入建立连接时的延迟。
③ 无连接状态,能支持更多的活跃客户。
④ 分组首部开销较小。

2、TCP工作流程图

在这里插入图片描述

3、UDP工作流程图

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Agisoft Metashape 自定义底图

Agisoft Metashape 自定义底图 前言 Agisoft Metashape 从2.0.2 版本开始,Agisoft Metashape Professional 和 Agisoft Viewer 支持自定义底图,可用于模型和正射视图模式。本文以添加Esri World Image卫星底图图源为例,介绍Agisoft Metashape 自定义底图的方法。 添加自定…

YOLOV8 pycharm

1 下载pycharm 社区版 https://www.jetbrains.com/zh-cn/pycharm/download/?sectionwindows 2 安装 3 新建 4 选择 文件-> setting 配置环境变量 5 添加conda 环境

vite打包配置

目录 minify默认是esbuild&#xff0c;不能启动下面配置 使用&#xff1a; plugins: [viteMockServe({mockPath: mock})]根目录新建mock/index.ts. 有例子Mock file examples&#xff1a;https://www.npmjs.com/package/vite-plugin-mock-server 开发环境生产环境地址替换。根…

php7.4在foreach中对使用数据使用无法??[]判读,无法使用引用传递

代码如下图&#xff1a;这样子在foreach中是无法修改class_history的。正确的应该是去掉??[]判断。 public function actionY(){$array [name>aaa,class_history>[[class_name>一班,class_num>1],[class_name>二班,class_num>2]]];foreach ($array[class_…

营收不过万,世道艰难,月末总结复盘ing

2024已经走过了1/3&#xff0c;从事实上看确实如大佬们所说世道越来越难&#xff0c;过往的几个月份营收只有区区10000。向下兼容的话从绝对值上看收入确实不少了&#xff0c;从相对值上看又少的可怜&#xff0c;只能满足温饱而已。 这个月上半场成绩非常喜人&#xff0c;半个月…

IDEA在setting中已经勾选了Use non-modal commit interface选项,还是不显示commit侧边栏

今天在拉取项目后&#xff0c;发现我得项目不显示commit的侧边栏&#xff0c;导致我的项目修改没有一个提示。 去网上搜了一些方案&#xff0c;都是让修改seting中的下图中的选项 但是我勾选上还是没有任何效果&#xff0c;侧边栏还是不显示commit的选项。 然后经过重重检索&…

C语言——柔性数组

1、柔性数组是什么 在C语言中&#xff0c;柔性数组成员&#xff08;Flexible Array Member&#xff0c;简称FAM&#xff09;是C99标准中引入的一种结构体成员&#xff0c;用于表示一个大小可变的数组。它是结构体的最后一个成员&#xff0c;不像普通的数组&#xff0c;没有固定…

spring boot运行过程中动态加载Controller

1.被加载的jar代码 package com.dl;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication public class App {public static void main(String[] args) {SpringApplication.run(A…

stm32f103c8t6学习笔记(学习B站up江科大自化协)-UNIX时间戳、BKPRTC

UNIX时间戳 UNIX时间戳最早是在UNIX系统使用的&#xff0c;所以叫做UNIX时间戳&#xff0c;之后很多由UNIX演变而来的系统也继承了UNIX时间戳的规定&#xff0c;目前linux&#xff0c;windows&#xff0c;安卓这些操作系统的底层计时系统都是用UNIX时间戳 时间戳这个计时系统和…

http请求报文的组成与作用?

http请求报文的组成与作用&#xff1f; 一、http 的请求报文组成二、请求行&#xff08;Request Line&#xff09;三、请求头部&#xff08;Request Headers&#xff09;四、请求体&#xff08;Request Body&#xff09;五、响应头部 &#xff08;Response Headers &#xff09…

部署YUM仓库和NFS共享存储服务

目录 1. YUM仓库服务 1.1 YUM概述 1.2 准备安装源 1.3 yum在线源替换方法 2.制作YUM源 2.1制作ftp源 3.yum软件包的下载方式 4.NFS共享存储服务 4.1 NFS 4.2 NFS网络文件系统 4.3 NFS配置 1. YUM仓库服务 1.1 YUM概述 yum是一个基于RPM包&#xff08;是Red-Ha…

MAC 本地搭建Dify环境

Dify 介绍 Dify 是一款开源的大语言模型(LLM) 应用开发平台。它融合了后端即服务&#xff08;Backend as Service&#xff09;和 LLMOps 的理念&#xff0c;使开发者可以快速搭建生产级的生成式 AI 应用。即使你是非技术人员&#xff0c;也能参与到 AI 应用的定义和数据运营过…

运维的利器–监控–zabbix–第二步:建设–汉化补丁--导致乱码问题

文章目录 问题原因解决方法 问题 点击对应主机的【图形】即可看到以下乱码情况 原因 上述的图标数据&#xff0c;下面的小白框表示乱码含义&#xff0c;是因为我们改了zabbix的 语言为中文 解决方法 服务器需要安装字体 [rootzabbix-server01 ~]#yum -y install wqy-mic…

go设计模式之抽象工厂模式

抽象工厂模式 提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类。 工厂方法模式通过引入工厂等级结构&#xff0c;解决了简单工厂模式中工厂类职责太重的问题&#xff0c;但由于工厂方法模式中的每个工厂只生产一类产品&#xff0c;可能会导致…

maven-依赖管理

依赖配置 https://mvnrepository.com/?__cf_chl_rt_tkvRzDsumjmJ_HF95MK4otu9XluVRHGqAY5Wv4UQYETR8-1714103058-0.0.1.1-1557 <dependencies><dependency><groupId></groupId><artifactId></artifactId><version></version>…

精确测量地面沉降:静力水准仪的应用

地面沉降是一个全球性的地质问题&#xff0c;它可能对建筑物、道路和地下设施造成严重的损害。因此&#xff0c;对地面沉降进行精确测量和监测至关重要。静力水准仪作为一种先进的测量设备&#xff0c;为地面沉降的精确测量提供了有效手段。本文将探讨静力水准仪在地面沉降测量…

Dokcer容器分布式搭建LNMP+wordpress论坛

目录 引言 一、架构环境 二、搭建容器 &#xff08;一&#xff09;自定义网络 &#xff08;二&#xff09;搭建nginx容器 1.文件准备 2.查看与编辑文件 3.生成镜像 4.创建容器 &#xff08;三&#xff09;搭建MySQL容器 1.文件准备 2.查看与编辑文件 3.生成镜像 …

版本控制系统-Git

目录 1. Git简介 2. 下载及安装 3.命令行操做 3.1全局设置 3.2初始化仓库 3.3提交代码 3.4查看提交历史 3.5推送代码 3.6拉取合并代码 3.7克隆仓库 3.8. 配置忽略文件 3.9. 凭据管理 4. GUI工具操作 4.1. 全局设置 4.2. 初始化仓库 4.3. 提交代码 输入提交日志…

【linux-1-Ubuntu常用命令-vim编辑器-Vscode链接ubuntu远程开发】

目录 1. 安装虚拟机Vmare和在虚拟机上安装Ubuntu系统&#xff1a;2. 常用的Ubuntu常识和常用命令2.1 文件系统结构2.2 常用命令2.3 vim编辑器 3. Ubuntu能联网但是ping不通电脑&#xff1a;4. Windows上安装VScode链接ubuntu系统&#xff0c;进行远程开发&#xff1a; 1. 安装虚…

uni-app - 使用地图功能打包安卓apk的完美流程以及重要的注意事项(带您一次打包成功)

在移动应用开发中&#xff0c;地图功能是一个非常常见且实用的功能&#xff0c;可以帮助用户快速定位并浏览周边信息。而在uni-app开发中&#xff0c;使用地图功能也是一项必备技能。本文将介绍uni-app使用地图功能打包安卓apk的注意事项&#xff0c;帮助开发者顺利完成地图功能…