【探索Linux】—— 强大的命令行工具 P.28(网络编程套接字 —— 简单的UDP网络程序模拟实现)

news2024/11/26 12:43:38

在这里插入图片描述

阅读导航

  • 引言
  • 一、UDP协议
  • 二、UDP网络程序模拟实现
    • 1. 预备代码
      • ⭕makefile文件
      • ⭕打印日志文件
      • ⭕打开指定的终端设备文件,并将其作为标准错误输出的目标文件描述符
    • 2. UDP 服务器端实现(UdpServer.hpp)
    • 3. UDP 客户端实现(main函数)
  • 温馨提示

引言

在前一篇文章中,我们详细介绍了UDP协议和TCP协议的特点以及它们之间的异同点。本文将延续上文内容,重点讨论简单的UDP网络程序模拟实现。通过本文的学习,读者将能够深入了解UDP协议的实际应用,并掌握如何编写简单的UDP网络程序。让我们一起深入探讨UDP网络程序的实现细节,为网络编程的学习之旅添上一份精彩的实践经验。

一、UDP协议

UDP(User Datagram Protocol)是一种无连接的、轻量级的网络传输协议,它提供了快速、简单的数据传输服务。下面是一个简单的UDP程序实现示例,包括一个UDP服务器和一个UDP客户端。详介绍可以看上一篇文章:UDP协议介绍 | TCP协议介绍 | UDP 和 TCP 的异同

二、UDP网络程序模拟实现

1. 预备代码

⭕makefile文件

.PHONY:all
all:udpserver udpclient

udpserver:Main.cc
	g++ -o $@ $^ -std=c++11
udpclient:UdpClient.cc
	g++ -o $@ $^ -lpthread -std=c++11


.PHONY:clean
clean:
	rm -f udpserver udpclient

这段代码是一个简单的 Makefile 文件,用于编译 UDP 服务器(udpserver)和 UDP 客户端(udpclient)的程序。在这个 Makefile 中定义了两个规则:

  1. all:表示默认的目标,依赖于 udpserver 和 udpclient 目标,即执行 make 命令时会编译 udpserver 和 udpclient。
  2. clean:用于清理生成的可执行文件 udpserver 和 udpclient。

在 Makefile 中使用了一些特殊的关键字和变量:

  • .PHONY:声明 all 和 clean 是伪目标,不是真正的文件名。
  • $@:表示目标文件名。
  • $^:表示所有依赖文件列表。
  • -std=c++11:指定 C++ 的编译标准为 C++11。
  • -lpthread:链接 pthread 库,用于多线程支持。

⭕打印日志文件

#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen; // 默认输出方式为屏幕打印
        path = "./log/"; // 默认日志文件存放路径
    }

    void Enable(int method)
    {
        printMethod = method; // 设置日志输出方式(屏幕、单个文件、分类文件)
    }

    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl; // 屏幕打印日志信息
            break;
        case Onefile:
            printOneFile(LogFile, logtxt); // 将日志信息追加写入单个文件
            break;
        case Classfile:
            printClassFile(level, logtxt); // 将日志信息追加写入分类文件
            break;
        default:
            break;
        }
    }

    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname; // 构建日志文件的完整路径
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // 打开文件,如果文件不存在则创建
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size()); // 将日志信息写入文件
        close(fd);
    }

    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // 构建分类文件名,例如"log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt); // 将日志信息追加写入分类文件
    }

    ~Log()
    {
    }

    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        printLog(level, logtxt); // 打印日志信息
    }

private:
    int printMethod; // 日志输出方式
    std::string path; // 日志文件存放路径
};

该代码实现了一个简单的日志记录类(Log),其中包括设置日志输出方式(屏幕、单个文件、分类文件)和打印日志信息的功能。

  • Log 类是一个用于记录日志的类。
  • Enable 函数用于设置日志输出方式,可以选择屏幕打印、单个文件或分类文件。
  • printLog 函数根据设置的日志输出方式,将日志信息打印到屏幕、追加写入单个文件或分类文件。
  • printOneFile 函数用于将日志信息追加写入单个文件。
  • printClassFile 函数用于将日志信息追加写入分类文件。
  • levelToString 函数将日志级别转换为对应的字符串表示。
  • operator() 函数是重载的函数调用运算符,用于打印日志信息。
  • path 是日志文件存放路径,默认为"./log/"。
  • printMethod 是日志输出方式,默认为屏幕打印。
  • SIZE 定义了缓冲区大小。
  • InfoDebugWarningErrorFatal 是日志级别的定义。
  • ScreenOnefileClassfile 是日志输出方式的定义。
  • LogFile 是单个文件名的定义。

