C/C++ Linux Socket网络编程

news2024/12/23 13:08:46

之前已经学习了QT的socket编程 和 C/C++在window环境的socket编程,现在再来学习一波C/C++在Linux环境下的socket编程,为以后学习C++ Linux 服务器开发做准备。


目录

一、Socket简介

二、Socket编程基础

 1. 网络字节序

2. sockaddr数据结构

3. IP地址转换函数

三、Socket编程函数

1. socket函数

2. bind 函数

3. listen 函数

4. accept 函数

5. connect 函数

6. read 函数

7. write 函数

8. close 函数

四、回声服务器案例

1. 服务器

2. 客户端

3. 运行测试

五、总结


一、Socket简介

既然是socket,那必然有TCP 和 UDP之分,本文所记录的是TCP协议的socket编程。

socket编程分为TCP和UDP两个模块,其中TCP是可靠的、安全的,常用于发送文件等,而UDP是不可靠的、不安全的,常用作视频通话等。

如下图

 Socket通信3要素:

  1. 通信的目的地址;
  2. 使用的端口号;
  3. 使用的传输层协议(如TCP、UDP)

Socket通信模型

 Socket被称之为套接字。

在Linux环境中,Socket编程都是以伪文件的形式运行着;既然是文件,我们可以使用文件描述符引用套接字。(Linux一切皆文件

Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是文件主要应用于本地持久化数据的读写,而套接字多应用于网络进程间数据的传递。

在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。

套接字通信原理如下图所示:

在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符来发送缓冲区和接收缓冲区。

Socket 通信创建流程图


二、Socket编程基础

 1. 网络字节序

在计算机世界里,有两种字节序:

        大端字节序 --- 低地址高字节,高地址低字节

        小端字节序 --- 低地址低字节,高地址高字节

内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。

网络数据流的地址有这样规定:先发出的数据是低地址,后发出的数据是高地址。

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

所以,我们在代码中必须要将ip地址和端口号做相应的转换,转换为网络字节序才可以进行通讯。

大多数使用 htonl 和 htons 。

为什么需要转换呢?

假设本地主机使用的是小端字节序,而对方主机使用的是大端字节序;你发送数据过去的地址顺序是:0x06b3,而对方接受到的却是:0xb306;这样数据就乱了,所以需要进行转换!

需要通过以下接口进行转换:

#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位短整数。

l 结尾的函数用于ip地址转换,s 结尾的函数用于端口号的转换。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。


2. sockaddr数据结构

我们在使用socket中,需要使用结构体sockaddr_in将IP地址和端口号等保存,然后用于绑定socket;

但进行绑定时,我们却要将结构体sockaddr_in强制类型转换为结构体sockaddr,这是为什么呢?

由于历史原因,一开始是没有结构体sockaddr_in,只有结构体sockaddr。

后来为了适配ipv4的到来,将结构体sockaddr细化为结构体sockaddr_in,如上图。

两个结构体如下:

struct sockaddr {
	sa_family_t sa_family; 		/* address family, AF_xxx */
	char sa_data[14];			/* 14 bytes of protocol address */
};

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

IPv4的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,但是sock API的实现早于ANSI C标准化,那时还没有void *类型,因此这些像bind 、accept函数的参数都用struct sockaddr *类型表示,在传递参数之前要强制类型转换一下,

例如:

struct sockaddr_in servaddr;

bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));        /* initialize servaddr */


3. IP地址转换函数

上面网络字节序中我们使用了htonl 和 ntohl 两个函数进行ip地址的转换,但只能将uint32_t类型的地址进行转换,例如:INADDR_ANY ==> 0.0.0.0

但是实际项目中我们设置ip地址大多数都是字符串,所以得使用特定的函数去进行转换。

#include <arpa/inet.h>

int inet_pton (int af, const char *src, void *dst);        // “本地ip转换为网络ip”

const char *inet_ntop (int af, const void *src, char *dst, socklen_t size);        // “网络ip转换为本地ip”

af 取值可选为 AF_INET 和 AF_INET6 ,即和 ipv4 和ipv6对应支持IPv4和IPv6;

