Linux下网络通信及socket编程

news2024/10/7 13:19:40

文章目录

  • 网络通信
  • socket应用编程
    • 函数介绍
    • IP地址转换函数
    • 程序源代码
    • 程序执行结果

网络通信

网络通信本质上是一种进程间通信,是位于网络中不同主机上的进程之间的通信。网络通信大致分为以下三层。
在这里插入图片描述
在硬件层,两台主机都提供了网卡设备,满足了进行网络通信最基本的要求,网卡设备是实现网络数据收发的硬件基础。
在内核层提供了网卡驱动程序,其可以驱动底层网卡硬件设备,同时向应用层提供socket接口。
在应用层基于内核提供的socket接口进行应用编程,以实现特定的网络应用程序。socket接口是内核向应用层提供的一套网络编程接口,学习网络编程其实就是学习socket编程,即如何基于socket接口编写应用程序。
除了socket接口,在应用层通常还会使用一些更为高级的编程接口,譬如http、网络控件等,这些接口实际上是对socket接口的一种更高级别的封装。
OSI(Open System Interconnection)七层参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间网络互联的标准体系,一般称为OSI参考模型或七层模型。
在这里插入图片描述
物理层是OSI参考模型的最低层,物理层的主要功能是利用传输介质为数据链路层提供物理连接,实现比特流的透明传输,物理层的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。使数据链路层不必考虑网络的具体传输介质是什么。透明传送比特流表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。网络数据信号的传输是通过物理层实现的,通过物理介质传输比特流,物理层规定了物理设备标准、电平、传输速率等,集线器、中继器、调制解调器、网线、双绞线、同轴电缆等都是物理层的传输介质。
数据链路层是OSI参考模型中的第二层,负责建立和管理节点间逻辑连接、进行硬件地址寻址、差错检测等功能。数据链路层又分为两个子层,即逻辑链路控制子层和媒体访问控制子层。MAC子层的主要任务是解决共享型网络中多用户对信道竞争的问题,完成网络介质的访问控制,LLC子层的主要任务是建立和维护网络连接,执行差错校验、流量控制和链路控制。数据链路层的具体工作是接收来自物理层的比特流,并封装成帧,传送到上一层,同样也可将来自上层的数据帧拆为比特流形式的数据转发到物理层,此外还负责处理接收端发回的确认帧的信息,以便提供可靠的数据传输。
网络层进行逻辑地址寻址,实现不同网络之间的路径选择,该层通过IP寻址来建立两个节点之间的连接,为源端发送的数据包选择合适的路由和交换节点,正确无误地按照地址传送给目的端。该层的主要协议有IP、ICMP等。
传输层定义传输数据的协议端口号,以及端到端的流控和差错校验,该层建立了主机端到端的连接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括差错校验处理和流控等问题。该层的主要协议有TCP、UDP等。
会话层对应主机进程,指本地主机与远程主机正在进行的会话,会话层负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成,将不同实体之间表示层的连接称为会话,因此会话层的任务就是组织和协调两个会话进程之间的通信,并对数据交换进行管理。
表示层提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。
应用层是OSI参考模型中的最高层,为上层用户提供应用接口或直接提供各种网络服务。
TCP/IP五层模型是OSI七层模型的简化,TCP/IP五层模型中下面四层不变,并将OSI七层模型的最上面三层合并为应用层。
TCP/IP四层模型在TCP/IP五层模型的基础上,将底层的数据链路层和物理层合并为网络接口层。
在这里插入图片描述
网络通信中,数据从上层到下层交付时,要进行封装,同样地,当目标主机接收到数据时,数据由下层传递给上层时需要进行拆封。数据的封装过程如下图所示。
在这里插入图片描述
可以看到,用户发送数据时会从上层到下层一次进行封装,也就是添加各层的头部信息,在链路层封装完成后将数据交付给网卡,然后网卡硬件设备将数据转换成物理链路上的电平信号,数据就被发送到了网络中了。
当数据被目标主机接收到之后,会进行相反的拆封过程,将每一层的首部进行拆解最终得到用户数据。
关于IP地址的描述可以参看文章:计算机网络——网络层数据交换方式、IP数据报、IPv4地址、重要协议、IPv6。
关于TCP和UDP的内容可以参看文章:计算机网络——传输层 UDP 和 TCP。
根据IP地址可以找到对应的主机,但是主机上运行着很多的进程,比如QQ、微信、浏览器等,这些进程都需要进行网络连接,它们都可通过网络发送/接收数据,主机接收到网络数据之后,如何确定该数据发往哪个进程呢?其实就是通过端口号来确定的。端口号本质上就是一个数字编号,用来在一台主机中唯一标识一个能上网的进程,端口号的取值范围为0~65535,一台主机通常只有一个IP地址,但是可能有多个端口号。一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务都是能够进行网络通信的进程,IP地址只能区分网络中不同的主机,并不能区分主机中的这些进程,有了端口号,通过IP地址+端口号来区分主机不同的进程。
很多常见的服务器都有特定的端口号,HTTP服务端口号是80,FTP服务的端口号是21,SMTP(简单邮件传输协议)服务的端口号是25,TFTP(简单文件传输协议)服务的端口号是69,SSH(安全外壳协议)服务的端口号是22,Telnet(终端远程登录协议)服务的端口号是23等。


