Linux socket编程(10):UDP详解、聊天室实现及进阶知识

首先来回顾以下TCP的知识,TCP是一种面向连接的、可靠的传输协议,具有以下特点:

  • TCP通过三次握手建立连接,确保通信的可靠性和完整性
  • 使用流控制和拥塞控制机制,有效地调整数据传输的速率,防止网络拥塞
  • TCP提供错误检测和重传机制,以确保数据在传输过程中不会丢失或损坏
  • TCP支持全双工通信,允许双方同时发送和接收数据,从而提高通信效率

然而,与TCP不同,UDP是一种无连接的、不可靠的传输协议。相比于TCP,UDP更加轻量级,没有连接的建立和断开过程,也没有复杂的流控制和拥塞控制机制。UDP直接将数据包发送到目标地址,不保证数据的顺序和可靠性,因此在某些实时性要求较高、可以容忍少量数据丢失的应用场景中表现得更为适用。

文章目录

  • 1 相关函数
    • 1.1 socket
    • 1.2 bind
    • 1.3 sendto
    • 1.4 recvfrom
  • 2 UDP聊天室
    • 2.1 客户端
    • 2.2 服务端
    • 2.3 实验结果
    • 2.4 完整代码
  • 3 UDP进阶注意事项
    • 3.1 如何判断对端断开
    • 3.2 UDP报文丢失、重复、乱序、流量控制
    • 3.3 UDP数据被丢弃
    • 3.4 connect判断对端是否存在

1 相关函数

首先来看一下UDP的流程图:
在这里插入图片描述

对于服务端来说,UDP无需像TCP一样listen,因为UDP是无连接的。同样地,对于客户端来说,UDP也不需要调用connect建立连接。对于socket/bind等函数可以参考之前TCP的文章socket函数介绍及C/S模型代码实现中的介绍。这里就来介绍一下recvfromsendto

1.1 socket

socket()函数用于创建套接字,即通信的端点。

int socket(int domain, int type, int protocol);
  • 参数
    • domain:协议族,常用的是 AF_INET 表示IPv4协议族。
    • type:套接字类型,常用的是 SOCK_DGRAM 表示UDP套接字。
    • protocol:协议,通常设置为 0 表示使用默认协议。
  • 示例
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);

1.2 bind

bind()函数用于将套接字绑定到特定的地址和端口。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 参数
    • sockfd:套接字文件描述符。
    • addr:指向包含地址信息的结构体的指针,通常是struct sockaddr_in
    • addrlen:结构体的长度,使用sizeof(struct sockaddr_in)
  • 示例
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = INADDR_ANY;
server_address.sin_port = htons(8080);

bind(udp_socket, (struct sockaddr*)&server_address, sizeof(server_address));

1.3 sendto

sendto()函数用于向指定的目标地址发送数据。

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
  • 参数
    • sockfd:套接字文件描述符。
    • buf:要发送的数据的指针。
    • len:要发送的数据的字节数。
    • flags:发送标志,通常设置为 0
    • dest_addr:指向目标地址信息的结构体的指针,通常是struct sockaddr_in
    • addrlen:结构体的长度,使用sizeof(struct sockaddr_in)
  • 示例
char message[] = "Hello,world";
struct sockaddr_in client_address;
client_address.sin_family = AF_INET;
client_address.sin_addr.s_addr = inet_addr("127.0.0.1");
client_address.sin_port = htons(8081);

sendto(udp_socket, message, sizeof(message), 0, (struct sockaddr*)&client_address, sizeof(client_address));

1.4 recvfrom

recvfrom()函数用于接收数据以及发送方的地址。

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  • 参数
    • sockfd:套接字文件描述符。
    • buf:用于存放接收数据的缓冲区的指针。
    • len:缓冲区的大小。
    • flags:接收标志,通常设置为 0
    • src_addr:指向发送方地址信息的结构体的指针,通常是struct sockaddr_in
    • addrlen:指向存储发送方地址结构体长度的变量的指针。
  • 示例:
char buffer[1024];
struct sockaddr_in client_address;
socklen_t client_address_len = sizeof(client_address);

recvfrom(udp_socket, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_address, &client_address_len);

