【嵌入式开发之网络编程】Socket套接字及TCP、UDP通信的实现

news2024/11/14 15:17:21

Socket套接字

现有计算机网络体系结构有三种划分形式:OSI七层协议TCP/IP四层协议结构五层协议结构。具体介绍参考:【嵌入式开发之网络编程】网络分层、OSI七层模型、TCP/IP及五层体系结构

网络的体系结构 (Network Architecture) 是计算机网络的各层及其协议的集合,就是这个计算机网络及其构件所应完成的功能的精确定义(不涉及实现)。

实现 (implementation) 是遵循这种体系结构的前提下,用何种硬件或软件完成这些功能的问题。

几种常见的网络编程接口

  • Berkeley UNIX 操作系统定义了一种 API,它又称为套接字接口 (socket interface)
  • 微软公司在其操作系统中采用了套接字接口  API,形成了一个稍有不同的 API,并称之为  Windows Socket
  • AT&T 为其 UNIX 系统 V 定义了一种 API,简写为 TLI (Transport Layer Interface)

套接字的基本概念

套接字(Socket)就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。

套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元

套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念。 Socket是由IP地址和端口结合的,提供向应用层进程传送数据包的机制。

Socket是面向客户/服务器模型而设计的。通信的一方扮演客户机的角色,另一方扮演服务器的角色。服务器在运行中一直监听套接字指定的传输层端口,并等待着客户机的连接请求。当服务器端收到客户机发来的连接请求以后,服务器会接受客户机的连接请求,双方建立连接后,就可进行数据的传递。

套接字的类型

Socket处于网络协议的传输层套接字可以分为流套接字、数据报套接字和原始套接字3种不同的类型。

  • 流式套接字 (SOCK_STREAM) 提供可靠的、面向连接的通信流;它使用TCP,从而保证数据传输的可靠性和顺序性。
  • 数据报套接字 (SOCK_DGRAM) 定义了一种不可靠、面向无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。
  • 原始套接字(SOCK_RAW) 允许直接访问底层协议,如IP或ICMP,它功能强大但使用较为不便,主要用于协议开发。 

套接字是一种特殊的文件描述符,UNIX域套接字用于进程间通信。

套接字是服务器和客户端各自维护一个“文件”,在建立连接后,向自己文件写入内容供对方读取或者读取对方内容,通信结束时关闭文件。 

套接字的地址族

socket通用地址族结构体

socket 网络编程接口中表示 socket 地址的是结构体 sockaddr,其定义如下:

#include <bits/socket.h>

struct sockaddr { 
        sa_family_t sa_family; 
        char sa_data[14]; 
}; 
 
typedef unsigned short int sa_family_t;

sa_family 成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对应。常见的协议 族(protocol family,也称domain)和对应的地址族入下所示:

协议族地址族描述
PF_UNIXAF_UNIXUNIX本地域协议族
PF_INETAF_INETTCP/IPv4协议族
PF_INET6AF_INET6TCP/IPv6协议族

宏 PF_* 和 AF_* 都定义在 bits/socket.h 头文件中,且后者与前者有完全相同的值,所以二者通常混用。

sa_data 成员用于存放 socket 地址值。但是,不同的协议族的地址值具有不同的含义和长度,如下所示:

协议族地址值含义和长度
PF_UNIX文件的路径名,长度可达108字节
PF_INET16 bit端口号和32 bit IPv4地址,共6个字节
PF_INET616 bit端口号、32 bit 流标识和 128 bit IPv6地址,共6个字节,32 bit范围ID,共26字节

由此可知,14 字节的sa_data 根本无法容纳多数协议族的地址值。 

因此,Linux定义了下面这个新的通用的 socket 地址结构体,这个结构体不仅提供了足够大的空间用于存放地址值,而且是内存对齐的。

#include <bits/socket.h>

struct sockaddr_storage 
{ 
        sa_family_t sa_family; 
        unsigned long int __ss_align; 
        char __ss_padding[ 128 - sizeof(__ss_align) ]; 
}; 
 
typedef unsigned short int sa_family_t;

socket专用地址族结构体