socket应用编程

函数介绍

Linux下的网络编程一般称为socket编程,socket是内核向应用层提供的一套网络编程接口,用户基于socket接口可开发自己的网络相关应用程序。
套接字(socket)是Linux下的一种进程间通信机制,通过这种机制可以在不同主机上的应用程序之间进行通信,也可以在同一台主机的不同应用程序之间通信。
socket()函数用于创建一个网络通信端点,成功就返回一个网络文件描述符。socket()函数和open()函数比较相似,open()函数会返回一个文件描述符,socket()函数会返回一个socket描述符,之后的操作中都会使用到这个socket描述符。
使用 man 2 socket 命令即可查看socket()函数原型如下。

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

参数domain用于指定一个通信域,这将选择将用于通信的协议族,常用的协议族有AF_INET(IPv4 Internet protocols)、AF_INET6(IPv6 Internet protocols)、AF_UNIX/AF_LOCAL(Local communication)等,对于TCP/IP协议来说,一般选择AF_INET。
参数type指定套接字的类型,主要有:SOCK_STREAM(用于TCP协议)、SOCK_DGRAM(用于UDP协议)等。
参数protocol通常设置为0,表示为给定的通信域和套接字类型选择默认协议。
同open()函数一样,套接字使用完成后使用close()函数来关闭socket描述符。
bind()函数原型如下。

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

bind()函数用于将一个IP地址或端口号与一个套接字进行绑定,即将套接字与地址进行关联。一般会将一个服务器的套接字绑定到某个固定的IP地址和端口号上(大家都知道的),这样客户端就会知道自己要访问的服务器IP地址和端口号,从而进行通信。
addr参数是一个struct sockaddr类型的指针,其结构体如下。

struct sockaddr 
{ 
	sa_family_t sa_family; 
	char sa_data[14]; 
}

sa_data结构体成员是一个字符数组,包含了IP地址和端口号等信息,用户对这个数组无法进行赋值。所以使用struct sockaddr_in结构体替代struct sockaddr,然后在函数中进行类型转换即可。struct sockaddr_in结构体的内容如下。

struct sockaddr_in 
{ 
	sa_family_t sin_family;    // 协议族 
	in_port_t sin_port;       // 端口号
	struct in_addr sin_addr;  // IP地址
	unsigned char sin_zero[8]; 
};

实际使用的时候按照下面这样使用即可。

struct sockaddr_in socket_addr;
server_addr.sin_family = AF_INET;    //协议族
server_addr.sin_port = htons(SERVER_PORT);  //端口号
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // IP地址

代码中的htons和htonl并不是函数,只是一个宏定义,主要的作用在于为了避免大小端的问题,使用这些宏需要在代码中包含头文件<netinet/in.h>。
listen()函数只能在服务器进程中使用,让服务器进程进入监听状态,等待客户端的连接请求,listen()函数在一般在bind()函数之后调用,在accept()函数之前调用,它的函数原型如下。

int listen(int sockfd, int backlog);

参数backlog用来描述sockfd的等待连接队列能够达到的最大值,当有多个客户端请求建立连接的时候,等待连接队列上限在这里设置。当一个客户端的连接请求到达并且该队列为满时,客户端可能会收到一个表示连接失败的错误,本次请求会被丢弃不作处理。
无法在一个已经连接的套接字上执行listen(),即已经成功执行connect()的套接字或由accept()调用返回的套接字。
服务器调用listen()函数之后,就会进入到监听状态,等待客户端的连接请求,使用accept()函数获取客户端的连接请求并建立连接,函数原型如下。

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

