【玩转RT-Thread】RT-Thread网络框架:BSD网络接口SAL套接字抽象层

news2025/6/18 5:43:51

文章目录

  • RT-Thread网络框架:BSD网络接口&SAL套接字抽象层
    • 基础知识
        • 1.TCP与UDP的区别
        • 2.TCP编程 服务端配置过程
        • 3.TCP编程 客户端配置过程
        • 4.UDP编程 客户端配置过程
    • SAL套接字抽象层
        • 1.SAL组件主要功能特点:
        • 2.SAL网络框架
        • 3.工作原理
        • 4.多协议接入与接口函数统一抽象功能
        • 5.SAL TLS加密传输功能
    • BSD Socket API
        • 1.创建套接字(socket)
        • 2.绑定套接字(bind)
        • 3.监听套接字(listen)
        • 4.接收连接(accept)
        • 5.建立连接(connect)
        • 6.TCP数据发送(send)
        • 7.TCP数据接收(recv)
        • 8.UDP数据发送(sendto)
        • 9.UDP数据接收(recfrom)
    • SAL网络协议栈接入方式
    • 附录

RT-Thread网络框架:BSD网络接口&SAL套接字抽象层


基础知识

1.TCP与UDP的区别

TCP(Transmission Control Protocol 传输控制协议):是一种面向连接、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。

UDP(User Datagram Protocol 用户数据报协议):是OSI(Open System Interconnection 开放式系统互联):参考模型中的一种无连接的传输层协议,提供面向事务的简单不可靠传送服务。

OSI七层模型和TCP/IP四层模型详解请看此处

区别:

  • TCP提供的是面向连接、可靠的数据流传输;UDP提供的是非面向连接、不可靠的数据流传输。
  • TCP提供可靠的服务,通过TCP连接传送的数据:无差错、不丢失、不重复、按序到达;UDP尽最大努力交付,但不保证可靠性。
  • TCP面向字节流;UDP面向报文。
  • TCP仅支持点对点连接;UDP支持一对一、一对多、多对多的交互通信。
  • TCP最低开销20字节(首部开销);UDP首部开销8字节,开销小。
  • TCP的逻辑同性能信道是全双工的可靠信道;UDP的逻辑通信信道是不可靠信道。

2.TCP编程 服务端配置过程

  • socket():创建一个socket
  • setsockopt():设置socket属性
  • bind():绑定IP地址、端口等信息到socket类上
  • listen():开启监听
  • accept():接收来自客户端的连接
  • 收发数据:send()、recv()、read()、write()
  • 关闭网络连接
  • 关闭监听

3.TCP编程 客户端配置过程

  • socket():创建一个socket
  • setsockopt():设置socket属性,可选
  • bind():绑定IP地址、端口等信息到socket上
  • recvfrom():循环接收数据
  • 关闭网络连接

4.UDP编程 客户端配置过程

  • socket():创建一个socket
  • setsockopt():设置socket属性,可选
  • bind():绑定IP地址、端口等信息到socket上
  • 设置对方的IP地址和端口等属性
  • sendto():发送数据
  • 关闭网络连接

SAL套接字抽象层

SAL(套接字抽象层)是RT-Thread官方为避免系统对单一网络协议栈的依赖,同时也为适配更多网络协议栈类型而提供的一套网络组件,该组件主要完成对不同网络协议栈或网络实现接口的抽象并对上层一共一组标准BSD Socket API,这样开发者只需关心和使用网络应用层提供的网络接口,而无需关心底层具体网络协议栈类型和实现,极大提高了系统的兼容性。

1.SAL组件主要功能特点:

  • 抽象、统一多种网络协议栈接口
  • 提供Socket层面的TLS加密传输特性
  • 支持标准 BSD Socket API
  • 统一的FD管理,便于使用read/write poll/select来操作网络功能

2.SAL网络框架