2 UDP聊天室

现在来实现一个UDP聊天室,在代码编写的过程中,我们能够学到一些细节。

  • 目的:服务端能够将收到的客户端的消息转发给其它的所有客户端,从而实现多个客户端之间的通信。

2.1 客户端

1、创建UDP套接字

int udp_socket;
udp_socket = socket(AF_INET, SOCK_DGRAM, 0));

2、服务端地址配置

这里使用本地环回地址127.0.0.1作为服务端IP:

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
memset(&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr(SERVER_IP);
server_address.sin_port = htons(SERVER_PORT);

3、使用select监听stdin和服务端消息

现在需要使用select一边从stdin获取用户输入的数据然后发送给服务端,一边从服务端获取消息。

fd_set read_fds;
while (1)
{
    FD_ZERO(&read_fds);
    FD_SET(STDIN_FILENO, &read_fds);
    FD_SET(udp_socket, &read_fds);

    int activity = select(udp_socket + 1, &read_fds, NULL, NULL, NULL);

    if (FD_ISSET(STDIN_FILENO, &read_fds))
    {
        fgets(buffer, sizeof(buffer), stdin);
        buffer[strlen(buffer) - 1] = '\0';  //换行符替换为结束符
        // 发送消息给服务器
        sendto(udp_socket, buffer, strlen(buffer), 0, (struct sockaddr*)&server_address, sizeof(server_address));
    }

    if (FD_ISSET(udp_socket, &read_fds))
    {
        ssize_t bytes_received = recvfrom(udp_socket, buffer, sizeof(buffer), 0, NULL, NULL);
        buffer[bytes_received] = '\0';
        printf("%s\n", buffer);
    }
}

客户端在调用sendto的时候指定了服务端的地址,服务端收到消息后通过recvfromsrc_addr参数知道客户端的地址并保存起来,接着就可以通过这个地址发送消息给客户端。

2.2 服务端

1、创建并绑定套接字

udp_socket = socket(AF_INET, SOCK_DGRAM, 0);

memset(&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = INADDR_ANY;//监听所有地址
server_address.sin_port = htons(PORT);

bind(udp_socket, (struct sockaddr*)&server_address, sizeof(server_address));

2、接收并转发客户端的消息

由于服务端需要接收多个客户端的消息,所以需要创建一个数组来保存已经连接上的客户端的IP和端口号信息。

struct Client {
    struct sockaddr_in address;
    socklen_t len;
    int socket;
};
#define MAX_CLIENTS 10
struct Client clients[MAX_CLIENTS];

然后是调用recvfrom来监听新的客户端消息并创建连接:

ssize_t bytes_received = recvfrom(udp_socket, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_address, &client_address_len);

if (bytes_received == -1)
{
    perror("Error receiving data");
    continue;
}

// 查找客户端是否已存在
int client_index = -1;
for (int i = 0; i < client_count; ++i)
{
    if (memcmp(&client_address, &clients[i].address, sizeof(struct sockaddr_in)) == 0)
    {
        client_index = i;
        break;
    }
}

// 如果是新客户端,则添加到客户端列表
if (client_index == -1)
{
    if (client_count < MAX_CLIENTS)
    {
        clients[client_count].address = client_address;
        clients[client_count].len = client_address_len;
        clients[client_count].socket = udp_socket;
        client_index = client_count;
        printf("new client:(%s:%d)\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
        client_count++;
    }
    else
    {
        printf("聊天室已满,拒绝新连接\n");
    }
}

最后将其中一个客户端发来的消息转发给其它所有已经建立连接的客户端:

buffer[bytes_received] = '\0';
for (int i = 0; i < client_count; ++i)
{
    if (i != client_index)
    {
        sendto(udp_socket, buffer, bytes_received, 0, (struct sockaddr*)&clients[i].address, clients[i].len);
    }
}

2.3 实验结果

在这里插入图片描述

可以看到,客户端通过发送一个消息与服务端建立连接,建立连接后的所有客户端发送的消息,在服务端收到后会广播给所有的客户端,这样就相当于多个客户端之间的一个聊天室。

2.4 完整代码

1.客户端

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

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define MAX_BUFFER_SIZE 1024

int main() {
    int udp_socket;
    struct sockaddr_in server_address;
    char buffer[MAX_BUFFER_SIZE];

    // 创建UDP套接字
    if ((udp_socket = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("Error creating socket");
        return EXIT_FAILURE;
    }

    // 设置服务器地址结构
    memset(&server_address, 0, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr(SERVER_IP);
    server_address.sin_port = htons(SERVER_PORT);

    printf("UDP聊天室客户端启动,连接到服务器 %s:%d\n", SERVER_IP, SERVER_PORT);

    fd_set read_fds;
    struct timeval timeout;

    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(STDIN_FILENO, &read_fds);
        FD_SET(udp_socket, &read_fds);
        // 使用 select 监听 stdin 和 udp_socket
        int activity = select(udp_socket + 1, &read_fds, NULL, NULL, NULL);

        if (activity < 0) {
            perror("Error in select");
            break;
        }

        if (FD_ISSET(STDIN_FILENO, &read_fds)) {
            // 从 stdin 读取用户输入
            fgets(buffer, sizeof(buffer), stdin);
            buffer[strlen(buffer) - 1] = '\0';  // 移除末尾的换行符
            // 发送消息给服务器
            sendto(udp_socket, buffer, strlen(buffer), 0, (struct sockaddr*)&server_address, sizeof(server_address));
        }

        if (FD_ISSET(udp_socket, &read_fds)) {
            // 接收服务器消息
            ssize_t bytes_received = recvfrom(udp_socket, buffer, sizeof(buffer), 0, NULL, NULL);

            if (bytes_received == -1) {
                perror("Error receiving data");
                break;
            } else if (bytes_received == 0) {
                // 服务器断开连接
                printf("server closed\n");
                break;
            }

            buffer[bytes_received] = '\0';
            printf("%s\n", buffer);
        }
    }

    close(udp_socket);
    return EXIT_SUCCESS;
}

2.服务端

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

#define PORT 8080
#define MAX_CLIENTS 10
#define MAX_BUFFER_SIZE 1024

struct Client {
    struct sockaddr_in address;
    socklen_t len;
    int socket;
};

int main() {
    int udp_socket;
    struct sockaddr_in server_address, client_address;
    socklen_t client_address_len = sizeof(client_address);
    char buffer[MAX_BUFFER_SIZE];

    struct Client clients[MAX_CLIENTS];
    int client_count = 0;

    // 创建UDP套接字
    if ((udp_socket = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("Error creating socket");
        return EXIT_FAILURE;
    }

    // 设置服务器地址结构
    memset(&server_address, 0, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = INADDR_ANY;
    server_address.sin_port = htons(PORT);

    // 将套接字绑定到服务器地址和端口
    if (bind(udp_socket, (struct sockaddr*)&server_address, sizeof(server_address)) == -1) {
        perror("Error binding socket");
        close(udp_socket);
        return EXIT_FAILURE;
    }

    printf("UDP聊天室服务端启动,监听端口 %d\n", PORT);

    while (1) {
        // 接收客户端消息
        ssize_t bytes_received = recvfrom(udp_socket, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_address, &client_address_len);

        if (bytes_received == -1) {
            perror("Error receiving data");
            continue;
        }

        // 查找客户端是否已存在
        int client_index = -1;
        for (int i = 0; i < client_count; ++i) {
            if (memcmp(&client_address, &clients[i].address, sizeof(struct sockaddr_in)) == 0) {
                client_index = i;
                break;
            }
        }

        // 如果是新客户端,则添加到客户端列表
        if (client_index == -1) {
            if (client_count < MAX_CLIENTS) {
                clients[client_count].address = client_address;
                clients[client_count].len = client_address_len;
                clients[client_count].socket = udp_socket;
				client_index = client_count;
                printf("new client:(%s:%d)\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
                client_count++;
            } else {
                printf("聊天室已满,拒绝新连接\n");
            }
        }
        // 转发消息给所有其他客户端
        buffer[bytes_received] = '\0';
        for (int i = 0; i < client_count; ++i) {
            if (i != client_index) {
                sendto(udp_socket, buffer, bytes_received, 0, (struct sockaddr*)&clients[i].address, clients[i].len);
            }
        }
        printf("来自 (%s:%d) 的消息:%s\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port), buffer);
    }

    close(udp_socket);
    return EXIT_SUCCESS;
}

3 UDP进阶注意事项

3.1 如何判断对端断开

在UDP中,由于其面向无连接的特性,没有明确的连接状态,因此在传统的意义上无法像TCP一样直接判断连接是否断开。UDP是无连接、无状态的协议,它不维护连接状态,也不会报告连接断开的事件。

我们可以通过收发数据的过程中发生的错误来判断连接状态。如果你在发送数据时遇到错误(如sendto返回 -1),这可能意味着对方无法接收数据,但这并不一定意味着连接已经断开,只是当前无法发送数据而已。

一个常见的方法是在应用层定义一种心跳机制,定期向对端发送一些特殊的数据包,如果长时间没有收到对端的响应,可以认为连接可能已经断开。

  • 在TCP中readrecv返回0的时候表示连接断开,但在UDP中recvfrom返回0时并不代表连接断开,可能只是对端发了一个空的数据

3.2 UDP报文丢失、重复、乱序、流量控制

在UDP协议下,确实可能会发生报文丢失、重复和乱序的情况。UDP是一种无连接的协议,它不提供可靠性,因此没有内建的机制来保证数据的完整性、顺序性和不重复性。

  1. 报文丢失:

    • 原因: UDP不保证报文的可靠传输,因此报文可能在传输过程中丢失。
    • 解决方案: 应用层可以通过在协议中实现重传机制或者使用应用层的确认机制来处理丢失的报文。
  2. 报文重复:

    • 原因: 在网络中,可能由于网络拥塞、重传机制等原因导致报文重复。
    • 解决方案: 应用层可以通过在报文中添加唯一标识符,并在接收端进行去重处理来避免重复的问题。
  3. 报文乱序:

    • 原因: 报文在传输过程中可能会因为经过不同的网络路径而导致乱序。

    • 解决方案: 在报文中添加序列号,并在接收端进行排序操作,将乱序的报文按序组装成完整的数据。

  4. 无流量控制:

    流量控制通常用于防止发送方发送速率过快,导致网络拥塞或接收方无法及时处理数据的情况。TCP通过滑动窗口机制,发送方可以根据接收方的处理能力来动态调整发送速率。

    在UDP中,由于缺少流量控制机制,发送方会以最大速率发送数据,而无法感知网络的拥塞状况。这可能导致一些问题,例如在网络状况不佳时,UDP发送的数据可能会导致丢包,而且无法自动适应网络状况。

应用层可以实现自定义的协议,通过添加额外的信息,如确认机制、重传机制、序列号等来确保数据的可靠性和顺序性。当然,UDP并不适合所有场景的协议,如果对数据的可靠性要求较高,建议TCP。

3.3 UDP数据被丢弃

在UDP中,如果接收端的缓冲区大小无法容纳整个UDP数据包,多出来的数据将被丢弃。修改一下前面的客户端代码的recvfrom中的参数,每次只接收8个字节:

在这里插入图片描述

来看看效果:

在这里插入图片描述

3.4 connect判断对端是否存在

在我们调用sendto发送数据的时候,即使对端不存在,sendto也会返回成功,这是因为这个函数仅仅是完成了将数据拷贝到套接字的过程。**那如果对端不存在的话,我们有没有什么方法可以判断呢?**我们可以使用connect函数。

在UDP通信中,connect函数通常用于绑定本地UDP套接字到指定的远程地址和端口。这与TCP中的connect 有所不同,因为UDP是无连接的协议,connect并不会像在TCP中一样建立连接。在UDP中,connect主要有两个作用:

  1. 指定默认的目标地址和端口: 通过使用 connect函数,你可以在UDP套接字上指定一个默认的目标地址和端口。这样,在使用 send 函数时,就不需要每次都指定目标地址和端口了。这对于一直与同一个目标通信的UDP套接字而言是方便的。
  2. 用于错误检测和筛选数据包: 虽然connect并不会建立连接,但它可以用于检测目标是否可达。当你使用 connect后,如果目标地址不可达,后续的send操作可能会返回错误。这在某种程度上可以用来判断对端是否存在。但需要注意,UDP的特性仍然存在,即使使用了connectsend仍然是无阻塞的,而不会等待连接的建立。

下面来更改一下代码:

在这里插入图片描述

增加connect,然后sendto可以改为send进行发送,此时如果服务端不存在的话,recvfrom会返回-1,errno的值为ECONNREFUSED,表示连接被拒绝。这样我们就知道对端不存在了。

在这里插入图片描述

这个的原理实际上是当数据无法到达的时候,TCP协议栈会产生一个ICMP错误发给客户端,客户端需要在recvfrom中处理。

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

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

相关文章

redis的keys命令和scan命令性能对比

项目场景 Redis的keys *命令在生产环境是慎用的&#xff0c;特别是一些并发量很大的项目&#xff0c;原因是Redis是单线程的&#xff0c;keys *会引发Redis锁&#xff0c;占用reids CPU&#xff0c;如果key数量很大而且并发是比较大的情况&#xff0c;效率是很慢的&#xff0c…

vivado实现分析与收敛技巧9-分析使用率统计数据

实现问题的常见原因之一是未考量显式和隐式物理约束。例如 &#xff0c; 管脚分配 (pinout) 在逻辑布局上变为显式物理约束。 slice&#xff08; 分片 &#xff09; 逻辑在大部分器件中都是一致的。但如下专用资源表示的是隐式物理约束 &#xff0c; 因为这些资源仅在某些位置…

C语言碎片知识

sizeof 1.sizeof是C语言中的一个操作符&#xff0c;同时也是关键字&#xff01;&#xff01;&#xff01;&#xff01; 2.sizeof的操作数可以是类型&#xff0c;变量或表达式 如图&#xff0c;第一个为什么是6&#xff1f;&#xff0c;因为先计算了3的大小&#xff0c;占4个字…

【模电】放大电路的组成原则

放大电路的组成原则 组成原则常用的两种共射放大电路 组成原则 通过对基本共射放大电路的简单分析可以总结出&#xff0c;在组成放大电路时必须遵循以下几个原则&#xff1a;    1. 必须根据所用放大管的类型提供直流电源&#xff0c;以便设置合适的静态工作点&#xff0c;并…

【unity3D】unity中如何查找和获取游戏物体

&#x1f497; 未来的游戏开发程序媛&#xff0c;现在的努力学习菜鸡 &#x1f4a6;本专栏是我关于游戏开发的学习笔记 &#x1f236;本篇是unity中游戏物体的查找与获取 这里写自定义目录标题 获取当前物体的基本属性查找其它物体- 通过名称查找其它物体- 通过标签查找- 通过类…

互联网Java工程师面试题·Spring Boot篇·第二弹

目录 8、什么是 YAML&#xff1f; 9、如何实现 Spring Boot 应用程序的安全性&#xff1f; 10、如何集成 Spring Boot 和 ActiveMQ&#xff1f; 11、如何使用 Spring Boot 实现分页和排序&#xff1f; 12、什么是 Swagger&#xff1f;你用 Spring Boot 实现了它吗&#xff1f; …

MySQL之时间戳(DateTime和TimeStamp)

MySQL之时间戳&#xff08;DateTime和TimeStamp&#xff09; 文章目录&#xff1a; MySQL之时间戳&#xff08;DateTime和TimeStamp&#xff09;一、DateTime类型二、TimeStamp类型三、DateTime和TimeStamp的区别 当插入数据时&#xff0c;需要自动记录一个时间时候&#xff0c…

【springboot】整合redis

1.前提条件:docker安装好了redis确定redis可以访问 可选软件: 2.测试代码 (1)redis依赖 org.springframework.boot spring-boot-starter-data-redis (2)配置redis &#xff08;3&#xff09; 注入 Resource StringRedisTemplate stringRedisTemplate; 对键进行操作 –o…

SCTransform normalization seurat

完成了前面的基础质控、过滤以及去除细胞周期的影响后&#xff0c;我们可以开始SCTransform normalization。 SCTransform normalization的优势&#xff1a; 1️⃣ 一个SCTransform函数即可替代NormalizeData, ScaleData, FindVariableFeatures三个函数;2️⃣ 对测序深度的校正…

[足式机器人]Part2 Dr. CAN学习笔记-Ch0-1矩阵的导数运算

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-Ch0-1矩阵的导数运算 1. 标量向量方程对向量求导&#xff0c;分母布局&#xff0c;分子布局1.1 标量方程对向量的导数1.2 向量方程对向量的导数 2. 案例分析&#xff0c;线性回归3. 矩阵求导的链…

DCDC电源的选择

https://blog.csdn.net/xiahailong90/article/details/79086490 先说结论&#xff1a; 高开关频率的交换式电源转换器有利也有弊&#xff0c;本文提到的好处包括体积更小、瞬时响应更快以及电压overshoot 和undershoot 值都更小&#xff0c;主要缺点则是效率降低和热量增加。 …

STK Components 二次开发-飞行器

1.创建飞机 参数帮助文档 var poitList GetTracksData(); var waypointPropagator new WaypointPropagator(m_earth, poitList); var locationPoint waypointPropagator.CreatePoint();m_aircraft new Platform {Name "MH730",LocationPoint locationPoint,Or…

独立版求职招聘平台小程序开发

小程序招聘系统开发 我们开发了一款高效、便捷的互联网招聘平台。在这里&#xff0c;可以轻松实现企业入驻、职位发布、在线求职、精准匹配职位和人才&#xff0c;以及参与招聘会等功能。目标是为求职者和企业搭建一个连接彼此的桥梁&#xff0c;帮助您更快地找到满意的工作&…

基于Go语言实现简易Web应用

目录 前言Go语言特点写在使用Go语言实现Web应用前面创建Web服务器声明一个结构体操作加入中间件的使用使用静态文件服务器最后 前言 在编程语言中&#xff0c;近几年问世的几个新语言都是非常不错的&#xff0c;比如Go、Python、 Rust等等。其中&#xff0c;Go语言(Golang)作…

线程池、及Springboot线程池实践

摘要 本文介绍了线程池基本概念、线程及线程池状态、java中线程池提交task后执行流程、Executors线程池工具类、最后介绍在springboot框架下使用线程池和定时线程池&#xff0c;以及task取消 线程池基本 背景 线程池 线程池是一种多线程处理形式&#xff0c;处理过程中将任务…

探索人工智能领域——每日20个名词详解【day8】

目录 前言 正文 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x1f4e3;如需转载&#xff0c;请事先与我联系以…

使用UART和USART在STM32上进行双向通信

在本文中&#xff0c;我们将深入了解如何在STM32上使用UART&#xff08;通用异步收发传输器&#xff09;和USART&#xff08;通用同步异步收发传输器&#xff09;实现双向通信。UART和USART是常见的串口通信协议&#xff0c;通常用于与其他设备进行数据传输。我们将重点介绍如何…

01_W5500简介

目录 W5500简介&#xff1a; 芯片特点: 全硬件TCPIP协议栈: 引脚分布&#xff1a; W5500简介&#xff1a; W5500是一款高性价比的以太网芯片&#xff0c;其全球独一无二的全硬件TCPIP协议栈专利技术&#xff0c;解决了嵌入式以太网的接入问题&#xff0c;简单易用&#xff…

redis 安装在liunx安装和常用文件配置

文章目录 安装配置文件设置测试启动服务连接服务 安装 1.官网下载压缩包: https://redis.io/download/ 2.将压缩包上传到Linux环境中 解压: tar -xvf redis-xxxxx 3.liunx 需要c的环境 yum -y install gcc-c4.进入redis文件夹 make && make install5.推荐不是必须…

CPP-SCNUOJ-Problem P24. [算法课贪心] 跳跃游戏

Problem P24. [算法课贪心] 跳跃游戏 给定一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。 数组中的每个元素代表你在该位置可以跳跃的最大长度 判断你是否能够到达最后一个下标。 输入 输入一行数组nums 输出 输出true/fasle 样例 标准输入 2 3 1 …