src 是转换前ip,dst 是转换后ip;

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr。

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[]) {
	
	char ip[] = "6.7.8.9";
	char server_ip[64];
	
	struct sockaddr_in server_addr;
	
	inet_pton(AF_INET, ip, &server_addr.sin_addr.s_addr);
	
	printf("s_addr : %x\n", server_addr.sin_addr.s_addr);
	
	printf("s_addr from net to host : %x \n", ntohl(server_addr.sin_addr.s_addr));
	
	inet_ntop(AF_INET, &server_addr.sin_addr.s_addr, server_ip, sizeof(server_ip));
	
	printf("server_ip : %s \n", server_ip);
	
	printf("INADDR_ANY: %d \n", INADDR_ANY);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	inet_ntop(AF_INET, &server_addr.sin_addr.s_addr, server_ip, sizeof(server_ip));
	printf("INADDR_ANY ip : %s\n", server_ip);
	
	return 0;
}

 运行结果:

s_addr : 9080706
s_addr from net to host : 6070809
server_ip : 6.7.8.9
INADDR_ANY: 0
INADDR_ANY ip : 0.0.0.0

ip地址:6.7.8.9

因为网络上使用的是大端字节序,所以通过inet_pton函数转换后的ip地址输出为:9080706

当通过ntohl函数转换回主机ip地址后输出为:6070809

因为我的本地主机使用的是小段字节序,所以转换后的循序和ip地址顺序一致,大端字节序则反过来了;

如果需要将网络的ip地址转换为字符串,则需要使用inet_ntop函数;

如果需要将字符串ip地址转换为网络ip地址,则需要使用inet_pton函数。

在服务器中,如果有多个网络,一般我们都会绑定所有网卡,会进行如下设置:

server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	// 监听本地所有IP地址

INADDR_ANY是一个宏,即为0的宏,他转换后赋值给结构体实际上是:0.0.0.0这个ip地址。


三、Socket编程函数

1. socket函数

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

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

domain:

    AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址。

    AF_INET6 与上面类似,不过是来用IPv6的地址。

    AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用。

type:

    SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。

    SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。

    SOCK_SEQPACKET 该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。

    SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)。

    SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序。

protocol:

    传 0 表示使用默认协议。

返回值:

    成功:返回指向新创建的socket的文件描述符,失败:返回 -1设置errno

可以使用以下方式进行打印输出失败报错信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。

对于IPv4,domain 参数指定为AF_INET。

对于TCP协议,type 参数指定为SOCK_STREAM,表示面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协议。

protocol 参数的介绍 - 略,指定为0即可。

例:

int sock;
sock = socket(AF_INET, SOCK_STREAM, 0);

2. bind 函数

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

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

sockfd:

    socket文件描述符

addr:

    构造出IP地址加端口号的结构体

addrlen:

    sizeof(addr)长度

返回值:

    成功 返回 0失败 返回 -1, 设置 errno

 可以使用以下方式进行打印输出失败报错信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

    服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。

bind()的作用是将参数sockfd和addr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。

例:

struct sockaddr_in servaddr;            // 定义结构体
bzero(&servaddr, sizeof(servaddr));     // 将整个结构体清零

// 设置地址类型为AF_INET(IPv4)
servaddr.sin_family = AF_INET;          

/* 网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,
   每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,
   直到与某个客户端建立了连接时才确定下来到底用哪个IP地址. */
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

// 设置端口号为5000
servaddr.sin_port = htons(5000);

// 绑定
bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr));

3. listen 函数

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int listen (int sockfd, int backlog);

sockfd:

    socket 文件描述符

backlog:

    在Linux 系统中,它是指排队等待建立3次握手队列长度。(客户端同时进行连接服务器的个数)

返回值:

    成功 返回 0失败 返回 -1, 设置 errno

 可以使用以下方式进行打印输出失败报错信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

查看一下系统默认backlog

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

改变 系统限制的backlog 大小

1. 打开文件

        vim /etc/sysctl.conf


2. 在文件最后添加
        net.core.somaxconn = 1024

        net.ipv4.tcp_max_syn_backlog = 1024


3. 保存,然后执行
        sysctl -p

如下图:(修改系统默认backlog为1024)

 

 为什么要修改呢?如果不修改,即使我们在代码里设置10240(listen(sock, 10240);),它也还是按照系统默认的值来设置的!

