Linux网络编程——socket 通信基础

Linux网络编程——socket 通信基础

    • 1. socket 介绍
    • 2. 字节序
      • 2.1 简介
      • 2.2 字节序举例
      • 2.3 字节序转换函数
    • 3. socket 地址
      • 3.1 通用 socket 地址
      • 3.2 专用 socket 地址
    • 4. IP地址转换(字符串ip -> 整数,主机、网络字节序的转换 )
    • 5. TCP 通信流程
    • 6. 套接字函数

1. socket 介绍

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

    socket 可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个 逻辑上的概念。它是网络环境中 进程间通信API,也是可以被命名寻址通信端点,使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的 socket 中,该 socket 通过与 网络接口卡NIC)相连的传输介质将这段信息送到另外一台主机socket 中,使对方能够接收到这段信息。socket 是由 IP 地址端口 结合的,提供向应用层进程传送数据包的机制

    socket 本身有“ 插座 ”的意思,在 Linux 环境下,用于表示 进程间网络通信特殊文件类型本质为 内核 借助 缓冲区 形成的 伪文件。既然是文件,那么理所当然的,我们可以使用 文件描述符 引用套接字。与管道类似的,Linux 系统将其封装成文件的目的是为了 统一接口,使得 读写套接字读写文件 的操作一致。区别是 管道 主要应用于 本地进程间通信,而 套接字 多应用于 网络进程间数据的传递

在这里插入图片描述

使用 文件描述符 fd 引用 socket
在这里插入图片描述

套接字通信 分两部分:

  • 服务器端被动 接受连接,一般不会主动发起连接
  • 客户端主动 向服务器发起连接

socket 是一套 通信的接口LinuxWindows 都有,但是有一些细微的差别

2. 字节序

2.1 简介

    现代 CPU 的累加器 一次都能 装载(至少)4 字节(这里考虑 32 位机),即一个整数。那么这 4 字节 在 内存 中排列的顺序 将影响它被累加器装载成的整数的值,这就是字节序问题。在各种计算机体系结构中,对于字节等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元比特字节双字 等等)应该以什么样的顺序进行传送。如果不达成一致的规则,通信双方将无法进行正确的编码/译码从而导致通信失败。

    字节序,顾名思义 字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序 (一个字节的数据当然就无需谈顺序的问题了)。

    字节序 分为 小端字节序Big-Endian) 和 大端字节序Little-Endian)。

  • 小端字节序则 是指 整数的 低位字节 则存储在内存的 地址 处,而 高位字节 存储在内存的 地址 处。
  • 大端字节序 是指一个 整数的 最高位字节23 ~ 31 bit)存储在内存的 地址 处,低位字节0 ~ 7 bit)存储在 内存的 地址 处;

2.2 字节序举例

小端字节序

  • 0x 01 02 03 04 (十六进制,四字节ff = 255)

  • 内存的方向 ----->

  • 内存的低位 -----> 内存的高位
    04 03 02 01

0x 11 22 33 44 12 34 56 78八个字节
在这里插入图片描述

大端字节序

  • 0x 01 02 03 04
  • 内存的方向 ----->
  • 内存的低位 -----> 内存的高位
    01 02 03 04

0x 12 34 56 78 11 22 33 44
在这里插入图片描述

  • 通过代码检测当前主机的字节序
#include <stdio.h>

int main() {

    union {
        short value;    // 2字节
        char bytes[sizeof(short)];  // char[2]
    } test;

    test.value = 0x0102;
    if((test.bytes[0] == 1) && (test.bytes[1] == 2)) {
        printf("大端字节序\n");
    } else if((test.bytes[0] == 2) && (test.bytes[1] == 1)) {
        printf("小端字节序\n");
    } else {
        printf("未知\n");
    }

    return 0;
}

在这里插入图片描述

2.3 字节序转换函数

当格式化的数据在两台使用不同字节序的主机之间直接传递时,接收端必然错误的解释之。

  • 解决问题的方法是:发送端 总是 把要发送的数据 转换成 大端字节序数据 后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。

    网络字节顺序TCP/IP规定好的 一种数据表示格式,它与具体的 CPU 类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释,网络字节顺序 采用 大端排序 方式。


BSD Socket 提供了封装好的 转换接口,方便程序员使用。

  • 包括从 主机字节序网络字节序转换函数htonshtonl
  • 网络字节序主机字节序 的转换函数:ntohsntohl