image-20230411131524312

  • 应用层:提供一套标准BSD Socket API1。如socket、connect等函数,用于系统中大部分网络开发应用。
  • SAL套接字抽象层:RT-Thread通过该层能够适配下层不同的网络协议栈,并提供给上层统一的网络编程接口,方便不同协议栈的接入。套接字抽象层为上层应用层提供接口有:accept、connect、send、recv等。
  • netdev网卡层:主要作用是解决多网卡情况设备网络连接和网络管理相关问题,通过netdev网卡层,用户可以统一管理各个网卡信息和网络连接状态,并且可以使用统一的网卡调试命令接口。
  • 协议栈层:该层包括几种常用的TCP/IP协议栈,如嵌入式开发中常用的轻型TCP/IP协议栈lwip以及RT-Thread自主研发的AT Socket网络功能实现等。

3.工作原理

SAL组件工作原理的介绍主要分为如下两部分:

  • 多协议栈接入与接口函数统一抽象功能
  • SAL TLS加密传输功能

4.多协议接入与接口函数统一抽象功能

由于不同协议栈或网络功能的实现,其网络接口的名称各有不同,已连接函数为例,lwip协议栈中接口名称为lwip_connect,而AT Socket网络实现接口为at_connect。通过SAL组件可以完成对不同协议栈或网络实现接口的抽象和统一,组件再socket创建时通过判断传入的协议簇(domain)类型来判断使用的协议栈或网络功能。

目前RT-Thread SAL组件支持的协议栈或网络实现类型有:LWIP协议栈(AT_INET)、AT Socket协议栈(AF_AT)、WIZnet硬件 TCP/IP协议栈(AT_WIZ)2

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

为了动态适配不同协议栈或网络实现的接入,SAL组件中对于每个协议栈或者网络实现提供两种协议类型匹配方式:主协议簇类型和次协议簇类型,在socket创建之初收i西安判断传入协议簇类型是否存在已经支持的主协议类型,如果是则使用对应协议栈或网络实现,如果不是则判断次协议簇类型是否支持。

具体而言,主协议簇类型是指一个协议簇的最基本类型,例如 IPv4 或 IPv6。次协议簇类型则是在主协议簇类型的基础上进行扩展或增强,例如 TCP 或 UDP 协议。主协议簇类型可以被多个次协议簇类型所支持,但一个次协议簇类型只能属于一个主协议簇类型。

目前系统支持协议簇类型如下:

LWIP协议栈:family = AF_INET、sec_family = AF_INET
AT Socket协议栈:family = AF_AT、sec_family = AF_INET
WIZnet硬件 TCP/IP协议栈:family = AF_WIZ

SAL组件的主要作用是统一BSD Socket API接口,我们以官方示例对SAL组件函数进行调用方式的实现:

  • connect: SAL组件对外提供的抽象的BSD Socket API,用于统一fd管理;
  • sal_connect: SAL组件中connect实现函数,用于调用底层协议栈注册的operation函数;
  • lwip_connect: 底层协议栈提供的connect连接函数,在网卡初始化完成时注册到SAL组件中,最终调用的操作函数
/* SAL 组件为应用层提供的标准 BSD Socket API */
int connect(int s, const struct sockaddr *name, socklen_t namelen)
{
    /* 获取 SAL 套接字描述符 */
    int socket = dfs_net_getsocket(s);

    /* 通过 SAL 套接字描述符执行 sal_connect 函数 */
    return sal_connect(socket, name, namelen);
}

