Linux基础-socket详解、TCP/UDP

news2024/11/23 16:41:50

文章目录

  • 一、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工作流程图

在这里插入图片描述

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

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

相关文章

无人机+三维建模:倾斜摄影技术详解

无人机倾斜摄影测量技术是一项高新技术&#xff0c;近年来在国际摄影测量领域得到了快速发展。这种技术通过从一个垂直和四个倾斜的五个不同视角同步采集影像&#xff0c;从而获取到丰富的建筑物顶面及侧视的高分辨率纹理。这种技术不仅能够真实地反映地物情况&#xff0c;还能…

3.CQL使用-创建、查询、返回

Neo4j的Cypher语言为处理图形数据而构建的。 CQL代表Cypher查询语言。 https://neo4j.com/docs/cypher-manual/3.5/clauses/load-csv/ CQL语句使用文档地址。 小技巧&#xff1a;在neo4j的CQL语句输入框里&#xff0c;shift enter换行&#xff0c;直接enter键会执行CQL语…

2024智能科学与软件工程国际学术会议(ICISSE 2024)

2024智能科学与软件工程国际学术会议&#xff08;ICISSE 2024) 会议简介 2024智能科学与软件工程国际学术会议&#xff08;ICISSE 2024&#xff09;将在北京隆重举行。本次会议汇集了全球智能科学和软件工程领域的专家学者&#xff0c;共同探讨该领域的最新研究成果和发展趋…

跟TED演讲学英文:AI isn‘t as smart as you think -- but it could be by Jeff Dean

AI isn’t as smart as you think – but it could be Link: https://www.ted.com/talks/jeff_dean_ai_isn_t_as_smart_as_you_think_but_it_could_be Speaker: Jeff Dean Jeffrey Adgate “Jeff” Dean (born July 23, 1968) is an American computer scientist and software…

Spirng 当中 Bean的作用域

Spirng 当中 Bean的作用域 文章目录 Spirng 当中 Bean的作用域每博一文案1. Spring6 当中的 Bean的作用域1.2 singleton 默认1.3 prototype1.4 Spring 中的 bean 标签当中scope 属性其他的值说明1.5 自定义作用域&#xff0c;一个线程一个 Bean 2. 总结:3. 最后&#xff1a; 每…

JAVA基础---Stream流

Stream流出现背景 背景 在Java8之前&#xff0c;通常用 fori、for each 或者 Iterator 迭代来重排序合并数据&#xff0c;或者通过重新定义 Collections.sorts的 Comparator 方法来实现&#xff0c;这两种方式对 大数量系统来说&#xff0c;效率不理想。 Java8 中添加了一个…

一款可视化正则表达式工具

regex-vis是一款在线免费且可视化的正则表达式工具 界面图&#xff1a; 只能输入由26个英文字母组成的字符串 ^[A-Za-z]$ 只能输入数字 ^[0-9]*$测试错误 测试正确 快来感受一下叭 官方网址&#xff1a; Regex VisRegex visualizer & editor, make the regular expr…

西门子:HMI小游戏-灰太狼与喜羊羊

DB块&#xff1a; HMI界面&#xff1a; 实际视频&#xff1a; 抓羊小游戏

docker各目录含义

目录含义builder构建docker镜像的工具或过程buildkit用于构建和打包容器镜像&#xff0c;官方构建引擎&#xff0c;支持多阶段构建、缓存管理、并行化构建和多平台构建等功能containerd负责容器生命周期管理&#xff0c;能起、停、重启&#xff0c;确保容器运行。负责镜管理&am…

电脑技巧:推荐一款非常好用的媒体播放器PotPlayer

目录 一、 软件简介 二、功能介绍 2.1 格式兼容性强 2.2 高清播放与硬件加速 2.3 自定义皮肤与界面布局 2.4 多音轨切换与音效增强 2.5 字幕支持与编辑 2.6 视频截图与录像 2.7 网络流媒体播放 三、软件特色 四、使用技巧 五、总结 一、 软件简介 PotPlayer播放器 …

【面试经典 150 | 回溯】括号生成

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;暴力法方法二&#xff1a;回溯 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到…

compose调用系统分享功能分享图片文件

compose调用系统分享功能图片文件 简介UI界面提供给外部程序的文件访问权限创建FileProvider设置共享文件夹 通用分享工具虚拟机验证结果参考 本系列用于新人安卓基础入门学习笔记&#xff0c;有任何不同的见解欢迎留言 运行环境 jdk17 andriod 34 compose material3 简介 本案…

JAVA面试专题-Redis

你在最近的项目中哪些场景使用了Redis 缓存 缓存穿透 缓存穿透&#xff1a;查询一个不存在的数据&#xff0c;mysql查询不到数据也不好直接写入缓存&#xff0c;导致每次请求都查数据库。 解决方案一&#xff1a;缓存空数据&#xff0c;即使查询返回的数据为空&#xff0c;也把…

xss漏洞学习

1.xss漏洞简介 跨站脚本&#xff08;Cross-Site Scripting&#xff09;&#xff0c;本应该缩写为CSS&#xff0c;但是该缩写已被层叠样式脚本Cascading Style Sheets所用&#xff0c;所以改简称为XSS。也称跨站脚本或跨站脚本攻击。 原理&#xff1a;跨站脚本攻击XSS通过将恶…

SQLite如何处理CSV 虚拟表(三十七)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite的DBSTAT 虚拟表&#xff08;三十六&#xff09; 下一篇:SQLite的扩展函数Carray()表值函数(三十八) ​ RFC4180格式是一种文本文件格式&#xff0c;被用于表格数据间的交互&#xff0c;也可将表格数据转化…

Java进阶-JINQ详解与使用

本文详细介绍了JINQ&#xff08;Java Integrated Query&#xff09;&#xff0c;一种强化Java中数据查询能力的库&#xff0c;提供类SQL的查询语法和类型安全的操作。文章首先解释了JINQ的基本功能和应用&#xff0c;随后通过具体示例展示了如何使用JINQ进行数据过滤、投影、连…

Python自学篇3-PyCharm开发工具下载、安装及应用

一、Python开发工具 自学篇1中讲到了安装Python之后出现的几个应用程序&#xff0c;其中IDLE、Python.exe都可以用来编写python程序&#xff0c;也可以进行调试&#xff1b;但是比较基础&#xff0c;比较原始&#xff0c;调试不方便&#xff0c;界面也不友好&#xff0c;需要更…

天空卫士旗舰产品入选《网络安全专用产品指南》

权威认证 近日&#xff0c;中国网络安全产业联盟&#xff08;CCIA&#xff09;发布了第一版《网络安全专用产品指南》。这一权威指南中&#xff0c;天空卫士荣获殊荣&#xff0c;旗下三款尖端产品荣耀入选&#xff0c;分别是增强型Web安全网关&#xff08;ASWG&#xff09;、数…

解析Redis Key Prefix配置之谜:双冒号“::”的由来与作用

前言 在使用Spring Boot集成Redis进行应用开发时&#xff0c;为了增强缓存键的可读性和管理性&#xff0c;我们常常会在配置文件中设定一个全局的key-prefix。如果你发现存储至Redis的键自动附加了“::”&#xff0c;本文将深入探讨这一现象背后的原因&#xff0c;解析Spring …

长难句打卡4.29

If appropriate public policies were in place to help all women—whether CEOs or their children’s caregivers—and all families, Sandberg would be no more newsworthy than any other highly capable person living in a more just society 如果能制定适当的公共政策…