参数addr是一个传出参数,用来返回已连接的客户端的IP地址与端口号等信息,如果对客户端的信息不感兴趣,两个都可以设置为NULL。
accept()函数通常只用于服务器应用程序中,如果调用accept()函数时,并没有客户端请求连接,此时accept()会进入阻塞状态,直到有客户端连接请求到达为止。当有客户端连接请求到达时,accept()函数与远程客户端之间建立连接,accept()函数会返回一个新的套接字,这个套接字与socket()函数返回的套接字并不同,socket()函数返回的是服务器的套接字,而accept()函数返回的套接字连接到调用connect()的客户端,服务器通过该套接字与客户端进行数据交互。这个新的套接字就是服务器端accept()与执行connect()的客户端之间建立连接产生的新套接字,这个套接字代表了服务器与客户端的一个连接。
connect()函数用于客户端应用程序中,客户端调用connect()函数将套接字sockfd与远程服务器进行连接,其原型如下。

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

参数addr指定了待连接的服务器的IP地址以及端口号等信息,参数addrlen指定了addr指向的struct sockaddr对象的字节大小。
客户端通过connect()函数请求与服务器建立连接,对于TCP连接来说,调用该函数将发生TCP连接的握手过程,并最终建立一个TCP连接,而对于UDP协议来说,调用这个函数只是在sockfd中记录服务器IP地址与端口号,而不发送任何数据。
recv()函数用来接收数据,不论是客户端还是服务器都可以通过revc()函数读取网络数据,它与read()函数的功能是相似的,其函数原型如下。

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd是套接字描述符;参数buf指向了一个数据接收缓冲区;参数len指定了读取数据的字节大小;参数flags可以指定一些标志用于控制如何接收数据,一般设置为0。
send()函数用来发送数据,它与write()函数的功能是相似的,其函数原型如下。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数flags一般设置为0。
在程序中,revc()函数可以由read()函数替代,send()函数也可以由write()函数替代。

IP地址转换函数

我们经常看到的IP地址是点分十进制表示的,这其实是一种字符串的形式,但是计算机能理解的是二进制形式的IP地址,所以需要在点分十进制字符串和二进制地址之间进行转换。点分十进制字符串和二进制地址之间的转换函数主要有:inet_aton、inet_addr、inet_ntoa、inet_ntop、inet_pton。
下面这些函数已经不怎么使用了。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);

现在经常使用的是下面这两个函数进行转换。

#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

inet_pton()函数将点分十进制表示的字符串形式转换成二进制Ipv4或Ipv6地址。inet_ntop()函数将二进制IP地址转换为点分十进制形式的字符串。
参数af必须是AF_INET或AF_INET6,AF_INET表示待转换的Ipv4地址,AF_INET6表示待转换的是Ipv6地址;src是点分十进制字符串;参数af被指定为AF_INET,参数dst所指的是一个struct in_addr结构体对象,参数af被指定为AF_INET6,则参数dst所指的是一个struct in6_addr结构体对象。

程序源代码

服务器端代码如下。(以下代码来自正点原子,本人只做了一点改动!)

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