/* SAL 组件抽象函数接口实现 */
int sal_connect(int socket, const struct sockaddr *name, socklen_t namelen)
{
    struct sal_socket *sock;
    struct sal_proto_family *pf;
    int ret;

    /* 检查 SAL socket 结构体是否正常 */
    SAL_SOCKET_OBJ_GET(sock, socket);

    /* 检查当前 socket 网络连接状态是否正常  */
    SAL_NETDEV_IS_COMMONICABLE(sock->netdev);
    /* 检查当前 socket 对应的底层 operation 函数是否正常  */
    SAL_NETDEV_SOCKETOPS_VALID(sock->netdev, pf, connect);

    /* 执行底层注册的 connect operation 函数 */
    ret = pf->skt_ops->connect((int) sock->user_data, name, namelen);
#ifdef SAL_USING_TLS
    if (ret >= 0 && SAL_SOCKOPS_PROTO_TLS_VALID(sock, connect))
    {
        if (proto_tls->ops->connect(sock->user_data_tls) < 0)
        {
            return -1;
        }
        return ret;
    }
#endif
    return ret;
}

/* lwIP 协议栈函数底层 connect 函数实现 */
int lwip_connect(int socket, const struct sockaddr *name, socklen_t namelen)
{
    ...
}

5.SAL TLS加密传输功能

在TCP、UDP等协议数据传输时,由于数据包是明文的,所以很可能被拦截,甚至被解析出数据,为了保证网络传输的安全性,需要用户在应用层和传输层之间添加SSL/TLS协议。

TLS(Transport Layer Security,传输层安全协议)是建立在传输层TCP协议之上的协议,其前身是SSL(Secure Socket Layer,安全套接字层),主要作用是将应用层的报文进行非对称加密后再由TCP协议进行传输,实现了数据的加密安全交互。3

对于通过的加密方式,需要使用其指定的加密接口和流程进行加密,而SAL TLS功能的主要作用是提供Socket层面的TLS加密传输特性,抽象多种TLS处理方式,提供统一的接口用于完成TLS数据交互。

使用流程:

  • 配置开启任意网络协议栈支持(如LWIP协议栈)
  • 配置开启MbedTLS软件包(目前仅支持MbedTLS类型加密方式)
  • 配置开启SAL_TLS功能支持

配置完成后,需要在socket创建时传入的potocol类型是使用PROTOCOL_TLS或者PROTOCOL_DTLS,即可使用标准BSD Socket API接口,完成TLS连接的建立和数据的收发。

示例如下,参考RT-Threda文档中心:

#include <stdio.h>
#include <string.h>

#include <rtthread.h>
#include <sys/socket.h>
#include <netdb.h>

/* RT-Thread 官网,支持 TLS 功能 */
#define SAL_TLS_HOST    "www.rt-thread.org"
#define SAL_TLS_PORT    443
#define SAL_TLS_BUFSZ   1024

static const char *send_data = "GET /download/rt-thread.txt HTTP/1.1\r\n"
    "Host: www.rt-thread.org\r\n"
    "User-Agent: rtthread/4.0.1 rtt\r\n\r\n";

void sal_tls_test(void)
{
    int ret, i;
    char *recv_data;
    struct hostent *host;
    int sock = -1, bytes_received;
    struct sockaddr_in server_addr;

    /* 通过函数入口参数url获得host地址(如果是域名,会做域名解析) */
    host = gethostbyname(SAL_TLS_HOST);

    recv_data = rt_calloc(1, SAL_TLS_BUFSZ);
    if (recv_data == RT_NULL)
    {
        rt_kprintf("No memory\n");
        return;
    }

    /* 创建一个socket,类型是SOCKET_STREAM,TCP 协议, TLS 类型 */
    if ((sock = socket(AF_INET, SOCK_STREAM, PROTOCOL_TLS)) < 0)
    {
        rt_kprintf("Socket error\n");
        goto __exit;
    }

    /* 初始化预连接的服务端地址 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SAL_TLS_PORT);
    server_addr.sin_addr = *((struct in_addr *)host->h_addr);
    rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

    if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
    {
        rt_kprintf("Connect fail!\n");
        goto __exit;
    }

    /* 发送数据到 socket 连接 */
    ret = send(sock, send_data, strlen(send_data), 0);
    if (ret <= 0)
    {
        rt_kprintf("send error,close the socket.\n");
        goto __exit;
    }

    /* 接收并打印响应的数据,使用加密数据传输 */
    bytes_received = recv(sock, recv_data, SAL_TLS_BUFSZ  - 1, 0);
    if (bytes_received <= 0)
    {
        rt_kprintf("received error,close the socket.\n");
        goto __exit;
    }

    rt_kprintf("recv data:\n");
    for (i = 0; i < bytes_received; i++)
    {
        rt_kprintf("%c", recv_data[i]);
    }

__exit:
    if (recv_data)
        rt_free(recv_data);

    if (sock >= 0)
        closesocket(sock);
}