h 	- host 主机,主机字节序
to 	- 转换成什么
n 	- network 网络字节序
s 	- short : unsigned short   无符号短整型,两个字节
l 	- long : unsigned int		 无符号长整型,四个字节
#include <arpa/inet.h>
/*
	  网络通信时,需要将主机字节序转换成网络字节序(大端),
	  另外一段获取到数据以后根据情况将网络字节序转换成主机字节序。
*/

// 32位,转IP
uint32_t htonl(uint32_t hostlong);    	// 主机字节序 -> 网络字节序
uint32_t ntohl(uint32_t netlong); 		// 网络字节序 -> 主机字节序

// 16位,转换端口
uint16_t htons(uint16_t hostshort); 	// 主机字节序 -> 网络字节序
uint16_t ntohs(uint16_t netshort);		// 网络字节序 -> 主机字节序
#include <stdio.h>
#include <arpa/inet.h>

int main() {

    // htonl  转换IP
    char buf[4] = {192, 168, 1, 100};
    int num = *(int *)buf;
    int sum = htonl(num);
    unsigned char *p = (char *)&sum;

    printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3));

    printf("=======================\n");

    // htons 转换端口
    unsigned short a = 0x0102;
    printf("a : %x\n", a);
    unsigned short b = htons(a);
    printf("b : %x\n", b);

    printf("=======================\n");

    // ntohl 转换IP
    unsigned char buf1[4] = {1, 1, 168, 192};
    int num1 = *(int *)buf1;
    int sum1 = ntohl(num1);
    unsigned char *p1 = (unsigned char *)&sum1;
    printf("%d %d %d %d\n", *p1, *(p1+1), *(p1+2), *(p1+3));
    
    // ntohs 转换端口


    return 0;
}

3. socket 地址


    socket 地址 其实是一个 结构体,封装 端口号IP 等信息。 后面的 socket 相关的 api 中需要使用到这个 socket地址

  • 客户端 -> 服务器IP, Port

3.1 通用 socket 地址


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

#include <bits/socket.h>

struct sockaddr {
	sa_family_t sa_family;	// 地址族类型
	char sa_data[14]; 		// 14字节
};

typedef unsigned short int sa_family_t;		// 2字节

    sa_family 成员是 地址族类型sa_family_t)的 变量地址族类型 通常与 协议族类型 对应。常见的 协议族protocol family,也称 domain)和对应的 地址族address family) 如下所示:
在这里插入图片描述
     PF_*AF_* 都定义在 bits/socket.h 头文件中,后者前者完全相同的值,所以二者通常混用

    sa_data 成员用于存放 socket 地址值。但是,不同的协议族的地址值具有不同的含义和长度,如下所示:
在这里插入图片描述

     由上表可知,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;

3.2 专用 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_insockaddr_in6 两个专用的 socket 地址结构体,它们分别用于 IPv4IPv6

#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;
};

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 */
};

typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;

#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))

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

4. IP地址转换(字符串ip -> 整数,主机、网络字节序的转换 )

    通常,人们习惯用 可读性好的字符串 来表示 IP 地址,比如用 点分十进制字符串 表示 IPv4 地址,以及用 十六进制字符串 表示 IPv6 地址。但编程中我们需要先把它们 转化为 整数(二进制数)方能使用。而记录日志时则相反,我们要把整数表示的 IP 地址 转化为 可读的字符串。下面 3 个函数可用于用 点分十进制字符串 表示的 IPv4 地址 和 用 网络字节序整数 表示的 IPv4 地址 之间的转换:

#include <arpa/inet.h>
 
// 下面的这些函数比较久,使用起来比较麻烦,不推荐使用 
in_addr_t inet_addr(const char *cp); 	// 点分十进制字符串 -> 整数
int inet_aton(const char *cp, struct in_addr *inp);	// 点分十进制字符串 -> 整数, 并保存到结构体指针 inp 中
char *inet_ntoa(struct in_addr in); 	// 整数 -> 点分十进制字符串

    下面这对更新的函数也能完成前面 3 个函数同样的功能,并且它们同时适用 IPv4 地址IPv6 地址:⭐️

#include <arpa/inet.h>

// p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af, const char *src, void *dst);
	af:地址族: AF_INET AF_INET6
	src:需要转换的点分十进制的IP字符串
	dst:转换后的结果保存在这个里面
	
// 将网络字节序的整数,转换成点分十进制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
	af:地址族: AF_INET AF_INET6
	src: 要转换的ip的整数的地址
	dst: 转换成IP地址字符串保存的地方
	size:第三个参数的大小(数组的大小)
	返回值:返回转换后的数据的地址(字符串),和 dst 是一样的