#define SERVER_PORT 8888     //设置服务器端口号
int main(int argc, char *argv[])
{
    struct sockaddr_in server_addr;  //定义服务器结构体
    struct sockaddr_in client_addr;
    char ip_str[20];      //存放IP地址
    int sockfd, connfd;   //一个socket描述符,一个建立连接后的描述符
    char recvbuf[512];   //接收客户端发来的数据
    int addrlen = sizeof(client_addr);
    int ret;

    sockfd = socket(AF_INET,SOCK_STREAM,0);  //打开套接字,得到套接字描述符
    if(sockfd < 0) 
    {
        perror("socket error");
        return sockfd;
    }

    /*给结构体成员设置值*/ 
    server_addr.sin_family = AF_INET;    //协议族
    server_addr.sin_port = htons(SERVER_PORT);  //端口号
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // IP地址
    ret = bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));  //将套接字与指定端口号进行绑定
    if(ret < 0)
    {
        perror("bind error");
        close(sockfd);
        return ret;
    }

    ret = listen(sockfd, 50);  //服务器进入监听状态,等待连接队列最大值为50
    if(ret < 0)
    {
        perror("listen error");
        close(sockfd);
        return ret;
    }
    printf("等待客户端接入...\n"); 
    
    connfd = accept(sockfd,(struct sockaddr *)&client_addr,&addrlen);  //阻塞等待客户端连接
    if(connfd < 0)
    {
        perror("accept error");
        close(sockfd);
        return connfd;
    }

    printf("一个客户端已接入!\n"); 
    inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip_str,sizeof(ip_str));   //得到点分十进制IP地址
    printf("客户端主机的IP地址: %s\n",ip_str); 
    printf("客户端进程的端口号: %d\n",client_addr.sin_port);

    /* 接收客户端发送过来的数据 */
    while(1)
    {
        memset(recvbuf,0x0,sizeof(recvbuf));  // 接收缓冲区清零
        ret = recv(connfd,recvbuf,sizeof(recvbuf),0);  //接收客户端发来的数据
        //ret = read(connfd,recvbuf,sizeof(recvbuf));
        if(ret < 0)
        {
            perror("recv error");
            close(connfd);
            break;
        }
        printf("message from client: %s\n", recvbuf);

        ret = send(connfd,"data received.",15,0);  //给客户端发送反馈信息
        //ret = write(connfd,"data received.",15);
        if(ret < 0)
        {
            perror("send error");
            break;
        }

        if (strncmp("exit",recvbuf,4) == 0)  //客户端发来"exit"就关闭套接字退出程序
        { 
            printf("server exit!\n"); 
            close(connfd); 
            break; 
        }
    }

    close(sockfd);  //关闭套接字描述符
    return 0;
}

客户端代码如下。(以下代码来自正点原子,本人只做了一点改动!)

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

#define SERVER_PORT 8888         //服务器端口号
#define SERVER_IP "192.168.0.232"   //服务器的IP地址

int main(int argc, char *argv[])
{
    struct sockaddr_in server_addr; 
    int sockfd;
    char buf[512];     //发送数据缓冲区
    char rcvbuf[512];   //接收数据缓冲区
    int ret;

    sockfd = socket(AF_INET,SOCK_STREAM,0);  //打开套接字,得到套接字描述符
    if(sockfd < 0) 
    {
        perror("socket error");
        return sockfd;
    }

     /*给结构体成员设置值*/ 
    server_addr.sin_family = AF_INET;   //协议族
    server_addr.sin_port = htons(SERVER_PORT);  //端口号
    inet_pton(AF_INET,SERVER_IP,&server_addr.sin_addr);   //得到二进制IP地址
    
    ret = connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));  //连接服务器
    if(ret < 0)
    {
        perror("connect error");
        close(sockfd);
        return ret;
    }
    printf("服务器连接成功...\n\n");

    /* 向服务器发送数据 */
    while(1)
    {
        memset(buf,0x0,sizeof(buf));     //发送数据缓冲区清零
        printf("Please enter a string: "); 
        fgets(buf,sizeof(buf),stdin);    //接收用户输入
        ret = send(sockfd,buf,strlen(buf),0);  //给服务器发送数据
        //ret = write(sockfd,buf,strlen(buf));
        if(ret < 0)
        {
            perror("send error");
            break;
        }
        
        memset(rcvbuf, 0x0, sizeof(rcvbuf));  //接收数据缓冲区清零
        ret = recv(sockfd,rcvbuf,sizeof(rcvbuf),0);   //接收服务器反馈信息
        //ret = read(sockfd,rcvbuf,sizeof(rcvbuf));
        printf("message from server: %s\n", rcvbuf);

        if (strncmp("exit",buf,4) == 0)  //发送"exit"就退出
        { 
            printf("client exit!\n"); 
            break; 
        }
    }

    close(sockfd);  //关闭套接字描述符
    return 0;
}

程序执行结果