很多网络编程函数诞生早于 IPv4 协议,那时候都使用的是 struct sockaddr 结构体,为了向前兼容,现在sockaddr 退化成了(void *)的作用,传递一个地址给函数,至于这个函数是 sockaddr_in 还是 sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

UNIX 本地域协议族使用如下专用的 socket 地址结构体:

#include <sys/un.h>

struct sockaddr_un 
{ 
    sa_family_t sin_family; 
    char sun_path[108]; 
};

TCP/IP 协议族有 sockaddr_in 和 sockaddr_in6 两个专用的 socket 地址结构体,它们分别用于 IPv4 和 IPv6。

用于 IPv4的socket地址结构体 sockaddr_in:

#include <netinet/in.h>

struct sockaddr_in 
{ 
        sa_family_t sin_family;    /* __SOCKADDR_COMMON(sin_) */ 
        in_port_t sin_port;          /* Port number. */ 
        struct in_addr sin_addr; /* Internet address. */ 
        /* Pad to size of `struct sockaddr'. */ 
        unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]; 
}; 
 
struct in_addr 
{ 
        in_addr_t s_addr; 
}; 

 用于 IPv6的socket地址结构体 sockaddr_in6:

#include <netinet/in.h>

struct sockaddr_in6 
{ 
        sa_family_t sin6_family; 
        in_port_t sin6_port;                 /* Transport layer port # */ 
        uint32_t sin6_flowinfo;           /* IPv6 flow information */ 
        struct in6_addr sin6_addr;     /* IPv6 address */ 
        uint32_t sin6_scope_id;         /* IPv6 scope-id */ 
}; 
 
struct in6_addr {
        uint8_t   s6_addr[16];
};

typedef uint32_t in_addr_t;
typedef uint16_t in_port_t;

所有专用 socket 地址(以及 sockaddr_storage)类型的变量在实际使用时都需要转化为通用 socket 地址类型 sockaddr(强制转化即可),因为所有 socket 编程接口使用的地址参数类型都是 sockaddr。

以上内容参考:Linux网络协议之socket(套接字)通信基础

TCP通信的实现

套接字三元组

  • IP地址:标识计算机
  • 端口号:标识计算机当中的进程
  • 协议:指定数据传输的方式

TCP实现的基本框架

TCP服务端实现框架

  1. socket:创建一个用于监听所有客户端的套接字(是一个特殊的文件描述符)。
  2. bind:将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)。
  3. listen:设置监听,监听的文件描述符开始工作。
  4. accept:阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字(这个套接字与最开始创建的套接字不同,是另一个文件描述符)。 
  5. read和write:接收数据、发送数据。
  6. close:通信结束,断开连接,关闭文件。

TCP客户端实现框架

  1. socket:创建一个用于通信的套接(一个特殊的文件描述符)。
  2. connect:连接服务器,需要指定连接的服务器的IP和端口
  3. read和wirte:连接成功了,客户端可以直接和服务器通信,发送或者接收数据。
  4. close:通信结束,断开连接,关闭文件。

套接字函数

socket函数与通信域

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
        - 功能:创建一个套接字 
        - 参数: 
                - domain: 协议族 
                        AF_INET : ipv4 
                        AF_INET6 : ipv6 
                        AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信) 
                - type: 通信过程中使用的协议类型 
                        SOCK_STREAM : 流式协议 
                        SOCK_DGRAM : 报式协议 
                - protocol : 具体的一个协议。一般写0 
                        - SOCK_STREAM : 流式协议默认使用 TCP 
                        - SOCK_DGRAM : 报式协议默认使用 UDP 
                - 返回值: 
                        - 成功:返回文件描述符,操作的就是内核缓冲区。 
                        - 失败:-1

bind函数与通信结构体

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命 名 
        - 功能:绑定,将fd 和本地的IP + 端口进行绑定 
        - 参数: 
                - sockfd : 通过socket函数得到的文件描述符 
                - addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息 
                - addrlen : 第二个参数结构体占的内存大小 

示例:为套接字fd绑定通信结构体addr