#include <stdio.h>
#include <arpa/inet.h>

int main() {

    // 创建一个ip字符串,点分十进制的IP地址字符串
    char buf[] = "192.168.1.4"; // 后面默认还有一个字符串结束符
    unsigned int num = 0;

    // 将点分十进制的IP字符串转换成网络字节序的整数
    inet_pton(AF_INET, buf, &num);
    unsigned char * p = (unsigned char *)&num;
    printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3)); // 大端排序


    // 将网络字节序的IP整数转换成点分十进制的IP字符串
    char ip[16] = "";
    const char * str =  inet_ntop(AF_INET, &num, ip, 16);
    printf("str : %s\n", str);
    printf("ip : %s\n", str);
    printf("%d\n", ip == str);

    return 0;
}

在这里插入图片描述

5. TCP 通信流程

TCPUDP -> 传输层的协议

  • UDP: 用户数据报协议,面向无连接,可以单播,多播,广播, 面向 数据报,不可靠
  • TCP: 传输控制协议,面向连接的,可靠的,基于 字节流仅支持单播传输
UDPTCP
是否创建连接无连接面向连接
是否可靠不可靠可靠的
连接的对象个数一对一、一对多、多对一、多对多支持一对一
传输的方式面向数据报面向字节流
首部开销8个字节最少20个字节
适用场景实时应用(视频会议,直播)可靠性高的应用(文件传输)

在这里插入图片描述

TCP 通信的流程 ⭐️⭐️⭐️

⭐️服务器端被动接受连接的角色)⭐️

  1. 创建 一个用于监听的套接字
    • 监听:监听有客户端的连接
    • 套接字 :这个套接字其实就是一个 文件描述符
  2. 将这个 监听 文件描述符本地的IP端口 绑定IP端口 就是 服务器的地址信息
    • 客户端 连接 服务器 的时候使用的就是这个 IP端口
  3. 设置 监听,监听的 fd 开始工作
  4. 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个 和客户端通信的 套接字fd
  5. 通信
    • 接收数据
    • 发送数据
  6. 通信结束断开连接

⭐️客户端⭐️

  1. 创建一个用于通信的套接字fd
  2. 连接服务器,需要指定连接的服务器IP端口
  3. 连接成功了,客户端可以直接和服务器 通信
    • 接收数据
    • 发送数据
  4. 通信结束断开连接

6. 套接字函数

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.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

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

int listen(int sockfd, int backlog);  /proc/sys/net/core/somaxconn
	- 功能:监听这个socket上的连接
	- 参数:
		- sockfd : 通过socket()函数得到的文件描述符
		- backlog : 未连接的队列 和 已经连接的队列 和的最大值(以使用 cat /proc/sys/net/core/somaxconn 查看:4096),一般不用设置那么大,如:8/128

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

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

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

TCP 通信实现

(1)服务器端

    创建 server.c 文件

// TCP 通信的服务器端

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

int main() {

    // 1.创建socket(用于监听的套接字)
    int lfd = socket(AF_INET, SOCK_STREAM, 0);

    if(lfd == -1) {
        perror("socket");
        exit(-1);
    }

    // 2.绑定
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    // inet_pton(AF_INET, "192.168.216.129", &saddr.sin_addr.s_addr);  // 主机字节序 -> 网络字节序
    saddr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0   (任意地址)
    saddr.sin_port = htons(9999);   // 主机字节序要转为网络字节序
    int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    if(ret == -1) {
        perror("bind");
        exit(-1);
    }

    // 3.监听
    ret = listen(lfd, 8);
    if(ret == -1) {
        perror("listen");
        exit(-1);
    }

    // 4.接收客户端连接
    struct sockaddr_in clientaddr;  // 客户端地址信息
    int len = sizeof(clientaddr);
    int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);  // 阻塞函数;连接成功,返回用于通信的文件描述符
    
    if(cfd == -1) {
        perror("accept");
        exit(-1);
    }

    // 输出客户端的信息
    char clientIP[16];
    inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));  //IP:网络字节序 -> 主机字节序
    unsigned short clientPort = ntohs(clientaddr.sin_port);		//端口:网络字节序 -> 主机字节序
    printf("client ip is %s, port is %d\n", clientIP, clientPort);

    // 5.通信
    char recvBuf[1024] = {0};
    while(1) {
        
        // 获取客户端的数据
        int num = read(cfd, recvBuf, sizeof(recvBuf));  // 如果客户端没有发送数据,也会阻塞
        if(num == -1) {
            perror("read");
            exit(-1);
        } else if(num > 0) {
            printf("recv client data : %s\n", recvBuf);
        } else if(num == 0) {
            // 表示客户端断开连接
            printf("clinet closed...");
            break;
        }

        char * data = "hello,i am server";
        // 给客户端发送数据
        write(cfd, data, strlen(data));
    }
   
    // 关闭文件描述符
    close(cfd);
    close(lfd);

    return 0;
}