典型的服务器程序可以同时服务于多个客户端,当有客户端发起连接时,服务器调用的accept()返回并接受这个连接,如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接待状态,如果接收到更多的连接请求就忽略。

例:

// 监听,同时监听128个请求
listen(sock, 128);

4. accept 函数

#include <sys/types.h>      /* See NOTES */

#include <sys/socket.h>

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

sockdf:

    socket文件描述符

addr:

    传出参数,返回连接客户端地址信息,含IP地址和端口号

addrlen:

    传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小

返回值:

    成功返回一个新的socket文件描述符,用于和客户端通信,失败返回 -1,设置errno

 可以使用以下方式进行打印输出失败报错信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

三次握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。

addr 是一个传出参数,accept()返回时传出客户端的地址和端口号;如果给addr参数传NULL,表示不关心客户端的地址。

addrlen 参数是一个传入传出参数(value-result argument),传入的是调用者提供的缓冲区addr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。

例:

struct sockaddr_in client;
int client_sock;
		
socklen_t client_addr_len;		
client_addr_len = sizeof(client);
// 接受
client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);

5. connect 函数

客户端使用!

#include <sys/types.h>                 /* See NOTES */

#include <sys/socket.h>

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

sockdf:

    socket文件描述符

addr:

    传入参数,指定服务器端地址信息,含IP地址和端口号

addrlen:

    传入参数,传入sizeof(addr)大小

返回值:

   

  成功 返回 0失败 返回 -1, 设置 errno

 可以使用以下方式进行打印输出失败报错信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。

例:

int sockfd = 0;
struct sockaddr_in serveraddr;

sockfd = socket(AF_INET, SOCK_STREAM, 0);

serveraddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
serveraddr.sin_port = htons(5000);

// 连接服务器
connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

6. read 函数

#include <unistd.h>

ssize_t read (int fd, void *buf, size_t count);
 

fd:

    socket文件描述符;

buf:

    存储读取到的数据,一般传char *类型或字符数组;

count:

    指定最多读取的大小。

返回值:

    读取成功返回读取到的字节数,读取失败返回 -1,设置errno

 可以使用以下方式进行打印输出失败报错信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

从socket文件符中,读取count指定的大小以内的数据存储到buf中。

例:

int client_sock;
char buf[256];
// 对client_sock的赋值这里省略...

len = read(client_sock, buf, sizeof(buf)-1);

7. write 函数

#include <unistd.h>

ssize_t write (int fd, const void *buf, size_t count);

fd:

    socket文件描述符;

buf:

    需要发送(写入)的数据;

count:

    指定最多发送(写入)的大小。

返回值:

    发送(写入)成功返回写入的字节数,发送(写入)失败返回 -1,设置errno

 可以使用以下方式进行打印输出失败报错信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

例:

int client_sock;
char buf[256];
// 对client_sock的赋值这里省略...

len = write(client_sock, buf, sizeof(buf)-1);

8. close 函数

#include <unistd.h>

int close (int fd);

fd:

    socket文件描述符;

返回值:

    成功返回 0,失败返回 -1,并适当设置errno。

 可以使用以下方式进行打印输出失败报错信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

close()关闭一个文件描述符。

例:

int client_sock;
// client_sock= socket(AF_INET, SOCK_STREAM, 0);

close(client_sock);


四、回声服务器案例

描述:

客户端连接服务器,给服务器发送“hello world!”,服务器接收到后,将信息打印输出后,原封不动的给客户端发送回去,客户端接收到到后,也就数据打印输出,程序结束。

1. 服务器

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>        // strerror
#include <ctype.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <errno.h>

#define SERVER_PORT 5000