上面的代码不管是开发板做服务器还是电脑做服务器都是可以的,确认好之后在客户端代码中设定要做服务器一方的主机IP,然后将一个代码用GCC编译器编译,编译后的可执行程序运行在电脑端,另一个代码用交叉编译器编译,编译后的可执行程序运行在开发板上。
运行的时候,先运行服务器端程序,再运行客户端程序,没有问题的话连接就建立了,之后就可以传输数据了。
①开发板做服务器,PC做客户端
开发板做服务器,PC做客户端时,先在开发板上执行服务器程序,然后在PC上执行客户端程序。
服务器端执行结果如下图所示。
在这里插入图片描述
客户端执行结果如下图所示。
在这里插入图片描述
②PC做服务器,开发板做客户端
PC做服务器,开发板做客户端时,先在PC上执行服务器程序,然后在开发板上执行客户端程序。
服务器端执行结果如下图所示。
在这里插入图片描述
客户端执行结果如下图所示。
在这里插入图片描述
通过上面的程序执行结果可以看到,通过socket实现了服务器和客户端的通信。


参考资料:
I.MX6U嵌入式Linux C应用编程指南V1.4——正点原子

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

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

相关文章

ADC学习总结

ADC的架构分类&#xff1a; 1、Delta-Sigma 采样率一般是在1M以内&#xff0c;位数一般可以做的很高&#xff0c;比如24位&#xff0c;Delta-Sigma ADC采用了过采样技术&#xff0c;不需要在模拟输入端加抗混叠滤波&#xff0c;由后端数字滤波器进行处理&#xff0c;通过信噪…

DDD领域驱动架构设计学习网站和开源框架

文章目录 介绍1、国外Axon2、阿里Cola 介绍 近年来&#xff0c;关于DDD的讨论越来越多&#xff0c;关于网上的文章很多都是理论上的介绍&#xff0c;由于自己最近也在学习相关知识&#xff0c;所以分享几个关于DDD落地的开源框架。 1、国外Axon Axon是国外一款比较系统的DDD…

Re59:读论文 Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文名称&#xff1a;Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks 模型开源地址&#xff1a;https://huggingface.co/facebook/rag-token-nq ArXiv下载地址&#xff1a;https://arxi…

高校刮起元宇宙风!3DCAT实时云渲染助力川轻化元校园建设

元宇宙&#xff0c;是一个虚拟的网络世界&#xff0c;它与现实世界相互连接&#xff0c;为人们提供了一个身临其境的数字体验。元宇宙的概念并不新鲜&#xff0c;早在上个世纪就有科幻作家和电影导演对它进行了想象和创造。但是&#xff0c;随着科技的发展&#xff0c;特别是5G…

iic应用篇

一.iic的优点 1. IIC总线物理链路简单&#xff0c;硬件实现方便&#xff0c;扩展性非常好&#xff08;1个主机控制器可以根据需求增加从机数量&#xff0c;同时删减从机数量也不会影响总线通信&#xff09;&#xff1b;IIC总线只需要SDA和SCL两条信号线&#xff0c;相比于PCI/…

python和pygame实现烟花特效

python和pygame实现烟花特效 新年来临之际&#xff0c;来一个欢庆新年烟花祝贺&#xff0c;需要安装使用第三方库pygame&#xff0c;关于Python中pygame游戏模块的安装使用可见 https://blog.csdn.net/cnds123/article/details/119514520 效果图及源码 先看效果图&#xff1a…

c#异常强大的统计运行时间功能,一行代码,监控 C# 方法执行耗时

MethodTimer.Fody MethodTimer.Fody 是一个功能强大的库&#xff0c;可以用于测量 .NET 应用程序中的方法的执行时间。 它使用 Fody 插件框架可以无缝集成到您的项目中&#xff0c;所以向代码中添加性能测量功能变得非常容易。 如何使用 首先&#xff0c;需要安装 Nuget 包&am…

程序员必备的十种排序算法

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

MS913/914 25-100MHz 10/12 位用于平面显示器链路Ⅲ的具有直流平衡编码和双向控制通道的串化器和解串器

MS913/MS914 芯片组是 25MHz~100MHz 10 位/12 位 FPD Link III SER/DES(串化器/解串器)&#xff0c;它提供高速 FPD-Link III 接口和高速正向通路以及用于差分对上数据发送的双向 控制通路。广泛应用于车载摄像&#xff0c;医疗设备&#xff0c;管道探测等领 域。 主要特点…

改进的A*算法的路径规划(2)