(2)客户端


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

int main() {

    // 1.创建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        exit(-1);
    }

    // 2.连接服务器端
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.216.129", &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);   // 要和服务器端的一致
    int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    if(ret == -1) {
        perror("connect");
        exit(-1);
    }

    
    // 3. 通信
    char recvBuf[1024] = {0};
    while(1) {

        char * data = "hello,i am client";
        // 给客户端发送数据
        write(fd, data , strlen(data));

        sleep(1);
        
        int len = read(fd, recvBuf, sizeof(recvBuf));
        if(len == -1) {
            perror("read");
            exit(-1);
        } else if(len > 0) {
            printf("recv server data : %s\n", recvBuf);
        } else if(len == 0) {
            // 表示服务器端断开连接
            printf("server closed...");
            break;
        }

    }

    // 关闭连接
    close(fd);

    return 0;
}

在这里插入图片描述

注: 仅供学习参考,如有不足,欢迎指正!

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

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

相关文章

智能驾驶规划控制理论学习05-车辆运动学规划案例分析

目录 案例一——Hybrid A*&#xff08;基于正向运动学&#xff09; 1、基本思想 2、 实现流程 3、启发函数设计 4、分析扩张&#xff08;Analytic Expansions&#xff09; 5、分级规划&#xff08;Hierarchical planning&#xff09; 案例二——State Lattice Planning&…

Vue3快速上手(十六)Vue3路由传参大全

Vue3路由传参 一、传参的多种方式 1.1 拼接方式 这种方式适合传递单个参数的情况&#xff0c;比如点击查看详情&#xff0c;传个id这样的场景 传参&#xff1a; <RouterLink to"/person?id1" active-class"active">person</RouterLink> …

RabbitMQ相关问题

RabbitMQ相关问题 一、RabbitMQ的核心组件和工作原理&#xff1f;二、如何保证消息可靠投递不丢失的&#xff1f;三、RabbitMQ如何保证消息的幂等性&#xff1f;四、什么是死信队列&#xff1f;死信队列是如何导致的&#xff1f;五、RabbitMQ死信队列是如何导致的&#xff1f;六…

PDF 解析问题调研

说点真实的感受 &#xff1a;网上看啥组件都好&#xff0c;实际测&#xff0c;啥组件都不行。效果好的不开源收费&#xff0c;开源的效果不好。测试下来&#xff0c;发现把组件融合起来&#xff0c;还是能不花钱解决问题的&#xff0c;都是麻烦折腾一些。 这里分享了目前网上能…

数据结构 第3章 栈、队列和数组(一轮习题总结)

第3章 栈、队列和数组 3.1 栈3.2 队列3.3 栈与队列的应用3.4 数组和特殊矩阵 3.1 栈&#xff08;1 10 11 20&#xff09; 3.2 队列&#xff08;6 12 14 17&#xff09; 3.3 栈与队列的应用&#xff08;6 11&#xff09; 3.4 数组和特殊矩阵 3.1 栈 T1 栈和队列具有相同的逻辑…

一周学会Django5 Python Web开发-Django5详细视图DetailView

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计28条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

Linux-信号2

文章目录 前言一、信号是如何保存的&#xff1f;int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset (sigset_t *set, int signo);int sigdelset(sigset_t *set, int signo);int sigismember&#xff08;const sigset_t *set, int signo);int sigpen…

leetcode 长度最小的子数组

在本题中&#xff0c;我们可以知道&#xff0c;是要求数组中组成和为target的最小子数组的长度。所以&#xff0c;我们肯定可以想到用两层for循环进行遍历&#xff0c;然后枚举所有的结果进行挑选&#xff0c;但这样时间复杂度过高。 我们可以采用滑动窗口&#xff0c;其实就是…

NoSQL--1.虚拟机网络配置

目录 1.初识NoSQL 1.1 NoSQL之虚拟机网络配置 1.1.1 首先&#xff0c;导入预先配置好的NoSQL版本到VMware Workstation中 1.1.2 开启虚拟机操作&#xff1a; 1.1.2.1 点击开启虚拟机&#xff1a; 1.1.2.2 默认选择回车CentOS Linux&#xff08;3.10.0-1127.e17.x86_64) 7 …