int main(void) {
	
	int ret = 0;
	int sock;	// 通信套接字
	struct sockaddr_in server_addr;
	
	// 1.创建通信套接字
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sock) {
		fprintf(stderr, "create socket error, reason: %s\n", strerror(errno));
		exit(-1);
	}
	
	// 2.清空标签,写上地址和端口号
	bzero(&server_addr, sizeof(server_addr));
	
	server_addr.sin_family = AF_INET;	// 选择协议组ipv4
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	// 监听本地所有IP地址
	server_addr.sin_port = htons(SERVER_PORT);			// 绑定端口号
	
	// 3.绑定
	ret = bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (-1 == ret) {
		fprintf(stderr, "socket bind error, reason: %s\n", strerror(errno));
		close(sock);
		exit(-2);
	}
	
	// 4.监听,同时监听128个请求
	ret = listen(sock, 128);
	if (-1 == ret) {
		fprintf(stderr, "listen error, reason: %s\n", strerror(errno));
		close(sock);
		exit(-2);
	}
	
	printf("等待客户端的链接\n");
	
	int done = 1;
	
	while (done) {
		
		struct sockaddr_in client;
		int client_sock;
		char client_ip[64];
		int len = 0;
		char buf[256];
		
		socklen_t client_addr_len;		
		client_addr_len = sizeof(client);
		// 5.接受
		client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
		if (-1 == client_sock) {
			perror("accept error");
			close(sock);
			exit(-3);
		}
		
		// 打印客户端IP地址和端口号
		printf("client ip: %s\t port: %d\n",
				inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)),
				ntohs(client.sin_port));
		
		
		// 6.读取客户端发送的数据
		len = read(client_sock, buf, sizeof(buf)-1);
		if (-1 == len) {
			perror("read error");
			close(sock);
			close(client_sock);
			exit(-4);
		}
		
		buf[len] = '\0';
		printf("recive[%d]: %s\n", len, buf);
		
		// 7.给客户端发送数据
		len = write(client_sock, buf, len);
		if (-1 == len) {
			perror("write error");
			close(sock);
			close(client_sock);
			exit(-5);
		}
		
		printf("write finished. len: %d\n", len);
		// 8.关闭客户端套接字
		close(client_sock);
	}
	
	// 9.关闭服务器套接字
	close(sock);
	
	return 0;
}

2. 客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>



#define SERVER_PORT		5000
#define SERVER_IP		"127.0.0.1"


int main(int argc, char *argv[]) {
	
	int ret = 0;
	int sockfd = 0;			// 通信套接字
	char *message = NULL;
	struct sockaddr_in serveraddr;
	int n = 0;
	char buff[64];
	
	if (2 != argc) {	
		fprintf(stderr, "Usage: ./echo_client message \n");
		exit(1);
	}
	
	// 获取第二个参数的字符串
	message = argv[1];
	printf("message: %s\n", message);
	
		// 1.创建通信套接字
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd) {
		perror("create sockfd error");
		exit(-1);
	}
	
	// 2.清空标签,写上地址和端口号
	bzero(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;							// IPv4
	inet_pton(AF_INET, SERVER_IP, &serveraddr.sin_addr);		// 服务器地址
	serveraddr.sin_port = htons(SERVER_PORT);					// 服务器端口号
	
	// 3.连接服务器
	ret = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
	if (-1 == ret) {
		perror("connect error");
		close(sockfd);
		exit(-2);
	}
	
	// 4.给服务器发送数据
	ret = write(sockfd, message, strlen(message));
	if (-1 == ret) {
		perror("write error");
		close(sockfd);
		exit(-3);
	}
	
	// 5.接受服务器发送过来的数据
	n = read(sockfd, buff, sizeof(buff)-1);
	if (-1 == n) {
		perror("read error");
		close(sockfd);
		exit(-4);
	}
	
	if (n > 0) {
		buff[n] = '\0';
		printf("receive: %s\n", buff);
	} else {
		perror("error!!!\n");
	}
	
	printf("client finished.\n");
	// 6.关闭套接字
	close(sockfd);
	
	return 0;
}

3. 运行测试

1. 服务器

ygt@YGT:~/echo_server$ gcc echo_server.c -o echo_server
ygt@YGT:~/echo_server$ ./echo_server
等待客户端的链接
client ip: 127.0.0.1     port: 41168
recive[12]: hello world!
write finished. len: 12

这里打印客户端的IP地址是127.0.0.1,是因为我是在同一台Linux系统中进行测试的,所以打印的是这个本地地址。

2. 客户端

root@YGT:/home/ygt/echo_server# gcc echo_client.c -o echo_client
root@YGT:/home/ygt/echo_server# ./echo_client "hello world!"
message: hello world!
receive: hello world!
client finished.

再来测试一下,在Linux中运行服务器程序,然后再window环境使用cmd控制台敲命令telnet去连接服务器。