#ifdef FINSH_USING_MSH
#include <finsh.h>
MSH_CMD_EXPORT(sal_tls_test, SAL TLS function test);
#endif /* FINSH_USING_MSH */

BSD Socket API

1.创建套接字(socket)

为通信创建一个端点并返回一个文件描述符

int socket(int domain, int type, int protocol);
  • domain:确定协议簇
  • type:数据类型
  • protocol:协议
# domain / 协议族类型
AF_INET		#  IPv4 协议族
AF_INET6	#  IPv6 协议族
# type / 协议类型
/* Socket protocol types (1:TCP/2:UDP/3:RAW) */
#define SOCK_STREAM     1
#define SOCK_DGRAM      2
#define SOCK_RAW        3

2.绑定套接字(bind)

当使用socket()创造一个套接字时,只是给定了协议簇,并没有分配地址。在套接字能够接收来自其他主机的连接时,必须bind()给它绑定一个地址。

int bind(int s, const struct sockaddr *name, socklen_t namelen);
  • s:代表socket的文件描述符
  • name:指向sockaddr结构体的指针,代表要绑定的地址
  • namelen:是sockaddr结构体的大小

附:SAL组件依赖netdev组件,当使用bind()函数时,可通过netdev网卡名称获取网卡对象中IP地址信息,用于将创建的Socket套接字绑定到指定的网卡对象。

来自RT-Thread文档中心,完成通过传入的网卡名称绑定该网卡IP地址并和服务器进行连接的过程:

#include <rtthread.h>
#include <arpa/inet.h>
#include <netdev.h>

#define SERVER_HOST   "192.168.1.123"
#define SERVER_PORT   1234

static int bing_test(int argc, char **argv)
{
    struct sockaddr_in client_addr;
    struct sockaddr_in server_addr;
    struct netdev *netdev = RT_NULL;
    int sockfd = -1;

    if (argc != 2)
    {
        rt_kprintf("bind_test [netdev_name]  --bind network interface device by name.\n");
        return -RT_ERROR;
    }

    /* 通过名称获取 netdev 网卡对象 */
    netdev = netdev_get_by_name(argv[1]);
    if (netdev == RT_NULL)
    {
        rt_kprintf("get network interface device(%s) failed.\n", argv[1]);
        return -RT_ERROR;
    }

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        rt_kprintf("Socket create failed.\n");
        return -RT_ERROR;
    }

    /* 初始化需要绑定的客户端地址 */
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(8080);
    /* 获取网卡对象中 IP 地址信息 */
    client_addr.sin_addr.s_addr = netdev->ip_addr.addr;
    rt_memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero));

    if (bind(sockfd, (struct sockaddr *)&client_addr, sizeof(struct sockaddr)) < 0)
    {
        rt_kprintf("socket bind failed.\n");
        closesocket(sockfd);
        return -RT_ERROR;
    }
    rt_kprintf("socket bind network interface device(%s) success!\n", netdev->name);

    /* 初始化预连接的服务端地址 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVER_HOST);
    rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

    /* 连接到服务端 */
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
    {
        rt_kprintf("socket connect failed!\n");
        closesocket(sockfd);
        return -RT_ERROR;
    }
    else
    {
        rt_kprintf("socket connect success!\n");
    }

    /* 关闭连接 */
    closesocket(sockfd);
    return RT_EOK;
}