同样是证书,NPDP和PMP有什么区别?

PMP和NPDP的区别是啥&#xff1f; PMP、NPDP证书考哪个更有用&#xff1f;还是两个都考&#xff1f; PMP和NPDP哪个更适合现在及以后发展&#xff1f; PMP和NPDP这两哪个含金量更高&#xff1f; 一&#xff0c;关于PMP和NPDP PMP和NPDP都是美国PMI/PDMA的专业考试&#xf…

C语言中的分支和循环语句:从入门到精通

分支和循环语句 1. 前言2. 预备知识2.1 getchar函数2.2 putchar函数2.3 计算数组的元素个数2.4 清屏2.5 程序的暂停2.6 字符串的比较 3. 结构化3.1 顺序结构3.2 分支结构3.3 循环结构 4. 真假性5. 分支语句&#xff08;选择结构&#xff09;5.1 if语句5.1.1 语法形式5.1.2 else…

Stable Cascade又升级了,现在只需要两个模型

Stable Cascade这个模型&#xff0c;大家如果还有印象的话&#xff0c;是需要下载三个模型的&#xff0c;分别是Stage_a,Stage_b和Stage_c,如果全都下载下来&#xff0c;需要20多个G&#xff0c;但是最近使用ComfyUI做尝试的时候&#xff0c;发现官方的案例中已经没有用到单独的…

数据审计 -本福德定律 Benford‘s law (sample database classicmodels _No.6)

数据审计 -本福德定律 Benford’s law 准备工作&#xff0c;可以去下载 classicmodels 数据库资源如下 [ 点击&#xff1a;classicmodels] 也可以去我的博客资源下载 文章目录 数据审计 -本福德定律 Benfords law 前言一、什么是 本福德定律&#xff1f;二、数学公式三、应用…

单细胞Seurat - 降维与细胞标记(4)

本系列持续更新Seurat单细胞分析教程&#xff0c;欢迎关注&#xff01; 非线形降维 Seurat 提供了几种非线性降维技术&#xff0c;例如 tSNE 和 UMAP&#xff0c;来可视化和探索这些数据集。这些算法的目标是学习数据集中的底层结构&#xff0c;以便将相似的细胞放在低维空间中…

Grpc项目集成到java方式调用实践

背景&#xff1a;由于项目要对接到grcp 的框架&#xff0c;然后需要对接老外的东西&#xff0c;还有签名和证书刚开始没有接触其实有点懵逼。 gRPC 是由 Google 开发的高性能、开源的远程过程调用&#xff08;RPC&#xff09;框架。它建立在 HTTP/2 协议之上&#xff0c;使用 …

从零开始手写RPC框架(3)——ZooKeeper入门

目录 ZooKeeper简介ZooKeeper中的一些概念 ZooKeeper安装与常用命令常用命令 ZooKeeper Java客户端 Curator入门 ZooKeeper简介 是什么&#xff1f; ZooKeeper 是一个开源的分布式协调服务&#xff0c;本身就是一个分布式程序&#xff08;只要半数以上节点存活&#xff0c;Zo…

django-admin登录窗口添加验证码功能-(替换原有的login.html)captcha插件

需求&#xff1a; 1&#xff1a;更改django框架的admin登录窗口标题 2&#xff1a;在admin登录窗口中添加验证码功能 3&#xff1a;验证码允许点击更换 步骤如下&#xff1a; 1:安装插件以及在安装列表中添加插件 2:自定义表单forms.py 3:创建login.html文件(复制django内置的l…

中国电子学会2020年6月份青少年软件编程Sc ratch图形化等级考试试卷四级真题。

第 1 题 【 单选题 】 1.执行下面程序&#xff0c;输入4和7后&#xff0c;角色说出的内容是&#xff1f; A&#xff1a;4&#xff0c;7 B&#xff1a;7&#xff0c;7 C&#xff1a;7&#xff0c;4 D&#xff1a;4&#xff0c;4 2.执行下面程序&#xff0c;输出是&#xff…

备战蓝桥杯Day22 - 计数排序

计数排序问题描述 对列表进行排序&#xff0c;已知列表中的数范围都在0-100之间。设计时间复杂度为O(n)的算法。 比如列表中有一串数字&#xff0c;2 5 3 1 6 3 2 1 &#xff0c;需要将他们按照从小到大的次序排列&#xff0c;得到1 1 2 2 3 3 5 6 的结果。那么此时计数排序是…