才cmd中,telnet 后面接 服务器的ip地址 和 端口号

 当按下回车键后,就连接上服务器了,服务器也接受到了客户端的IP地址和端口号,并将其打印出来;然后客户端将字符 ‘h’ 发送给了服务器,服务器接收到后将其打印出来,然后给客户端也发送字符 'h',但是我们在cmd上是没有接收功能的,所以就没有接收到服务器发送过来的消息;最后服务器发送完成后就close断开了和客户端的连接,cmd这边就提示“遗失对主机的连接”。


五、总结

Linux环境中的C/C++ socket 与Window环境中的C/C++ socket类似。

创建服务器时需要按照指定流程来创建,根据上面图Socket 通信创建流程图来创建即可。

注意调用系统函数失败时,可以打印失败原因帮助我们定位问题。

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

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

相关文章

新C++(5):异常

"山&#xff0c;请你慢些走向我呀~"一、异常初始每当我们使用传统C写一些诸如malloc\realloc,或者不允许传入的参数为空(nullptr)时&#xff0c;我们时长会加一个断言(assert),一旦条件为false立即终止程序&#xff0c;不仅如此&#xff0c;当申请的空间够大&#xf…

二叉树:二叉树的最近公共祖先

二叉树的最近公共祖先 文章目录一、题目描述二、解题思路三、代码解析一、题目描述 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c…

Vue实现下载文件而非浏览器直接打开

问题背景 对于一个txt文本、图片、视频、音频等浏览器可以直接使用浏览器进行预览的文件&#xff0c; 使用传统的a标签加download属性进行下载是行不通的&#xff0c;会在浏览器直接打开&#xff0c;因此需要搞一个新的方式进行下载。 实现流程 实现思路 这里使用Vue自定义…

ElasticSearch从入门到出门【中】

文章目录DSL查询文档DSL查询分类全文检索查询使用场景基本语法示例精准查询term查询range查询地理坐标查询矩形范围查询附近查询复合查询相关性算分算分函数查询布尔查询搜索结果处理排序普通字段排序地理坐标排序分页基本的分页深度分页问题高亮高亮原理实现高亮RestClient查询…

档案数据智能采集工厂模型设计与实现

档案信息化从业人员肯定对异构系统数据采集过程中碰到的各种问题深有体会&#xff0c;有源系统供应商不配合的&#xff1b;数据接口不开放的&#xff1b;归档数据不符合规范的&#xff1b;数据敏感不提供的&#xff1b;等等&#xff1b;不一而足。但不幸的是档案信息系统处于政…

C 语言零基础入门教程(二十二)

C 错误处理 C 语言不提供对错误处理的直接支持&#xff0c;但是作为一种系统编程语言&#xff0c;它以返回值的形式允许您访问底层数据。在发生错误时&#xff0c;大多数的 C 或 UNIX 函数调用返回 1 或 NULL&#xff0c;同时会设置一个错误代码 errno&#xff0c;该错误代码是…

VS2017编译c dll的方法-编译bsio

VS2017编译c dll 有三种方法 &#xff08;1&#xff09;.h和.c文件函数增加 __declspec(dllexport) &#xff08;2&#xff09;仅在.h函数添加__declspec(dllexport) &#xff08;3&#xff09;使用.def文件&#xff0c;不用在.h和.c文件的函数前增加__declspec(dllexport) …

【数据结构初阶】顺序表的实现(文末附原码)

⭐博客主页&#xff1a;️CS semi主页 ⭐欢迎关注&#xff1a;点赞收藏留言 ⭐系列专栏&#xff1a;数据结构初阶 ⭐代码仓库&#xff1a;Data Structure 家人们更新不易&#xff0c;你们的点赞和关注对我而言十分重要&#xff0c;友友们麻烦多多点赞&#xff0b;关注&#xff…

基于C讲解协程设计原理

协程设计原理 背景 以epoll处理fd为例&#xff1a; func () {while (1) {epoll_wait();for(;;) {recv();send();}} }在IO操作较为密集的情况下&#xff08;网络IO和磁盘IO操作多&#xff0c;CPU计算少&#xff09;&#xff0c;由于检测到IO事件后&#xff0c;需要进行同步的…

