【网络】tcp_socket

news2024/9/20 18:33:45

tcp_socket

  • 一、tcp_server与udp_server一样的部分
  • 二、listen接口(监听)
  • 三、accept接收套接字
    • 1、为什么还要多一个套接字(明明已经有了个socket套接字文件了,为什么要多一个accept套接字文件?)
    • 2、底层拿到新连接并根据连接进行通信
    • 3、类比理解监听套接字和连接套接字的区别
  • 四、服务端提供服务(向客户端回消息)
  • 五、tcp_client客户端编写
    • 1、框架
    • 2、客户端卡退了,服务端怎么处理?(read返回值为0)
    • 3、一个有趣的现象--两个一样的客户端去连接客户端?(单进程服务)
    • 4、方法1:子进程关listensock,父进程关sockfd
    • 5、处理waitpid问题:孙子进程处理机制或者signal忽略信号
    • 6、方法2:多线程版本
    • 7、方法3:线程池版本
      • (1)线程池代码ThreadPool.hpp
      • (2)任务代码Task.hpp
      • (3)代码改进
      • (4)结果
  • 六、服务端翻译小程序
  • 七、进化版:出现错误的细节问题
    • 1、向一个已经关闭的文件描述符的文件中进行写入,读端已经关掉了,写端继续写,OS会把客户端进程杀掉
    • 2、重连
  • 八、在线翻译服务+重连
  • 九、地址复用
  • 十、守护进程介绍
  • 十一、tcp的通信原理


一、tcp_server与udp_server一样的部分

#pragma once

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h> 
#include "Log.hpp"

Log lg;

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const uint16_t defaultport = 8080;

enum
{
    SocketERR=2,
    BINDERR=3
};

class TcpServer
{
public:
    TcpServer(const uint16_t& port = defaultport, const std::string& ip = defaultip)
        : _socketfd(defaultfd)
        , _port(port)
        , _ip(ip)
    {}
    void InitServer()
    {
        _socketfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_socketfd < 0)
        {
            lg(Fatal, "create socket err, errno: %d, errst: %s", errno, strerror(errno));
            exit(SocketERR);
        }
        lg(Info, "create socket successful, sockfd:%d", _socketfd);
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        inet_aton(_ip.c_str(), &(local.sin_addr));
        int n = bind(_socketfd, (struct sockaddr*)&local, sizeof(local));
        if (n < 0)
        {
            lg(Fatal, "bind socket err, errno: %d, errst: %s", errno, strerror(errno));
            exit(BINDERR);
        }
        lg(Info, "bind sucessful");
        // 监听套接字  -- 因为tcp是要等待别人来连接的,所以要有个监听套接字进行监听等待别人来连接
    }
    void RunServer()
    {

    }
    ~TcpServer(){}
private:
    int _socketfd;
    uint16_t _port;
    std::string _ip;
};

这里我们用inet_aton将本地序列转化成为网络序列。
在这里插入图片描述

二、listen接口(监听)

在这里插入图片描述

启动服务器,状态是listen:
在这里插入图片描述

三、accept接收套接字

在这里插入图片描述

1、为什么还要多一个套接字(明明已经有了个socket套接字文件了,为什么要多一个accept套接字文件?)

在这里插入图片描述
各司其职呗~~_socketfd是接客用的,面对随时来的新连接先接客到底层去,而accept的返回值才真正是服务的套接字,也就是I/O端口进行服务的,从底层拿出来进行服务!所以_socketfd只有一个,而accept返回值却有多个!(一个接客,多个服务员)
在这里插入图片描述

修改一下socket套接字为listen套接字:
在这里插入图片描述
在这里插入图片描述

2、底层拿到新连接并根据连接进行通信

在这里插入图片描述

在这里插入图片描述

3、类比理解监听套接字和连接套接字的区别