addr.sin_family = AF_INET;
addr.sin_port = htons(5001);
addr.sin_addr.s_addr = 0;
bind(fd, (struct sockaddr *)&addr, sizeof(addr) );

listen函数

int listen(int sockfd, int backlog);      // /proc/sys/net/core/somaxconn 
        - 功能:监听这个socket上的连接 
        - 参数: 
                - sockfd : 通过socket()函数得到的文件描述符 
                - backlog : 未连接的和已经连接的和的最大值, 5 

accept函数

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 
        - 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接 
        - 参数: 
                - sockfd : 用于监听的文件描述符 
                - addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)
                - addrlen : 指定第二个参数的对应的内存大小 
        - 返回值: 
                - 成功 :用于通信的文件描述符 
                - -1 : 失败 

connect函数

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        - 功能: 客户端连接服务器 
        - 参数: 
                - sockfd : 用于通信的文件描述符 
                - addr : 客户端要连接的服务器的地址信息 
                - addrlen : 第二个参数的内存大小 
        - 返回值:成功 0, 失败 -1 

read和write函数

ssize_t write(int fd, const void *buf, size_t count);      // 写数据 
ssize_t read(int fd, void *buf, size_t count);             // 读数据

实现代码

服务端

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

#define BACKLOG 5

int main(int argc, const char *argv[])
{
	int fd, newfd;
	char buf[BUFSIZ] = {};//BUFSIZ 8142 
	struct sockaddr_in addr;//IPv4 socket专用地址
	int ret;

	//判断主函数传入参数
	if (argc < 3) {
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}

	/*创建套接字
	 * 使用IPv4互联网协议
	 * 流式套接字SOCK_STREAM对应协议TCP,所以第三个参数可以为0*/
	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		exit(0);
	}

	/*对地址结构体进行初始化*/
	addr.sin_family = AF_INET;
	addr.sin_port = htons(atoi(argv[2]));
#if 0
	addr.sin_addr.s_addr = inet_addr(argv[1]);
#else
	//IP地址序转换,点分十进制IP转换成网络字节序存储在addr.sin_addr中
	if (inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}
#endif

    //地址快速重用
	int flag = 1, len = sizeof(int);
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) {
		perror("setsockopt");
		exit(1);
	}

	//绑定通信结构体
	if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
		perror("bind");
		exit(0);
	}

	//设置套接字为监听模式
	if (listen(fd, BACKLOG) == -1) {
		perror("listen");
		exit(0);
	}
	
	//接受客户端的连接请求,生成新的和客户端通信的套接字
	if ((newfd = accept(fd, NULL, NULL)) < 0) {
		perror("listen");
		exit(0);
	}
	while(1){
		memset(buf, 0, BUFSIZ);
		ret = read(newfd, buf, BUFSIZ);
		if (ret < 0){
			perror("read");
			exit(0);
		} else if (ret == 0) {
			break;
		} else {
			printf("buf = %s\n", buf);
		}
	}
	close(newfd);
	close(fd);
	
	return 0;
}

客户端

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

#define BACKLOG 5

int main(int argc, const char *argv[])
{
	int fd;
	struct sockaddr_in addr;
	char buf[BUFSIZ] = {};

	//判断主函数传入参数
	if (argc < 3) {
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}

	/*创建套接字
	 * 使用IPv4互联网协议
	 * 流式套接字SOCK_STREAM对应协议TCP,所以第三个参数可以为0*/
	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		exit(0);
	}

	//domain和port赋值
	addr.sin_family = AF_INET;
	addr.sin_port = htons(atoi(argv[2]));

	//IP地址序转换,点分十进制IP转换成网络字节序存储在addr.sin_addr中
	if (inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}

	//向服务端发起连接
	if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
		perror("connect");
		exit(0);
	}

	while(1){
		printf("Input->");
		fgets(buf, BUFSIZ, stdin);
		write(fd, buf, strlen(buf));
	}
	close(fd);
	
	return 0;
}

编译Makefile


CC = gcc
CFLAGS = -Wall

all:client server

clean:
	rm client server

运行结果

客户端输入:

$ ./client 127.0.0.1 8888
Input->Hello World
Input->