美颜sdk动态贴纸是什么?

美颜sdk如今已经成了广大视频拍摄平台的刚需&#xff0c;用户们也习惯了这种新颖的拍摄形式&#xff0c;原相机被无情“打入冷宫”&#xff0c;特别是短视频和直播平台中&#xff0c;绝大部分用户都在使用美颜sdk的趣味功能进行拍摄&#xff0c;“动态贴纸”就是一个非常好的例…

什么是PEPPOL BIS?

和常见的X12以及EDIFACT类似&#xff0c;PEPPOL BIS也是一种EDI标准&#xff0c;主要用于B2G和B2B交易&#xff0c;在欧盟的应用十分广泛。在业务场景中&#xff0c;PEPPOL不单单只是用于发票&#xff0c;从下单到开票流程中均可提供标准化的数据传输。 在此前的文章中&#x…

[Ansible系列]ansible tag介绍

简介 在大型项目当中&#xff0c;通常一个playbook会有非常多的task。而我们每次执行这个playbook时&#xff0c;都会将 所有task运行一遍。而事实上&#xff0c;在实际使用过程中&#xff0c;我们可能只是想要执行其中的一部分任务而已&#xff0c; 并不想把整个playbook完整跑…

【单链表】数据结构单链表的实现

前言&#xff1a;在之前的学习中我们已经了解了顺序表的相关知识内容&#xff0c;但是顺序表我们通过思考可以想到如下问题&#xff1a; 中间/头部的插入删除&#xff0c;时间复杂度为O(N)增容需要申请新空间&#xff0c;拷贝数据&#xff0c;释放旧空间。会有不小的消耗。增容…

性能技术分享|Jmeter+InfluxDB+Grafana搭建性能平台

一、引言最近在公司做性能技术分享时&#xff0c;发现同事对环境搭建能力&#xff0c;还是有些欠缺。或许&#xff0c;这也是大部分性能测试工程师所欠缺的技能。因为绝大部分的性能测试工程师&#xff0c;要么是使用测试开发架构师搭建的性能平台&#xff0c;要么自己使用Jmet…

【Unity3D】激光灯、碰撞特效

1 需求描述 本文将模拟激光灯&#xff08;或碰撞&#xff09;特效&#xff0c;详细需求如下&#xff1a; 从鼠标位置发射屏幕射线&#xff0c;检测是否与物体发生碰撞当与物体发生碰撞时&#xff0c;在物体表面覆盖一层激光灯&#xff08;或碰撞&#xff09;特效本文代码见→激…

振弦采集模块VMTool 配置工具的传感器数据读取

振弦采集模块VMTool 配置工具的传感器数据读取 连接传感器 将振弦传感器两根线圈引线分别连接到 VM 模块模块的 SEN和 SEN-两个管脚。 通常不分正负极&#xff0c;任意连接即可。 连接模块电源 使用 5V~12V 直流电源连接到 VM 模块的 VIN 和 GND&#xff0c;电源正极连接到 VIN…

【数据结构基础】树 - 平衡二叉树(AVL)

平衡二叉树&#xff08;Balanced Binary Tree&#xff09;具有以下性质&#xff1a;它是一棵空树或它的左右两个子树的高度差的绝对值不超过1&#xff0c;并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。 最小二叉平…

数学建模与数据分析 || 1. 数学建模简介

数学建模简介 文章目录数学建模简介1. 数学建模比赛的理解2. 一般数据分析的流程3. 机器学习与统计数据分析4. 各种编程软件仅仅是工具&#xff0c;对问题的观察视角和解决问题的策略才是关键2.1 数学建模的特点2.2 以 python&#xff08;jupyter notebook工作界面&#xff09;…

JSR303校验(表单参数校验)

1、maven坐标<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId><version>3.0.1</version> </dependency>2、校验规则3、定义好校验规则还需要开启校验&#…

用户区网络缓冲区

用户区网络缓冲区 为什么要有用户层缓冲区 TCP内核协议栈&#xff0c;每个连接都有一个接收缓冲区和一个发送缓冲区&#xff0c;为啥用户层也要有&#xff1a; 为啥要有接收缓冲区 生产者速度大于消费者速度&#xff1a;客户端发送地太快&#xff0c;服务器处理不过来&#…