⭕打开指定的终端设备文件,并将其作为标准错误输出的目标文件描述符

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// 定义要打开的终端设备文件路径
std::string terminal = "/dev/pts/6";

// 打开指定的终端设备文件,并将其作为标准错误输出的目标文件描述符
int OpenTerminal()
{
    // 使用open函数以只写方式打开终端设备文件
    int fd = open(terminal.c_str(), O_WRONLY);
    if(fd < 0)
    {
        // 如果打开终端设备文件失败,则输出错误信息到标准错误输出
        std::cerr << "open terminal error" << std::endl;
        return 1; // 返回错误代码
    }

    // 将终端设备文件的文件描述符复制给标准错误输出的文件描述符
    // 这样标准错误输出就会重定向到指定的终端设备上
    dup2(fd, 2);

    // 如果需要在此处输出信息到标准错误输出,可以使用printf等函数

    // 关闭文件描述符
    // close(fd);

    return 0; // 返回成功代码
}

这段代码的作用是打开一个终端设备文件 “/dev/pts/6”,将其作为标准错误输出(stderr)的目标文件描述符,实现将错误信息输出到指定的终端设备上。

  • terminal 变量存储了要打开的终端设备文件路径 “/dev/pts/6”。
  • OpenTerminal 函数尝试打开指定的终端设备文件,并将其作为标准错误输出的目标文件描述符。
    • 首先使用 open 函数打开终端设备文件,以只写方式(O_WRONLY)。
    • 如果成功打开终端设备文件,则将其文件描述符复制给标准错误输出的文件描述符(2),即 dup2(fd, 2),这样标准错误输出就会重定向到该终端设备上。
    • 如果打开终端设备文件失败,则输出错误信息到标准错误输出,并返回错误代码 1。
    • 最后函数返回0表示成功。

2. UDP 服务器端实现(UdpServer.hpp)

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>
#include "Log.hpp"

// 使用Log类记录日志信息
Log lg;

enum {
    SOCKET_ERR = 1,
    BIND_ERR
};

uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
const int size = 1024;

class UdpServer {
public:
    UdpServer(const uint16_t& port = defaultport, const std::string& ip = defaultip)
        : sockfd_(0), port_(port), ip_(ip), isrunning_(false)
    {}

    void Init() {
        // 1. 创建UDP socket
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INET
        if (sockfd_ < 0) {
            lg(Fatal, "socket create error, sockfd: %d", sockfd_);
            exit(SOCKET_ERR);
        }
        lg(Info, "socket create success, sockfd: %d", sockfd_);

        // 2. 绑定socket
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_); // 端口号需要转换为网络字节序
        local.sin_addr.s_addr = inet_addr(ip_.c_str()); // 将IP地址转换为网络字节序

        if (bind(sockfd_, (const struct sockaddr*)&local, sizeof(local)) < 0) {
            lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));
    }

    void CheckUser(const struct sockaddr_in& client, const std::string clientip, uint16_t clientport) {
        // 检查用户是否已经存在在线用户列表中
        auto iter = online_user_.find(clientip);
        if (iter == online_user_.end()) {
            online_user_.insert({clientip, client});
            std::cout << "[" << clientip << ":" << clientport << "] add to online user." << std::endl;
        }
    }

    void Broadcast(const std::string& info, const std::string clientip, uint16_t clientport) {
        // 广播消息给所有在线用户
        for (const auto& user : online_user_) {
            std::string message = "[";
            message += clientip;
            message += ":";
            message += std::to_string(clientport);
            message += "]# ";
            message += info;
            
            socklen_t len = sizeof(user.second);
            sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr*)(&user.second), len);
        }
    }

    void Run() {
        isrunning_ = true;
        char inbuffer[size];
        while (isrunning_) {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);

            // 接收客户端发送的消息
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);
            if (n < 0) {
                lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }

            // 获取客户端的IP地址和端口号
            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr);

            // 检查用户是否已经存在在线用户列表中
            CheckUser(client, clientip, clientport);

            std::string info = inbuffer;

            // 将接收到的消息广播给所有在线用户
            Broadcast(info, clientip, clientport);
        }
    }

    ~UdpServer() {
        if (sockfd_ > 0)
            close(sockfd_);
    }