服务器端输出: 

$ ./server 0 8888
buf = Hello World

UDP通信的实现 

UDP实现的基本框架

UDP服务器实现框架 

  1. socket:创建一个用于监听所有客户端的套接字(是一个特殊的文件描述符)。
  2. bind:将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)。
  3. revfrom和sendto:接收数据、发送数据。
  4. close:通信结束,断开连接,关闭文件。

相比TCP通信,UDP通信少了bind和listen环节,传送数据的函数也有所区别。

UDP客户端实现框架

  1. socket:创建一个用于通信的套接(一个特殊的文件描述符)。
  2. revfrom和sendto:接收数据、发送数据。
  3. close:通信结束,断开连接,关闭文件。

相比TCP通信,UDP通信connect环节。

UDP通信中传递数据的函数

UDP 套接字是无连接协议,必须使用 sendto 函数发送数据,必须使用 recvfrom 函数接收数据,发送时需指明目的地址。sendto 函数与 send 功能基本相同, recvfrom 与 recv 功能基本相同,只不过 sendto 函数和 recvfrom 函数参数中都带有对方地址信息,这两个函数是专门为 UDP 协议提供的。

write/read到send/recv

#include <unistd.h>

ssize_t write(int fd, const void buf[.count], size_t count);
-功能:从 buf 指向的缓冲区中写入 count 个字节的数据到 fd 所表示的文件或设备中。
-参数:
     fd:文件描述符,表示要写入数据的文件或设备。
     buf:一个指向要写入数据的缓冲区的指针。
     count:要写入的字节数。
-返回值:成功,返回实际写入的字节数;失败,返回-1,并设置errno

ssize_t read(int fd, void buf[.count], size_t count);
-功能:读取fd对应的文件,并将读取的数据保存到buf,以nbytes为读取单位
-参数:
     fd:文件描述符,表示要读取数据的文件或设备。
     buf:一个指向要读取数据的缓冲区的指针。
     count:要读取的字节数。
-返回值:成功,返回实际读取的字节数;失败,返回-1,并设置errno
#include <sys/socket.h>

ssize_t send(int sockfd, const void buf[.len], size_t len, int flags);
-功能:是一个系统调用函数,用来发送消息到一个套接字中,和sendto,sendmsg功能相似。
-参数:
     fd:文件描述符,表示要发送数据的文件或设备。
     buf:一个指向要发送数据的缓冲区的指针。
     count:要发送的字节数。
     flags:下列标志中的0个或多个
          MSG_CONFIRM :用来告诉链路层,
          MSG_DONTROUTE:不要使用网关来发送数据,只发送到直接连接的主机上。通常只有诊断或者路由程序会使用,这只针对路由的协议族定义的,数据包的套接字没有。
          MSG_DONTWAIT :启用非阻塞操作,如果操作阻塞,就返回EAGAIN或EWOULDBLOCK
          MSG_EOR :当支持SOCK_SEQPACKET时,终止记录。
          MSG_MORE :调用方有更多的数据要发送。这个标志与TCP或者udp套接字一起使用
          MSG_NOSIGNAL :当另一端中断连接时,请求不向流定向套接字上的错误发送SIGPIPE ,EPIPE 错误仍然返回。
          MSG_OOB:在支持此概念的套接字上发送带外数据(例如,SOCK_STREAM类型);底层协议还必须支持带外数据

-返回值:成功,返回实际发送的字节数;失败,返回-1,并设置errno

ssize_t recv(int sockfd, void buf[.len], size_t len, int flags);
-功能:是一个系统调用函数,用来接收消息到一个套接字中,和recvfrom,recvmsg功能相似。
-参数:
     fd:文件描述符,表示要接收数据的文件或设备。
     buf:一个指向要接收数据的缓冲区的指针。
     count:要接收的字节数。
     flags:一个或多个标识

-返回值:成功,返回实际接收的字节数;失败,返回-1,并设置errno

sendto与recvfrom

#include <sys/socket.h>