子节点优化选择策略 (1)子节点选择方式 为了找到从起始点到终点的路径&#xff0c;需定义一种可以选择后续节点的方式。在 A*算法中两种常见的方法为4-邻接(见图5-7(a) 和8-邻接(见图5-7(b)), 但考虑到 在复杂越野环境上&#xff0c;我们希望智能车辆允许更多的自由运动来更…

20231210原始编译NanoPC-T4(RK3399)开发板的Android10的SDK

20231210原始编译NanoPC-T4(RK3399)开发板的Android10的SDK 2023/12/10 17:27 rootrootrootroot-X99-Turbo:~$ rootrootrootroot-X99-Turbo:~$ mkdir nanopc-t4 rootrootrootroot-X99-Turbo:~$ rootrootrootroot-X99-Turbo:~$ rootrootrootroot-X99-Turbo:~$ cd nanopc-t4/ …

企业U盘防泄密的必备秘籍!迅软DSE答疑解析一切你需要知道的!

关于U盘防泄密&#xff1a; U盘是企事业单位办公时经常需要用到的存储介质&#xff0c;而一旦U盘不慎丢失或是落入他人手中&#xff0c;都会面临U盘内数据泄密的情况发生。 因此&#xff0c;企事业单位可通过天锐绿盾安全U盘系统对公司重要数据进行U盘防泄密保护&#xff0c;确…

二分查找|双指针:LeetCode:2398.预算内的最多机器人数目

作者推荐 【动态规划】【广度优先】LeetCode2258:逃离火灾 本文涉及的基础知识点 二分查找算法合集 滑动窗口 单调队列&#xff1a;计算最大值时&#xff0c;如果前面的数小&#xff0c;则必定被淘汰&#xff0c;前面的数早出队。 题目 你有 n 个机器人&#xff0c;给你两…

【CANoe】CANoe手动发送XCP报文读取观测量

文章目录 1、硬件连接&#xff1a;配置CANoe的CAN端口&#xff0c;连接到ECU标定对应的CAN口2、配置CAN IG模块报文&#xff1a;连接XCP&#xff0c;读取观测量&#xff0c;断开XCP3、报文解析4、参考资料 1、硬件连接&#xff1a;配置CANoe的CAN端口&#xff0c;连接到ECU标定…

LeetCode:1631. 最小体力消耗路径(SPFA Java)

目录 1631. 最小体力消耗路径 题目描述&#xff1a; 实现代码与解析&#xff1a; BFSDP 原理思路&#xff1a; 1631. 最小体力消耗路径 题目描述&#xff1a; 你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights &#xff0c;其中 heights[row][col] 表…

掌握Selenium中元素缓存技巧,提高测试效率!

一、前言 / INTRODUCTION 本篇文章我们再来看下如何在Selenium中使用缓存 页面对象模型是UI自动化测试中的一种很好的设计模式&#xff0c;我们使用FindBy和FindAll注释来标记Page Object中的WebElement。 本次要讲的CacheLookup是一个非常重要但被忽视的注释&#xff0c;它可…

【UE5.2】通过Water插件使物体漂浮在水面上

效果 步骤 1. 新建一个工程&#xff0c;创建一个Basic关卡&#xff0c;添加初学者内容包到内容浏览器 2. 在插件中启用“Water”插件&#xff0c;然后重启工程 3. 重启后提示“碰撞描述文件设置不包括水体碰撞描述文件的条目&#xff0c;水碰撞必须使用该描述文件才能正常工作…

Node后端框架Express与Koa接口统一响应封装

背景 以前在写 SpringBoot 全栈开发的系列文章中全栈开发之后端脚手架&#xff1a;SpringBoot集成MybatisPlus代码生成&#xff0c;分页&#xff0c;雪花算法&#xff0c;统一响应&#xff0c;异常拦截&#xff0c;Swagger3接口文档&#xff0c;有提到对后端接口的响应数据进行…

flink找不到隐式项

增加 import org.apache.flink.streaming.api.scala._ 即可

逆向思考 C. Fence Painting

Problem - 1481C - Codeforces 思路&#xff1a;逆序考虑&#xff0c;因为每一块木板都是被最后一次粉刷所决定的。 从后往前开始&#xff0c;对于 c i c_i ci​来说&#xff0c; 如果这个颜色还有没有涂的木板&#xff0c;那么涂到其中一个木板即可如果这个颜色下没有未涂的…