private:
    int sockfd_; // 网络文件描述符
    std::string ip_; // 服务器IP地址
    uint16_t port_; // 服务器端口号
    bool isrunning_; // 服务器运行状态
    std::unordered_map<std::string, struct sockaddr_in> online_user_; // 在线用户列表
};
  • Log.hpp 是用于记录日志信息的头文件。
  • lg 是一个 Log 类的对象,用于输出日志信息。
  • enum 定义了两个错误类型:SOCKET_ERRBIND_ERR,分别表示 socket 创建错误和绑定错误。
  • defaultportdefaultip 分别设置默认的端口号和 IP 地址。
  • size 定义接收缓冲区的大小为 1024 字节。
  • UdpServer 类封装了一个 UDP 服务器。
  • 构造函数 UdpServer 接受端口号和 IP 地址作为参数,并初始化成员变量。
  • Init 函数用于初始化 UDP 服务器,其中:
    • 创建 UDP socket,并检查创建是否成功。
    • 绑定 socket 到指定的 IP 地址和端口号,并检查绑定是否成功。
  • CheckUser 函数用于检查用户是否已经存在在线用户列表中,如果不存在则将其添加到列表中。
  • Broadcast 函数用于向所有在线用户广播消息,其中:
    • 消息格式为 [发送者IP:发送者端口号]# 消息内容
    • 使用 sendto 函数发送消息给每个在线用户。
  • Run 函数是 UDP 服务器的主循环,其中:
    • 循环接收客户端发送的消息,并将其广播给所有在线用户。
    • 对每个客户端,获取其 IP 地址和端口号,并进行用户检查和消息广播。
  • ~UdpServer 析构函数关闭网络文件描述符。
  • sockfd_ 是网络文件描述符,用于创建和管理网络连接。
  • ip_ 是服务器的 IP 地址。
  • port_ 是服务器的端口号。
  • isrunning_ 表示服务器的运行状态,用于控制循环退出。
  • online_user_ 是一个无序映射,用于保存在线用户的 IP 地址和对应的 sockaddr_in 结构体。

3. UDP 客户端实现(main函数)

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "Terminal.hpp"

using namespace std;

// 函数声明:打印程序的使用方法
void Usage(std::string proc);

// 结构体:用于传递线程参数
struct ThreadData
{
    struct sockaddr_in server; // 服务器地址结构体
    int sockfd; // socket 文件描述符
    std::string serverip; // 服务器 IP 地址
};

// 线程函数:接收消息
void *recv_message(void *args);

// 线程函数:发送消息
void *send_message(void *args);

// 主函数
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]); // 打印使用方法
        exit(0);
    }

    // 解析命令行参数
    std::string serverip = argv[1]; // 服务器 IP 地址
    uint16_t serverport = std::stoi(argv[2]); // 服务器端口号

    // 初始化 ThreadData 结构体
    struct ThreadData td;
    bzero(&td.server, sizeof(td.server)); // 清零服务器地址结构体
    td.server.sin_family = AF_INET; // 设置地址族为 IPv4
    td.server.sin_port = htons(serverport); // 设置端口号(转换为网络字节序)
    td.server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 设置服务器 IP 地址(转换为网络字节序)

    // 创建 UDP socket
    td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (td.sockfd < 0)
    {
        cout << "socket error" << endl;
        return 1;
    }

    td.serverip = serverip; // 存储服务器 IP 地址

    pthread_t recvr, sender; // 定义接收消息和发送消息的线程
    pthread_create(&recvr, nullptr, recv_message, &td); // 创建接收消息线程
    pthread_create(&sender, nullptr, send_message, &td); // 创建发送消息线程

    // 等待接收消息和发送消息的线程退出
    pthread_join(recvr, nullptr);
    pthread_join(sender, nullptr);

    close(td.sockfd); // 关闭 socket
    return 0;
}

// 函数实现:打印程序的使用方法
void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n" << std::endl;
}

// 线程函数实现:接收消息
void *recv_message(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args); // 强制类型转换为 ThreadData 结构体指针
    char buffer[1024]; // 接收消息的缓冲区
    while (true)
    {
        memset(buffer, 0, sizeof(buffer)); // 清空缓冲区
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);

        ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len); // 接收消息
        if (s > 0)
        {
            buffer[s] = 0;
            cerr << buffer << endl; // 输出接收到的消息
        }
    }
}