ssize_t sendto(int sockfd, const void buf[.len], size_t len, int flags, 
               const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t recvfrom(int sockfd, void buf[restrict .len], size_t len, int flags, 
                 struct sockaddr *_Nullable restrict src_addr, 
                 socklen_t *_Nullable restrict addrlen);

这两个函数的前四个参数同recv/send一样,后两个参数是通信结构体和结构体的宽度。

UDP通信实现代码

服务端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

/* UDP协议的实现过程server端
 * 第一步:验证传入参数个数
 * 第二步:创建套接字
 * 第三步:设置通信结构体
 * 第四步:绑定通信结构体
 * 第五步:接收客户端数据并打印*/
int main(int argc, const char *argv[])
{
	int fd;
	struct sockaddr_in addr;
	char buf[BUFSIZ] = {};
	if (argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	/*创建套接字
	 * 创建SOCK_DGRAM型套接字,对应的协议UDP*/
	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("socket");
		exit(EXIT_FAILURE);
	}

	/*设置通信结构体*/
	bzero(&addr, sizeof(addr));
	addr.sin_port = htons(atoi(argv[2]));
	if (inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}

	/*绑定通信结构体*/
	if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
		perror("bind");
		exit(EXIT_FAILURE);
	}

	while(1) {
		bzero(buf, BUFSIZ);
		recvfrom(fd, buf, BUFSIZ, 0, NULL, NULL);
		printf("buf = %s\n", buf);
	}
	close(fd);
	
	return 0;
}

客户端

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

/* UDP协议的实现过程client端
 * 第一步:验证传入参数的个数
 * 第二步:创建套接字
 * 第三步:设置通信结构体
 * 第四步:向server端发送数据*/
int main(int argc, const char *argv[])
{
	int fd;
	struct sockaddr_in addr;
	char buf[BUFSIZ] = {"Hello World"};
	socklen_t addrlen = sizeof(addr);
	if (argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	/*创建套接字*/
	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("socket");
		exit(EXIT_FAILURE);
	}

	/*设置通信结构体*/
	bzero(&addr, sizeof(addr));
	/*将传入的端口转换为int型,并转换为大端存储*/
	addr.sin_port = htons(atoi(argv[2]));
	if (inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}

	while(1) {
		bzero(buf, BUFSIZ);
		printf("Input->");
		fgets(buf, BUFSIZ, stdin);
		sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&addr, addrlen);
	}
	close(fd);
	
	return 0;
}

Makefile文件

CC=gcc
CFLAGS=-Wall

all:server client

clean:
	rm server client

运行结果

$ ./server 0 8888
buf = woeoeoeo

buf = woeooe

总结 

UDP和TCP的主要区别在于连接性、可靠性、传输效率、连接对象、字节消耗和应用场景。

  1. 连接性:TCP是面向连接的,意味着在数据传输之前,必须先建立连接。这个过程通常涉及到三次握手,以确保双方都准备好进行通信。而UDP是无连接的,即发送数据之前不需要建立连接,它允许应用程序在没有任何前期设置的情况下发送数据报。
  2. 可靠性:TCP提供了可靠的数据传输服务,保证了数据的无差错、不丢失、不重复,并且按序到达。它通过使用确认机制、重传机制、校验和等方式来确保数据的完整性和准确性。相比之下,UDP不保证数据的可靠交付,它不会进行数据包的重传,也不保证数据包的顺序,因此被认为是不可靠的数据报协议。
  3. 传输效率:UDP具有较好的实时性和工作效率,它的首部开销小,只有8个字节,而且不需要建立连接和进行复杂的握手过程,因此传输速度较快。TCP的首部开销较大,至少有20个字节,并且由于它提供了可靠的传输服务,所以在传输效率上通常不如UDP。
  4. 连接对象:TCP连接只能是点到点的,即一条连接只有两个端点。而UDP支持一对一、一对多、多对一和多对多的交互通信,这使得UDP在某些应用场景下更加灵活。
  5. 字节消耗:TCP的首部开销较大,至少有20个字节。而UDP的首部开销小,只有8个字节。
  6. 应用场景:TCP由于其可靠的传输特性,经常用于需要高度数据完整性和准确性的场景,如网页浏览、文件传输等。而UDP由于其传输速度快、开销小的特点,经常用于对实时性要求较高的场景,如在线视频、在线游戏、实时通信等。

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

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