#ifdef FINSH_USING_MSH
#include <finsh.h>
MSH_CMD_EXPORT(bing_test, bind network interface device test);
#endif /* FINSH_USING_MSH */

3.监听套接字(listen)

当有一个套接字和一个地址联系之后,listen()监听到来的连接。只适用于面向连接的模式。

int listen(int s, int backlog);
  • sockfd:代表socket的文件描述符
  • backlog:一个整数,表示一次能够等待的最大连接数目。

4.接收连接(accept)

当应用程序监听来自其他他主机的面向数据流的连接时,通过事件通知它,必须用accept()函数初始化连接。该函数为每个连接创建新的套接字并从监听队列中移除这个连接。

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
  • s:监听的套接字描述符
  • addr:指向sockaddr结构体的指针,服务器地址信息
  • addrlen:sockaddr结构体的大小

5.建立连接(connect)

该函数用于建立与指定 socket 的连接。

int connect(int s, const struct sockaddr *name, socklen_t namelen);
  • s:套接字描述符
  • name:服务器地址信息
  • namelen:服务器地址结构体长度

6.TCP数据发送(send)

该函数常用于 TCP 连接发送数据。

int send(int s, const void *dataptr, size_t size, int flags);
  • s:套接字描述符
  • dataptr:发送的数据指针
  • size:发送的数据长度
  • flags:标志,一般为 0

7.TCP数据接收(recv)

该函数用于TCP连接接收数据。

int recv(int s, void *mem, size_t len, int flags);
  • s:套接字描述符
  • mem:接收的数据指针
  • len:接收的数据长度
  • flags:标志,一般为0

8.UDP数据发送(sendto)

该函数用于UDP连接发送数据。

int sendto(int s, const void *dataptr, size_t size, int flags, const struct sockaddr *to, socklen_t tolen);
  • S:套接字描述符
  • dataptr:发送的数据指针
  • size:发送的数据长度
  • flags:标志,一般为0
  • to:目标结构体指针
  • tolen:目标地址结构体长度

9.UDP数据接收(recfrom)

该函数用于UDP连接发送数据。