// 线程函数实现:发送消息
void *send_message(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args); // 强制类型转换为 ThreadData 结构体指针
    string message; // 存储用户输入的消息
    socklen_t len = sizeof(td->server); // 服务器地址的长度

    // 发送欢迎消息
    std::string welcome = td->serverip + " comming...";
    sendto(td->sockfd, welcome.c_str(), welcome.size(), 0, (struct sockaddr *)&(td->server), len);

    while (true)
    {
        cout << "Please Enter@ ";
        getline(cin, message); // 获取用户输入的消息

        sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->server), len); // 发送消息给服务器
    }
}

温馨提示

感谢您对博主文章的关注与支持!如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C++编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索Linux、C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

使用Redis做缓存的小案例

如果不了解Redis&#xff0c;可以查看本人博客&#xff1a;Redis入门 Redis基于内存&#xff0c;因此查询速度快&#xff0c;常常可以用来作为缓存使用&#xff0c;缓存就是我们在内存中开辟一段区域来存储我们查询比较频繁的数据&#xff0c;这样&#xff0c;我们在下一次查询…

服装行业实现数字化转型,只需上线这个系统!

近年来&#xff0c;内卷、降本增效等词频频冲上热搜&#xff0c;随着经济的周期性下行&#xff0c;服装企业的竞争愈发激烈&#xff0c;小单快返时代的到来&#xff0c;加速了服装行业的重新洗牌。在这样的大环境下&#xff0c;服装企业数字化转型几乎成了取得确定性增长的唯一…

QT网络编程之获取本机网络信息

一.概述 查询一个主机的MAC地址或者IP地址是网络应用中常用到的功能&#xff0c;Qt提供了QHostInfo和QNetworkInterface 类可以用于此类信息的查询 1.QHostInfo 类&#xff08;显示和查找本地的信息&#xff09; 2.QNetworkInterface 类&#xff08;获得应用程序上所在主机的…

Sublime Text3 C/C++一键调试运行代码

minGW的系统环境配置&#xff1a; 使用的C/C编译器是minGW&#xff0c;点此进入官网链接&#xff0c;下载后需要在线安装&#xff0c;安装后需要将安装目录下的bin目录所在路径加入path环境变量。本菜鸡的电脑里安装了CodeBlocks&#xff0c;在CodeBlocks的安装目录下有MinGW&…

婴儿洗衣机十大排名名牌:十款超高价值婴儿洗衣机综合整理

我们都知道宝宝的皮肤是超级娇嫩的&#xff0c;宝宝的衣物也一样&#xff0c;宝宝的衣物大部分都是纯棉的。如果将宝宝的衣物和大人衣服一起扔进大型洗衣机混洗&#xff0c;更可能出现细菌交叉感染&#xff0c;对小宝宝来说百害而无一利&#xff0c;会让小宝宝肌肤过敏、红肿、…

json字符串的数据提取

json的数据提取 学习目标 掌握 json相关的方法(load loads dump dumps)了解 jsonpath的使用(提取 json中的数据) 2 复习什么是json JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式&#xff0c;它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和…

蓝牙耳机哪个品牌质量比较好?真实用户实测推荐!

​一款舒适的蓝牙耳机能让你长时间沉浸在音乐之中&#xff0c;享受无拘无束的听音体验。但对于许多朋友来说&#xff0c;选择合适的蓝牙耳机可能是个难题。别担心&#xff0c;我来为你推荐几款既舒适又音质出色的蓝牙耳机&#xff0c;让你的选择变得更加简单。 一、蓝牙耳机这样…

AI浸入社交领域,泛娱乐APP如何抓住新风口?

2023年是大模型技术蓬勃发展的一年&#xff0c;自ChatGPT以惊艳姿态亮相以来&#xff0c;同年年底多模态大模型技术在国内及全球范围内的全面爆发&#xff0c;即模型能够理解并生成包括文本、图像、视频、音频等多种类型的内容。例如&#xff0c;基于大模型的文本到图像生成工具…

【SpringBoot3.x教程04】SpringBoot如何自定义starter

前言&#xff1a;什么是Starter POMs Starter POMs是预配置的依赖集合&#xff0c;旨在提供一种快速的方式来引入和管理Spring及相关技术栈的依赖。每个Starter POM都是针对特定的Spring模块或技术场景设计的。使用Starter POM&#xff0c;开发者只需要添加一个依赖项&#xff…