相关文章

MySQL索引(二)

MySQL索引(二) 文章目录 MySQL索引(二)MySQL有哪些索引&#xff1f;MySQL的主键是聚簇索引吗&#xff1f;聚簇索引和非聚簇索引的区别什么是覆盖索引什么是回表主键问题 外键约束什么是外键什么是外键约束外键带来的问题 联合索引最左匹配原则如何建立联合索引索引下推 学习地址…

适合 Spring Boot 3.0x的Redis 分布式锁

Spring Boot 中的 Redis 分布式锁 在分布式系统中&#xff0c;多个进程同时访问共享资源时&#xff0c;很容易出现并发问题。为了避免这些问题&#xff0c;我们可以使用分布式锁来保证共享资源的独占性。Redis 是一款非常流行的分布式缓存&#xff0c;它也提供了分布式锁的功能…

Ai+若依(页面调整--去除若依的各种痕迹,采用自己的):【07篇】

页面调整 如果使用若依框架项目做为脚手架,那么我们肯定需要在页面显示中,符合自己公司或者项目的标识才行,需要更换的地方很多,我们依次来解决它 浏览器标签页logo标识、标题 系统页面中的logo标识、标题 去除源码地址 & 文档地址 主题风格和菜单图标 登录名称及背景…

Java GIS开发工具包-GeoTools浅谈

目录 前言 一、关于Geotools 1、GeoTools简介 2、官方仓库 3、使用Geotools的一些项目 二、Geotools架构 1、功能架构 2、Geotools支持的数据格式 三、Geotools科研热点 1、知网信息 2、百度学术 四、总结 前言 地理信息&#xff0c;智联万物。地理信息在我们的生活中…

《机器学习》—— OpenCV 对图片的各种操作(均值、方框、高斯、中值滤波处理)

文章目录 1、对有椒盐噪声的图片进行均值、方框、高斯、中值滤波处理2、给图像边缘增加边框3、对图片进行阈值化操作 1、对有椒盐噪声的图片进行均值、方框、高斯、中值滤波处理 均值滤波 cv2.blur是 OpenCV 库中的一个函数&#xff0c;用于对图像进行均值模糊处理。这个函数通…

【Mysql】通过Keepalived搭建mysql双主高可用集群

一、环境信息 主机名ip操作系统mysql版本VIP&#xff08;虚拟ip&#xff09;hadoop01192.168.10.200centos7_x865.7192.168.10.253hadoop03192.168.10.202centos7_x865.7 二、mysql集群搭建 两台节点&#xff0c;如果未部署mysql服务&#xff0c;部署文档请看【Mysql】mysql…

前端自动导入依赖

前言 开发中通常会有很多导入语句&#xff0c;如何确保一些通用的api和hook无需每次手动导入即可使用。 <script setup lang"ts"> import { ref, reactive } from "vue" import { useRoute, useRouter } from "vue-router" import { log…

C++——string类(1)

### string是C中的一种类&#xff0c;在标准库中的&#xff1b;可以直接对字符串进行一系列操作。 string类类型的构造 1、无参构造&#xff1a; string(); 定义string对象的时候不给值&#xff0c;这个string类的对象里面没有字符&#xff1b; #include<iostream> #in…

VsCode中Jupyter找不到内核的问题

问题描述 之前可以选择内核&#xff08;可能要先 “Python: 选择解释器”&#xff0c;也可能不用&#xff09;&#xff0c;并且是自己检测到 conda 环境中的 Python。 但是后来会突然找不到内核&#xff0c;点击选择内核&#xff0c;会在空白下加载很久&#xff0c;无果。 这…

MIPI联盟D-PHYv1.2规范阅读笔记二之物理层接口协议PPI

本文阅读自eetop.cn_mipi_D-PHY_specification_v1-2.pdf Logical PHY-Protocol Interface Description&#xff08;PHY物理层协议接口描述PPI&#xff09; PHY物理层协议接口被用于连接物理层和通信栈与更高层协议栈之间建立联系。 表 31 定义了物理层协议接口&#xff08;P…