相当于我们去一家饭店,监听套接字是外面迎客的人,把人都迎进来,里面肯定有服务员吧,服务员就是连接套接字,服务员去服务,迎客的人去迎客。我们目前实现的是迎客连接一条龙,也就是来一群人,一个个迎客,再进来一个个服务,太慢了,所以我们的目标是实现迎客和服务两条线,来了人和迎客互不耽误,两者并发式的运行,就需要我们用多线程版本,但是会出现很多问题我们在下面一一进行讲解。

四、服务端提供服务(向客户端回消息)

那我们就写一个Server函数进行封装来将服务端进行提供服务!我们传参传accept从底层拿到的套接字和拿到的套接字的ip地址和port,我们找到ip地址用的是inet_ntop函数接口。
在这里插入图片描述
在这里插入图片描述

小问题:我上来通信的字符串和数字等难道到网络中不考虑大小端问题?我地址需要转大端,难道通信的字符串不用转吗?答案是肯定要转的,但是它网络里面自动帮忙转了。

在这里插入图片描述

我们用简单的Server函数中的代码为接收到消息,拼接一下再返回给服务器:
在这里插入图片描述
在这里插入图片描述

五、tcp_client客户端编写

1、框架

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

void Usage(const std::string& proc)
{
    std::cout << "\n\rUsages: " << proc << "serverip serverport\n" << std::endl;
}

// ./tcpclient serverip serverport
int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    int socketfd = socket(AF_INET, SOCK_STREAM, 0);
    if (socketfd < 0)
    {
        std::cerr << "socket create error " << std::endl; 
        return 1;
    }

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));

    // tcp客户端要绑定,不需要显示绑定,os随机分配
    // 向网络服务器请求连接,客户端发起connect的时候,进行自动随机绑定
    int n = connect(socketfd, (struct sockaddr*)&server, sizeof(server));
    if (n < 0)
    {
        std::cerr << "connect err " << std::endl;
        return 2;
    }
    // 连接成功
    std::string message;
    while (true)
    {
        std::cout << "Please Enter@ ";
        std::getline(std::cin, message);
        char inbuffer[4096];
        write(socketfd, message.c_str(), message.size()); // 写进socketfd

        int n = read(socketfd, inbuffer, sizeof(inbuffer)); // 从socketfd读入inbuffer
        if (n <= 0)
        {
            std::cerr << "read err " << std::endl;
            return 3;
        }
        inbuffer[n] = 0;
        std::cout << inbuffer << std::endl;
    }
    close(socketfd);
    return 0;
}

在这里插入图片描述

2、客户端卡退了,服务端怎么处理?(read返回值为0)

客户端卡退了,服务端怎么办?
服务端保存好消息,其余不管,也就是我们的服务器read的返回值为0,也就是从底层拿到的连接的文件描述符值为0的时候,表示客户端退出。
在这里插入图片描述
在这里插入图片描述

3、一个有趣的现象–两个一样的客户端去连接客户端?(单进程服务)

在这里插入图片描述
理由是单进程版,得等一个进程搞好退出后才能实现另一个进程的使用。

4、方法1:子进程关listensock,父进程关sockfd

因为子进程会有很多没必要的listensock套接字,父进程会有很多没必要的sockfd套接字,子进程是进行监听,父进程是进行连接,其套接字本质不一样,子进程负责监听,父进程负责连接,所以把这些没必要的套接字都关了。
在这里插入图片描述
但这种情况父进程用waitpid还是有很大问题,因为父进程得等子进程退出!所以跟单进程没什么区别了,下面我们介绍怎么解决这个问题:

5、处理waitpid问题:孙子进程处理机制或者signal忽略信号

在这里插入图片描述

因为父进程等待子进程是阻塞的方式,导致的使父进程要一直等待子进程退出,子进程退出需要一定的时间,并且连的子进程多了,子进程就会一直运行,等待一个运行后再等下一个运行,时间太久了,所以我们使用一下子进程创建孙子进程的方法,子进程创建完立马退出,告诉父进程我退出了,父进程就能够执行下一步操作,而孙子进程去跑服务,并且孙子进程给操作系统进行托孤,孙子进程不受爷爷进程控制,并发的去跑进程。