int recvfrom(int s, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
  • S:套接字描述符
  • mem:接收的数据指针
  • len:接收的数据长度
  • flags:标志,一般为0
  • from:接收地址结构体指针
  • fromlen:接收地址结构体长度

SAL网络协议栈接入方式

网络协议栈或网络功能实现的接入,主要是对协议簇结构体的初始化和注册处理,并且添加到SAL组件中协议簇列表中,协议簇结构体定义如下:

/* network interface socket opreations */
struct sal_socket_ops
{
    int (*socket)     (int domain, int type, int protocol);
    int (*closesocket)(int s);
    int (*bind)       (int s, const struct sockaddr *name, socklen_t namelen);
    int (*listen)     (int s, int backlog);
    int (*connect)    (int s, const struct sockaddr *name, socklen_t namelen);
    int (*accept)     (int s, struct sockaddr *addr, socklen_t *addrlen);
    int (*sendto)     (int s, const void *data, size_t size, int flags, const struct sockaddr *to, socklen_t tolen);
    int (*recvfrom)   (int s, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
    int (*getsockopt) (int s, int level, int optname, void *optval, socklen_t *optlen);
    int (*setsockopt) (int s, int level, int optname, const void *optval, socklen_t optlen);
    int (*shutdown)   (int s, int how);
    int (*getpeername)(int s, struct sockaddr *name, socklen_t *namelen);
    int (*getsockname)(int s, struct sockaddr *name, socklen_t *namelen);
    int (*ioctlsocket)(int s, long cmd, void *arg);
#ifdef SAL_USING_POSIX
    int (*poll)       (struct dfs_fd *file, struct rt_pollreq *req);
#endif
};

/* sal network database name resolving */
struct sal_netdb_ops
{
    struct hostent* (*gethostbyname)  (const char *name);
    int             (*gethostbyname_r)(const char *name, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop);
    int             (*getaddrinfo)    (const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res);
    void            (*freeaddrinfo)   (struct addrinfo *ai);
};

/* 协议簇结构体定义 */
struct sal_proto_family
{
    int family;                                  /* primary protocol families type */
    int sec_family;                              /* secondary protocol families type */
    const struct sal_socket_ops *skt_ops;        /* socket opreations */
    const struct sal_netdb_ops *netdb_ops;       /* network database opreations */
};
  • family:每个协议栈支持的主协议簇类型,例如lwip的为AF_INET、AT Socket为AF_AT,WIZnet为AF_WIZ。
  • sec_family:每个协议栈支持的次协议簇类型,用于支持单个协议栈或网络实现时,匹配软件包中其他类型的协议簇类型。
  • skt_ops:定义socket相关执行函数,如connect、send、recv等,每种协议簇都有一组通过的实现方式。
  • netdb_ops:定义非socket相关执行函数,如gethostbyname、getaddrinfo、freeaddrinfo等,每种协议簇都有一组不同的实现方式。

附录


  1. 伯克利套接字(Berkeley sockets),也称BSD Socket,伯克利套接字的应用编程接口(API)是采用C语言的进程间通信的库,经常用在计算机网络间的通信。 BSD Socket的应用编程接口已经是网络套接字的抽象标准。大多数其他程序语言使用一种相似的编程接口。最初是由加州伯克利大学为Unix系统开发出来。 ↩︎

  2. WIZnet的硬件TCP/IP协议栈采用了TOE(TCP/IP Core Offload Engine)技术,将TCP/IP协议栈等网络处理功能转移到专用硬件中,从而减少了CPU的负担,提高了整个系统的性能和稳定性。同时,WIZnet的硬件TCP/IP协议栈还支持多种网络协议,并提供了Socket API封装等高层次接口,方便用户进行开发和集成。 ↩︎

  3. 在 TLS 协议中,使用了非对称加密和对称加密两种加密方式。其中,非对称加密主要用于密钥协商和身份认证,而对称加密则用于数据传输的加密和解密。在TLS握手过程中,客户端和服务器会相互发送自己的公钥,并通过对方的公钥加密生成一个随机数的方式协商出用来进行对称加密的对称密钥。这个对称密钥就是用非对称加密算法加密后的数据包。接收方拿到这个数据包后,使用自己的私钥进行解密,获取生成的对称密钥。然后,双方就开始使用协商好的对称密钥进行数据传输。接收方会利用对称密钥对收到的数据进行解密,得到明文数据。这样,在整个数据传输过程中,只有公钥被公开,密钥等关键信息都是使用非对称加密算法进行加密传输的,保证了安全性。总之,在 TLS 协议中,接收方通过使用自己的私钥解密协商出的对称密钥,从而完成对加密数据的解析。这个过程是整个 TLS 协议中非常重要的一个环节,确保了加密数据在传输过程中的安全性和可靠性。 ↩︎

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

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

相关文章

“成年人”的数据库,既要又要也要!

欢迎访问 OceanBase 官网获取更多信息&#xff1a;https://www.oceanbase.com/ 3 月 25 日&#xff0c;第一届 OceanBase 开发者大会在北京举行&#xff0c;《明说三人行》访谈栏目创始人兼主持人卢东明、沃趣科技创始人兼 CEO 陈栋、DBAplus 社群联合创始人杨建荣、PostgreSQL…

7.1 基本运算电路(2)

七、集成运放性能指标对运算误差的影响 在上述各电路运算关系的分析中&#xff0c;均认为集成运放为理想运放。而实际上&#xff0c;当利用运放构成运算电路时&#xff0c;由于开环差模增益 AodA_{od}Aod​、差模输入电阻 ridr_{id}rid​ 和共模抑制比 KCMRK_{CMR}KCMR​ 为有…

【计算机网络-应用层】域名系统 DNS、文件传输协议 FTP、电子邮件

文章目录1 域名系统 DNS1.1 域名结构1.2 域名服务器1.2.1 根域名服务器1.2.2 顶级域名服务器1.2.3 权限域名服务器1.2.4 本地域名服务器1.3 域名解析过程1.3.1 递归查询1.3.2 递归与迭代相结合查询1.3.3 本地域名服务器的高速缓存2 文件传输协议 FTP2.1 主动模式&#xff08;建…

java编译和运行带有包名的类

写在前面 对于习惯了使用ide的我们似乎早已经忘记了如何通过命令行来编译和运行java类了&#xff0c;至少我是这样的&#xff0c;本文就一起来回顾下吧&#xff01; 1&#xff1a;运行不带包的类 这种相信大多数朋友都记得&#xff0c;直接javac yourCode.java,然后java you…

Camtasia studio2023录屏和后期剪辑的软件

Camtasia 2023是专门用于屏幕录制的软件&#xff0c;功能十分丰富&#xff0c;不仅可以录制电脑屏幕、局部区域和摄像头等&#xff0c;而且还能即时编辑视频&#xff0c;给视频添加转场、旁白、字幕等&#xff0c;能够轻松制作更优秀的视频。 兼顾录屏和后期剪辑的软件—Camtas…

Oracle_EBS_核心功能(MFG)(第二部分)

BOM: Routing工艺路线应用&#xff1a;Bills of Material 职责&#xff1a;Bills of Material 基础业务学习总体说明 Routing&#xff08;工艺路线&#xff09;最终解决的问题是生产过程中加工顺序、资源和用量的标准化。准确度要求在98%以上&#xff0c;要不断与现场比对&…

【离散数学】图论

1、有n个点没有边 零图 2、有1个点没有边 平凡图 3、含有平行边的图 多重图 4、简单图 不含有平行边和自回环的图 5、任意两个结点之间都有边 完全图 6、环贡献 两度 7、所有顶点的度数之和等于边数的两倍 8、在有向图中所有顶点的出度之和 或者 入度之和 等于边数 9、度数为…

特斯拉和OpenAI的加持,马斯克简直人生赢家

赢家已定 商人行事&#xff0c;最重要的因素之一是利益驱动。这里&#xff0c;最服“马斯克”。 以马斯克为首的特斯拉公司周日宣布&#xff0c;将在上海新建一家超级工厂&#xff0c;专门生产该公司的储能产品Megapack。签约的特斯拉储能超级工厂项目也是该公司在美国本土以…

【论文笔记】CRN: Camera Radar Net for Accurate, Robust, Efficient 3D Perception

原文链接&#xff1a;https://arxiv.org/abs/2304.00670 1. 引言 本文提出两阶段融合方法CRN&#xff0c;能使用相机和雷达生成语义丰富且位置精确的BEV特征。具体来说&#xff0c;首先将图像透视特征转换到BEV下&#xff0c;该步骤依赖雷达&#xff0c;称为雷达辅助的视图变换…

大数据技术(入门篇) --- centos7安装CDH6.2集群

随着信息化时代的进步&#xff0c;业务系统的数据量出现了爆发式的增长&#xff0c;带来的不良结果就是数据库的数据量剧增&#xff0c;而部分业务系统需要实时数据&#xff0c;有些业务系统需要离线计算后的数据&#xff0c;所以就产生了大数据技术&#xff0c;因此最近在学习…

面试官:说一说mysql的varchar字段最大长度?

在mysql建表sql里&#xff0c;我们经常会有定义字符串类型的需求。 CREATE TABLE user (name varchar(100) NOT NULL DEFAULT COMMENT 名字 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 ;比方说user表里的名字&#xff0c;就是个字符串。mysql里有两个类型比较适合这个场景。 ch…

剧本拆分如何用ai人工智能辅助完成

随着现代技术的发展&#xff0c;人工智能在电影制作领域中的应用已经越来越普遍。其中&#xff0c;辅助剧本拆分是人工智能技术的一种重要应用。人工智能可以帮助电影制作人员更快速、更准确地进行剧本拆分&#xff0c;提高制作效率和创作质量。 剧本拆分是电影制作中非常重要的…

二叉树的链式结构

思维导图 二叉树的创建 先定义一个二叉树链式结构的结构体 typedef int BTDatatype; typedef struct BinaryTreeNode {struct BinaryTreeNode* left;struct BinaryTreeNode* right;BTDatatype data; }BTNode; 手搓一个二叉树&#xff08;前序遍历的方式创建二叉树放到OJ题…

nm命令 以及 C++11 编译出现找不到stringstream 以及 undefined reference to `std::runtime_error

最近在学习ZLMediaKit 源码 里面用到了很多C11 的知识 本地有一个 ubuntu18.04 的服务器 源码下下来发现 直接编译报很多错误 比如 找不到 std::runtime_error 找不到 stringstream 等等等 后来偶然的机会发现 是libstdc.so.6 太老了 找一个新的 替换掉这个就可以 …

新 Nano(五)自己写个库,读 DHT11 / DHT22

DHT11 这款温湿度传感器 几乎是所有 MCU 入门第一个传感器&#xff0c; 现在看来有些不合时宜&#xff0c; 毕竟过于廉价&#xff0c;数据不太靠谱&#xff0c;远不如 AHT10 好用。早年买了两个&#xff0c;按例程读出数据后就吃灰了。某日看到有人说自己按datasheet去读&#…

c#快速入门~在java基础上,知道C#和JAVA 的不同即可

☺ 观看下文前提&#xff1a;如果你的主语言是java&#xff0c;现在想再学一门新语言C#&#xff0c;下文是在java基础上&#xff0c;对比和java的不同&#xff0c;快速上手C#&#xff0c;当然不是说学C#的前提是需要java&#xff0c;而是下文是从主语言是java的情况下&#xff…

CloudIDE 如何提升研发效能

原文作者&#xff1a;行云创新技术总监 邓冰寒 引言 CloudIDE &#xff0c;一种基于云计算技术开发的云原生集成开发环境&#xff0c;可以帮助企业提高研发效能&#xff0c;实现数字化转型的目标。本文将探讨 CloudIDE 如何在数字化时代体现业务价值、提升研发效能。 CloudID…

【一起撸个DL框架】1 绪论

文章目录第一章 绪论 &#x1f349;1.1 在人工智能的大潮里1.2 为什么重复造轮子1.3 深度学习框架简介第一章 绪论 &#x1f349; 1.1 在人工智能的大潮里 人工智能——一个如今十分火热的话题&#xff0c;人们在生活中越来越多地使用它、谈论它。在2022年之前&#xff0c;人…

ChatGPT会取代律师这份职业吗?

如今&#xff0c;一种新型的人工智能威胁再次来袭&#xff0c;律师们可能会感到似曾相识的感觉。有人警告称&#xff0c;类似于ChatGPT的软件&#xff0c;因为具有类似于人类的语言流畅性&#xff0c;可能会取代大部分法律工作。 人工智能​的进步曾让人们预测&#xff0c;法律…

Linux 网络扫描工具:nmap,涨知识的时间到了!

在Linux系统中&#xff0c;nmap是一个非常流行的网络扫描工具。它可以用于探测主机和网络上的开放端口、操作系统类型、服务和应用程序等信息。nmap还可以与Ping命令结合使用&#xff0c;以便快速识别网络上的活动主机。本文将介绍如何在Linux上使用nmap和Ping命令进行扫描。 …