mysql - 多表访问

多表访问 创建两个表 mysql> SELECT * FROM t1; ------------ | m1 | n1 | ------------ | 1 | a | | 2 | b | | 3 | c | ------------ 3 rows in set (0.00 sec)mysql> SELECT * FROM t2; ------------ | m2 | n2 | ------------ | 2 | …

2023年天府杯全国大学生数学建模竞赛A题震源属性识别模型构建与震级预测解题全过程文档及程序

2023年天府杯全国大学生数学建模竞赛 A题 震源属性识别模型构建与震级预测 原题再现&#xff1a; 地震是一种较为复杂的地壳运动现象&#xff0c;全世界每年发生的地震灾害事故不计其数。旨在减少地震灾害的地震预警预报技术需要在日常地震监测中有效识别出天然地震事件&…

PyTorch学习笔记之激活函数篇(三)

文章目录 3、ReLU3.1 公式3.2 对应的图像3.3 对应的图像的代码3.4 优点与不足3.5 torch.relu()函数 3、ReLU 3.1 公式 ReLU函数的公式&#xff1a; f ( x ) { x , x > 0 0 , x < 0 f(x) \begin{cases} x&,x>0 \\ 0&,x<0 \end{cases} f(x){x0​,x>…

鸿蒙4.0ArkUI快速入门(一)应用模型

ArkUI篇 应用模型Stage模型FA模型模型对比 应用模型 应用模型是HarmonyOS为开发者提供的应用程序所需能力的抽象提炼&#xff0c;它提供了应用程序必备的组件和运行机制。 HarmonyOS先后提供了两种应用模型&#xff1a; FA&#xff08;Feature Ability&#xff09;模型&…

HarmonyOS4.0—自定义渐变导航栏开发教程

前言 今天要分享的是一个自定义渐变导航栏&#xff0c;本项目基于鸿蒙4.0。 先看效果&#xff1a; 这种导航栏在开发中也比较常见&#xff0c;特点是导航栏背景色从透明到不透明的渐变&#xff0c;以及导航栏标题和按钮颜色的变化。 系统的导航栏无法满足要求&#xff0c;我们…

【Linux】项目部署CPU彪高如何定位

1.查看所有CPU占比 使用top指令获取彪高进程的PID 2.输出进程的信息 ps H -eo pid,tid,%cpu | grep 1313 3.查看线程的信息 jstack tid nid都是十六进制的 4.进制转换 将 tid的十进制转为十六进制 找到nid 可以定位到具体位置 5.关闭程序 ps -ef | grep java kill -9 jav…

LeetCode 面试经典150题 380.O(1)时间插入、删除和获取随机元素

题目&#xff1a; 实现RandomizedSet 类&#xff1a; RandomizedSet() 初始化 RandomizedSet 对象bool insert(int val) 当元素 val 不存在时&#xff0c;向集合中插入该项&#xff0c;并返回 true &#xff1b;否则&#xff0c;返回 false 。bool remove(int val) 当元素 va…

Lightroom Classic 2024 for mac 中文激活:强大的图像后期处理软件

对于追求极致画面效果的摄影师来说&#xff0c;Lightroom Classic 2024无疑是Mac平台上的一款必备软件。它凭借其强大的功能和出色的性能&#xff0c;赢得了众多摄影师的青睐。 软件下载&#xff1a;Lightroom Classic 2024 for mac 中文激活版下载 在Lightroom Classic 2024中…

软件测评中心:进行科技成果鉴定测试的注意事项和好处简析

软件产品科技成果鉴定是有效评价科技成果质量和水平的方法之一&#xff0c;也是鼓励科技成果通过市场竞争等方式得到有效的评价和认可&#xff0c;可以推动科技成果的进步和转化。 一、进行科技成果鉴定测试时的注意事项&#xff1a;   1、应由具备一定资质和能力的专业机构…

处理器方法的参数

处理器方法的参数&#xff1a; 处理器方法可以包含以下四类参数&#xff0c;这些参数会在系统调用时由系统自动赋值&#xff0c;即程序员可在方法内直接使用: HttpServletRequestHttpServletResponseHttpSession请求中所携带的请求参数 控制器方法&#xff1a; 前面三个参数怎…

【LeetCode: 173. 二叉搜索树迭代器 + dfs + 二叉搜索树】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…