但上面这个方法还是有很大的问题的,因为子进程的创建代价太大了,要有进程地址空间等很多需要创建的东西,很麻烦,所以我们用下面的这种方法:

6、方法2:多线程版本

在这里插入图片描述

在这里插入图片描述
上面的做法仍然有不合理之处,就是假如说是几亿个用户连接,那岂不是要几亿个线程,所以我们用线程池版本来解决!

7、方法3:线程池版本

(1)线程池代码ThreadPool.hpp

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defalutnum = 5;

template <class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Wakeup()
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool IsQueueEmpty()
    {
        return tasks_.empty();
    }
    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : threads_)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args)
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();

            while (tp->IsQueueEmpty())
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop();
            tp->Unlock();

            t();
        }
    }
    void Start()
    {
        int num = threads_.size();
        for (int i = 0; i < num; i++)
        {
            threads_[i].name = "thread-" + std::to_string(i + 1);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
        }
    }
    T Pop()
    {
        T t = tasks_.front();
        tasks_.pop();
        return t;
    }
    void Push(const T &t)
    {
        Lock();
        tasks_.push(t);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance()
    {
        if (nullptr == tp_) // ???
        {
            pthread_mutex_lock(&lock_);
            if (nullptr == tp_)
            {
                std::cout << "log: singleton create done first!" << std::endl;
                tp_ = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock_);
        }

        return tp_;
    }

private:
    ThreadPool(int num = defalutnum) : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:
    std::vector<ThreadInfo> threads_;
    std::queue<T> tasks_;

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *tp_;
    static pthread_mutex_t lock_;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

(2)任务代码Task.hpp

#pragma once
#include <iostream>
#include <string>
#include "Log.hpp"
extern Log lg;

class Task
{
public:
    Task(int sockfd, const std::string &ipstr, const uint16_t &clientport)
        : _sockfd(sockfd), _clientip(ipstr), _clientport(clientport)
    {
    }
    void run()
    {
        char buffer[4096];
        // 因为是面向字节流的,所以读网络跟读文件一样简单
        // 先读到buffer
        ssize_t n = read(_sockfd, buffer, sizeof(buffer)); // 从套接字信息中读取消息存到buffer中
        if (n < 0)
        {
            lg(Warning, "read err, readip:%s, readport:%d\n", _clientip.c_str(), _clientport);
        }
        else if (n == 0) // 客户端退出
        {
            lg(Info, "%s:%d quit, server close fd:%d", _clientip.c_str(), _clientport, _sockfd);
        }
        else
        {
            buffer[n] = 0;
            std::cout << "client say# " << buffer << std::endl;
            std::string echo_string = "tcpserver echo@ ";
            echo_string += buffer;
            // 再写入
            write(_sockfd, echo_string.c_str(), echo_string.size()); // 处理完的消息写回sockfd文件
        }
        close(_sockfd);
    }
    void operator()()
    {
        run();
    }

private:
    int _sockfd;
    std::string _clientip;
    uint16_t _clientport;
};

(3)代码改进

void RunServer()
    {
        ThreadPool<Task>::GetInstance()->Start(); // 开启线程池的单例模式
        // signal(SIGCHLD, SIG_IGN); // 信号忽略
        lg(Info, "tcp_server is running...");
        while (true)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            // 1.获取新连接
            int sockfd = accept(_listensocketfd, (struct sockaddr*)&client, &len);
            if (sockfd < 0)
            {
                lg(Warning, "accept err, errno: %d, errst: %s", errno, strerror(errno));
                continue;
            }

            uint16_t clientport = ntohs(client.sin_port);
            char ipstr[32]; // 自定义的缓冲区
            inet_ntop(AF_INET, &(client.sin_addr), ipstr, sizeof(ipstr));

            // 2.根据新连接来通信
            lg(Info, "get a new link, sockfd:%d, clentip:%s, clientport:%d", sockfd, ipstr, clientport);
            // 3.4 线程池版本
            Task t(sockfd, ipstr, clientport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }

(4)结果

发送一则消息则退出线程。
在这里插入图片描述

六、服务端翻译小程序

Init.hpp:

#pragma once 
#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>
#include "Log.hpp"

extern Log lg;

const std::string dicname = "./dict.txt";
const std::string sep = ":";

static bool Split(std::string &line, std::string* part1, std::string* part2) // line输入性参数 part1/2都是输出型参数
{
    auto pos = line.find(sep);
    if (pos == std::string::npos)
    {
        return false;
    }
    *part1 = line.substr(0, pos);
    *part2 = line.substr(pos + 1);
    return true;
}

class Init
{
public:
    Init()
    {
        std::ifstream in(dicname);
        if (!in.is_open())
        {
            lg(Fatal, "ifstream open %s error", dicname.c_str());
            exit(1);
        }
        std::string line;
        while (std::getline(in, line))
        {
            std::string part1, part2;
            Split(line, &part1, &part2);
            dict.insert({part1, part2});
        }
        in.close();
    }
    std::string Translation(const std::string& key)
    {
        auto it = dict.find(key);
        if (it == dict.end()) return "Unkonw";
        else return it->second;
    }
private:
    std::unordered_map<std::string, std::string> dict;
};

Task.hpp:

#pragma once
#include <iostream>
#include <string>
#include "Log.hpp"
#include "Init.hpp"
extern Log lg;
Init init;
class Task
{
public:
    Task(int sockfd, const std::string &ipstr, const uint16_t &clientport)
        : _sockfd(sockfd), _clientip(ipstr), _clientport(clientport)
    {
    }
    void run()
    {
        char buffer[4096];
        // 因为是面向字节流的,所以读网络跟读文件一样简单
        // 先读到buffer
        ssize_t n = read(_sockfd, buffer, sizeof(buffer)); // 从套接字信息中读取消息存到buffer中
        if (n < 0)
        {
            lg(Warning, "read err, readip:%s, readport:%d\n", _clientip.c_str(), _clientport);
        }
        else if (n == 0) // 客户端退出
        {
            lg(Info, "%s:%d quit, server close fd:%d", _clientip.c_str(), _clientport, _sockfd);
        }
        else
        {
            buffer[n - 2] = 0;
            std::cout << "client key# " << buffer << std::endl;
            std::string echo_string = init.Translation(buffer);
            // 再写入
            write(_sockfd, echo_string.c_str(), echo_string.size()); // 处理完的消息写回sockfd文件
        }
        close(_sockfd);
    }
    void operator()()
    {
        run();
    }

private:
    int _sockfd;
    std::string _clientip;
    uint16_t _clientport;
};

在这里插入图片描述

七、进化版:出现错误的细节问题

1、向一个已经关闭的文件描述符的文件中进行写入,读端已经关掉了,写端继续写,OS会把客户端进程杀掉

在这里插入图片描述

在这里插入图片描述

所以我们在write的时候都需要用返回值做一层判断,防止向已经关闭掉的文件描述符中写信息。要么就加信号忽略:
在这里插入图片描述

2、重连

tcpclient.cc:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

八、在线翻译服务+重连

我们先来这些词汇:
在这里插入图片描述
在这里插入图片描述

九、地址复用

在这里插入图片描述

十、守护进程介绍

守护进程

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

守护进程的启动bash:
在这里插入图片描述

带上日志文件(日志信息打印到当前路径下):
在这里插入图片描述

在这里插入图片描述

接口:默认00
在这里插入图片描述

十一、tcp的通信原理

在这里插入图片描述

tcp是全双工的:两个人吵架。
在这里插入图片描述

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

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

相关文章

基于VMware(虚拟机) 创建 Ubunton 24.04

目录 1. 设置网络 1. 在安装ubuntu时设置网络 2.在配置文件中修改 2.设置 root 密码 3. 防火墙设置 1 安装防火墙 2 开启和关闭防火墙 3 开放端口和服务规则 4 关闭端口和删除服务规则 5 查看防火墙状态 4. 换源 1. 在创建的时切换源 2.修改源配置 1、Ubuntu24.04 …

MBR60200PT-ASEMI无人机专用MBR60200PT

编辑&#xff1a;ll MBR60200PT-ASEMI无人机专用MBR60200PT 型号&#xff1a;MBR60200PT 品牌&#xff1a;ASEMI 封装&#xff1a;TO-247 批号&#xff1a;最新 恢复时间&#xff1a;35ns 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;60A 最大循环峰值反向…

学习华为IPD流程黑话2.0

目录 1、内容简介 2、概念六&#xff1a;管道管理 3、概念七&#xff1a;业务计划 4、概念八&#xff1a;IPMT 的投资活动 5、概念九&#xff1a;BETA、ESS、ESP 作者简介 1、内容简介 学习任何新事物都是从概念开始的。 以我个人最近遇到的一个事为例&#xff1a; 前…

TCP三次握手和四次挥手的理解

三次握手 第一次握手&#xff1a; 客户端发出 请求报文其中SYN应1&#xff0c;选择一个序列号x 第二次握手&#xff1a; 服务端接收到之后回复 确认报文&#xff0c;其中SYN应1&#xff0c;ACK1&#xff0c;确认号是x1&#xff0c;同时为自己初始化序列号y 第三次握手&…

uboot的mmc partconf命令

文章目录 命令格式参数解释具体命令解释总结 mmc partconf 是一个用于配置 MMC (MultiMediaCard) 分区的 U-Boot 命令。具体来说&#xff0c;这个命令允许你设置或读取 MMC 卡的分区配置参数。让我们详细解释一下 mmc partconf 0 0 1 0 命令的含义。 命令格式 mmc partconf &…

go语言day15 goroutine

Golang-100-Days/Day16-20(Go语言基础进阶)/day17_Go语言并发Goroutine.md at master rubyhan1314/Golang-100-Days GitHub 第2讲-调度器的由来和分析_哔哩哔哩_bilibili 一个进程最多可以创建多少个线程&#xff1f;-CSDN博客 引入协程 go语言中内置了协程goroutine&#…

使用水星Mecury人形机器人搭建VR遥操作控制平台!

VR遥操作机械臂是一种将虚拟现实技术与机械臂控制相结合的系统&#xff0c;使用户可以通过虚拟现实设备操控和交互实际的机械臂。这种技术可以应用于多个领域&#xff0c;包括远程操作、培训、危险环境中的工作等。 双臂人形机器人是一种模拟人体上半身结构&#xff0c;包括头部…

手机秒变高清电脑摄像头:轻松实现高清视频通话的方法

你知道手机也可以充当电脑摄像头来使用吗&#xff1f;随着科技的不断发展&#xff0c;手机摄像头的画质效果已经可以满足大多数普通用户的日常需求。很多自媒体平台在考虑性价比上都会优先考虑使用拥有高画质高分辨率的手机进行拍摄、直播。如果你需要临时召开视频会议&#xf…

【C++】类和对象(三)完结篇

个人主页 创作不易&#xff0c;感谢大家的关注&#xff01; 文章目录 ⭐一、再探构造函数1.初始化列表 &#x1f389;二、类型转换&#x1f3e0;三、static成员&#x1f3dd;️四、友元⏱️五、内部类&#x1f388;六、匿名对象&#x1f3a1;七、在拷贝对象时的编译器优化 ⭐一…

WEB漏洞知识点介绍

简要说明以上漏洞危害情况 SQL注入&#xff1a;对应网站数据库权限&#xff0c;通过这个漏洞可以获取到网站数据库里面的数据&#xff0c;范围不同权限不同&#xff1b; 文件上传&#xff1a;找到文件上传的漏洞大部分可以直接获取到网站权限 xss跨站&#xff1a;围绕网站管理…

运维锅总浅析网络攻击与防范

本文介绍常见的网络攻击手法及防御措施&#xff0c;并进一步介绍如何进行安全教育和培训、攻击溯源。希望对您提高网络安全防范意识有所帮助&#xff01; 一、常见的网络攻击手法 网络攻击手法多种多样&#xff0c;以下是一些常见的网络攻击手法及其基本原理&#xff1a; 1.…

MarkTool之TCP客户端

TCP客户端&#xff0c;主要作用是与TCP服务端连接进行数据通讯 1、连接参数就2个&#xff0c;服务器IP和服务器Port&#xff0c;参数设置好&#xff0c;再点连接则会连接成功 2、接收数据和发送数据的参数设置&#xff0c;有16进制&#xff0c;有字符&#xff0c;有原始数据&a…

Linux:基础命令学习

目录 一、ls命令 实例&#xff1a;-l以长格式显示文件和目录信息 实例&#xff1a;-F根据文件类型在列出的文件名称后加一符号 实例&#xff1a; -R 递归显示目录中的所有文件和子目录。 实例&#xff1a; 组合使用 Home目录和工作目录 二、目录修改和查看命令 三、mkd…

httpx,一个网络请求的 Python 新宠儿

大家好&#xff01;我是爱摸鱼的小鸿&#xff0c;关注我&#xff0c;收看每期的编程干货。 一个简单的库&#xff0c;也许能够开启我们的智慧之门&#xff0c; 一个普通的方法&#xff0c;也许能在危急时刻挽救我们于水深火热&#xff0c; 一个新颖的思维方式&#xff0c;也许能…

项目实战二

Git 服务器 公共代码平台GitLab 配置gitlab 1.设置管理员帐号密码 2.让程序员传代码到20主机上需要配置&#xff1a; 创建用户 mark 1234.com 创建用户组devops 然后把mark 添加到devons 创建项目 http://192.168.88.20/devops/myproject.git 3.客户端操作&#x…

【Linux】—— 进程的基本概念、PCB、fork

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;Linux跬步积累 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日一题 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0…

数据结构之二叉树详解及遍历算法(C/C#/C++)

文章目录 一、二叉树的基本概念二、二叉树的遍历1. 前序遍历2. 中序遍历3. 后序遍历 三、C语言实现四、C#语言实现五、C语言实现总结 当涉及到数据结构中的二叉树及其遍历方式时&#xff0c;了解如何正确操作和遍历二叉树是至关重要的。以下是关于二叉树及其三种常见遍历方式&a…

现实版的《农夫与蛇》,我给你三连支持,你说我限你流。给你提供资源,你背后骂我们?

文章目录 一、如何上热榜&#xff1f;农夫与蛇的故事1.1 故事之始——遇见“蛇”1.2 退群后——反咬“农夫” 二、事情大发展&#xff01;“我要轰炸你”&#xff01;三、报警不成就开始写文章污蔑四、关于技术交流群五、关于&#x1f34a;易编橙终身成长社群</font> 一、…

ActiViz实战:二维纹理贴图vtkTexture

文章目录 一、效果预览二、基本概念三、功能特性四、与C++不同五、完整示例代码一、效果预览 二、基本概念 vtkTexture是VTK(Visualization Toolkit)中用于纹理映射的一个类,它允许用户将二维图像(纹理)贴到三维物体的表面上,从而增加场景的真实感和细节。 纹理映射:是一…

【JavaEE初阶】线程的状态

目录 &#x1f4d5; 线程的状态 &#x1f333; 观察线程的所有状态 &#x1f6a9; NEW 状态 &#x1f6a9; TERMINATED 状态 &#x1f6a9; RUNNABLE 就绪状态 &#x1f6a9; WAITING 状态 &#x1f6a9; TIME_WAITING 状态 &#x1f6a9; BLOCKED 状态 &#x1f384;…