day10JS-this的使用规则

1. this情况总结 开启严格模式&#xff1a; "use strict"; //开启严格模式 1.全局&#xff1a;非严格this--->window &#xff0c;严格 this--->window。 2.普通函数执行&#xff1a;函数名() 非严格this-->window &#xff0c;严格 this--->undefined…

全新的大语言模型Grok-2,最新测评!!

埃隆马斯克再次引发轰动&#xff0c;他旗下的xAI公司推出了全新的大语言模型Grok-2&#xff01; 最新的Grok-2测试版已经发布&#xff0c;用户可以在&#x1d54f;平台上体验小版本的Grok-2 mini。 马斯克还通过一种谜语般的方式揭开了困扰大模型社区一个多月的谜团&#xff1a…

C/C++ 包管理器 Conan 安装及使用

文章目录 Github官网文档简介安装 Conan 包管理器Conan 私有存储库创建 profile 文件添加远程存储库依赖包操作命令 Artifactory 私有存储库下载安装包&#xff08;推荐&#xff09;Docker 方式安装 Conan 官方示例 Github https://github.com/conan-io/conan 官网 https://…

零代码上手,工厂数据管理从未如此简单

在当今快节奏的工业环境中&#xff0c;工厂管理者们越来越依赖于数据分析来优化生产流程、提高效率和降低成本。然而&#xff0c;传统的数据分析工具往往复杂难用&#xff0c;且动辄需要高昂的费用&#xff0c;这让很多工厂望而却步。不过最近本人发现了一款非常实用的报表工具…

智能废弃瓶子垃圾箱:城市环境的绿色守护者

随着城市化进程的加速&#xff0c;生活垃圾的处理成为城市管理中的一大挑战。智能废弃瓶子垃圾箱的出现&#xff0c;不仅提高了垃圾回收的效率&#xff0c;还促进了资源的循环利用&#xff0c;成为智慧城市建设的重要组成部分。 目录 技术概述 核心功能 应用场景 环境与社会…

Java数据结构栏目总结

目录 数组与稀疏数组 队列&#xff1a;自己用数组模拟Queue 环形队列&#xff0c;取模【取余】实现. 单链表(LinkList) 双向链表&#xff08;Next 、Pre&#xff09; 单向环形链表 线性结构 数组与稀疏数组 稀疏数组&#xff0c;很多0值&#xff0c;可用于压缩 特点&a…

在 AMD GPUs 上进行图分析使用 Gunrock

Graph analytics on AMD GPUs using Gunrock — ROCm Blogs 图和图分析是可以帮助我们理解复杂数据和关系的相关概念。在这种背景下&#xff0c;图是一种数学模型&#xff0c;用于表示实体&#xff08;称为节点或顶点&#xff09;及其连接&#xff08;称为边或链接&#xff09;…

【CTF Web】BUUCTF BUU BRUTE 1 Writeup(弱口令+暴力破解+字典攻击)

BUU BRUTE 1 1 点击启动靶机。 解法 随便输个用户名。 试试 admin。 用 burp 抓包。 生成四位数字的字典。 导入字典到 burp。 添加载荷位置。 开始爆破。破解完成&#xff0c;密码&#xff1a;6490。取得 flag。 注意 如果破解得慢的话&#xff0c;记得要续期靶机。不然靶机…

算法工程师秋招面试问题总结

大模型分布式训练并行 一般有 tensor parallelism、pipeline parallelism、data parallelism 几种并行方式,分别在模型的层内、模型的层间、训练数据三个维度上对 GPU 进行划分。三个并行度乘起来,就是这个训练任务总的 GPU 数量。 1.数据并行 数据并行是最常见的并行形式…

2024.8.27 作业

1> 提示并输入一个字符串&#xff0c;统计该字符串中字母个数、数字个数、空格个数、其他字符的个数 #include <iostream>using namespace std;int main() {string s;cout << "请输入字符串>>>";getline(cin,s);int letter0,digit0,blank0,…