Linux系统编程——线程控制

目录

一,关于线程控制

二,线程创建

2.1 pthread_create函数

2.2 ps命令查看线程信息

三,线程等待

3.1 pthread_join函数

 3.2 创建多个线程

3.3 pthread_join第二个参数 

四,线程终止

4.1 关于线程终止

4.2 pthread_exit线程退出

4.3 pthread_cancel线程取消

五,线程分离

七,线程ID和进程地址空间布局

7.1 线程ID与LWP

7.2 地址空间共享区中的线程栈

7.3 代码验证几个问题

八,pthread_create第四个参数传类的对象


一,关于线程控制

  1. 在线程概念章节,我们已经说过:Linux内核中没有明确的线程的概念,只有轻量级进程的概念,所以Linux没有给我们提供线程的系统调用,只有轻量级进程的系统调用
  2. 但又因为没有系统接口,用户用起来就比较犯难,所以他也必须给我们提供一套完整的线程接口。于是Linux设计者在用户层实现了一套线程方案,以动静态库的方式提供给用户进行使用 --> pthread线程库(也叫POSIX线程库或者原生线程库)
  3. 原生指的是大部分的Linux系统默认内置该线程库,使用这些库函数需要包含头文件"#include<pthread.h>",同时在编译链接时需要加上"-lpthread"选项
  4. 并且由于原生线程库是第三方库,不属于系统接口,那么pthread函数出错时不会设置errno全局变量,而是直接用返回值来告诉用户执行结果

 下面是常规情况下使用原生线程库的makefile内容:

mythread:mythread.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f mythread

二,线程创建

2.1 pthread_create函数

pthread_create就是的创建线程的函数了,在man手册中函数声明如下:

 解释下四个参数:

  1. 第一个参数是输出型参数,负责返回成功创建的新线程的id,pthread_t也是一个unsigned long int
  2. attr表示设置创建线程的属性,我们目前只要传入nullptr即可,表示不添加其它属性
  3. 第三个参数很明显是一个函数指针,该指针指向一个参数为void*,返回值为void*的函数
  4. 第四个参数arg表示要传入第三个函数指针参数的值,这个arg不仅仅可以传内置类型参数,也可以传C++类的对象,后面有代码

下面是用pthread_create函数创建线程,然后打印pid的代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void show(const string &name)
{
    cout << name << ", pid: " << getpid() << "\n" << endl; // 在一个进程里面获取pid
}

void *threadRun(void *args)
{
    const string name = (char *)args;
    while (true)
    {
        show(name);
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    char name[64];
    snprintf(name, sizeof name, "new thread");              // 以特定格式化,把内容搞到字符串里去,然后创建进程时把该字符串传过去
    pthread_create(&tid, nullptr, threadRun, (void *)name); // 新线程就跳转过去执行这个函数,主线程就继续向下运行
    while (true)
    {
        cout << "I am main thread" << ", pid: " << getpid() << endl;
        sleep(1);
    }
}

 打印结果如下:

 

可以发现,两个执行流打印的进程pid一样,可以证明确实创建了线程 

2.2 ps命令查看线程信息

先让上面的进程挂着,可以另起一个窗口,先查看进程的

ps -ajx | head -1 && ps ajx | grep mythread

发现只有一条,正常,因为我们创建的是线程不是进程,要查看线程信息需要使用ps -aL 

ps -aL | head -1 && ps -aL | grep mythread

 其中PID就是进程的PID,LWP(Light Weight Process)就是线程的ID了,其中最小的就是主线程,主线程的LWP值和PID一样,LWP也存在task_struct里。

所以CPU调度时本质是看的LWP,由于我们前面说的是看的PID,那其实是一个进程只有一个线程的这种特殊情况,所以在CPU看来,LWP和PID其实是一样滴

三,线程等待

3.1 pthread_join函数

线程和进程一样,线程也是需要进行等待的,如果主线程不等待新线程,就会引起类似僵尸进程得问题,导致内存泄漏。线程等待得函数为pthread_join,下面是函数声明

 参数解释:

  1. 第一个参数thread表示要等待得线程得ID
  2. 第二个参数retval表示线程退出时得退出码信息

 3.2 创建多个线程

和进程一样,我么也可以创建多个线程并进行等待,如下代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <cstdio>
using namespace std;

void show(const string &name)
{
    cout << name << ", pid: " << getpid() << endl; // 在一个进程里面获取pid
}

void *threadRun(void *args)
{
    const string name = (char *)args;
    for (int i = 0; i < 5; i++)
    {
        show(name);
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid[5]; // 创建5个线程,这个pthread_t类型也是一个unsigned long int
    char name[64];
    for (int i = 1; i <= 5; i++)
    {
        snprintf(name, sizeof name, "%s-%d", "thread", i);         // 以特定格式化,把内容搞到字符串里去,然后创建进程时把该字符串传过去
        pthread_create(tid + i, nullptr, threadRun, (void *)name); // 新线程就跳转过去执行这个函数,主线程就继续向下运行
    }
    for (int i = 1; i <= 5; i++)
    {
        int a = pthread_join(tid[i], nullptr);
        if (a == 0)
        {
            cout << "thread-" << i << " quit" << endl;
        }
        else
        {
            cout << "thread-" << i << " quit error: " << a << endl;
        }
    }
}

上面代码的逻辑大致是:一次性创建5个线程,每个线程各自打印部分内容,主线程等待,5秒过后,5个线程全部退出,和进程等待一样 

3.3 pthread_join第二个参数 

前面提到过:①原生线程库函数是通过返回值来告诉用户退出结果的    ②pthread_join的第二个参数可以获取线程退出码

但其实,第二个参数说明白点其实是获取线程退出信息,“获取退出码”其实是代表退出信号恰好是数字,所以我们可以用第二个参数获取其它信息,如下代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
// 1,线程谁先运行与调度器有关
// 2,线程一旦异常, 就可能导致整个进程崩溃:所以,所有线程共用一个寄存器的标记位
// 3,线程的输入值和返回值问题
// 4,线程异常退出的理解

void *threadRoutine(void *args)
{
    int *data = new int[10];
    for (int i = 0; i < 10; i++)
    {
        cout << "新线程:" << (char *)args << " running " << i << endl;
        sleep(1);
        data[i] = i;
        // int a = 100;
        // a /= 0;
        // 线程出现除0异常,进程也随之终止
    }
    // exit(10); exit是进程退出,所以不要在线程里轻易调用exit
    //  return (void *)10; // 这个返回值一般返回给主线程,返回给pthread_join的第二个参数,直接保存在ret里
    return (void *)data; // data保存i的值,返回给主线程
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread");

    int *ret = nullptr;               // 这里的ret属于指针变量。可以存值
    pthread_join(tid, (void **)&ret); // 默认是阻塞等待新线程退出,不关心线程异常退出,所以线程的健壮性会差一些
    // cout << "main thread wait done, main quit, " << (long long)ret << endl;    //64位下指针是8字节,指针是四字节,所以用long long
    for (int i = 0; i < 10; i++) // 打印线程返回给主线程的执行结果
    {
        cout << ret[i] << " ";
    }
    cout << endl;
    return 0;
}

上面代码大致逻辑是: 创建一个线程,然后在线程里new一个数组,然后循环打印,并且在每次循环就往数组里写一个数组,最后线程执行完后,把数组的指针返回给主线的pthread_join的第二个参数,然后打印这个数组,结果如下:

所以我们可以感觉到这些传来传去的值,其实自由度很高,而这都是void*这个特殊类型的优势所在,后面会专门讲下void*相比其它类型指针的优势

四,线程终止

4.1 关于线程终止

  1. 线程执行进程代码的一部分,换到代码上就是执行一个函数,而一个函数常见的退出方式有三种:return,exit,信号退出。
  2. return是线程的正常退出,对其它线程没有影响,但是如果是exit,那么就是进程退出,会把所有的线程全部给退出掉,而且发生异常例如除0错误,是信号退出,信号退出也是进程退出
  3. 所以主线的pthread_join等待线程函数不关心线程的异常退出信息,因为线程如果出异常,那么全没了,再等待也没有意义了;而且主线程return时,其它线程也会退出,因为主线程一旦退出,其它线程赖以生存的地址空间资源也就全释放了,所以也会退出
  4. 所以要想线程退出并且不影响其它线程,有三种方法:return,pthread_exit和pthread_cancel,就是我们接下来要讲的

4.2 pthread_exit线程退出

使用起来很简单,功能就是终止当前,但是由于线程结时无法返回退出信息给它的调用者(因为是干掉自身,所以返回值无意义),所以pthread_exit和return返回的指针必须是全局的或者是malloc从堆上申请的,不能返回线程的独立栈上的数据,因为线程退出也会销毁栈

如下代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void *threadRun(void *args)
{
    cout << "I am new thread, my quit number is: 666" << endl;
    pthread_exit((void *)666);
}

int main()
{
    pthread_t tid;
    void *ret = nullptr;
    pthread_create(&tid, nullptr, threadRun, nullptr);
    pthread_join(tid, &ret);
    cout << (long long)ret << endl;
}

代码的逻辑很简单,线程函数直接调用pthread_exit退出,然后将666作为返回值返回,然后主线程的pthread_join捕捉到,就拿到了线程的返回值“666”

4.3 pthread_cancel线程取消

上面的pthread_exit是线程自己退出自己,其实使用起来比较别扭,而且容易出错,所以我们可以用pthread_cancel线程取消函数,这个函数可以指定线程ID取消,相比上面线程退出,线程取消灵活性更高,我们可以直接在主线程像类似“远程操控”一样控制线程退出

如下代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void *threadRoutine(void *args)
{
    int i = 0;
    // int *data = new int[10];
    cout << "新线程:" << (char *)args << " running " << endl;
    while (true)
    {
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    for (int i = 5; i > 0; i--)
    {
        cout << "线程取消倒计时: " << i << endl;
        sleep(1);
    }
    pthread_cancel(tid); // 直接取消线程
    int *ret = nullptr;
    pthread_join(tid, (void **)&ret); // 线程被取消,join的时候,退出码是-1 --> #define PTHREAD_CANCELED ((void *)-1)
    cout << "新线程退出,退出码为:  " << (long long)ret << endl;
}

五,线程分离

  1. 其实线程分离的概念和进程分离的概念是高度重合的,都是“如果不关心线程退出情况”,就不再需要pthread_join等待了,因为在不关心线程退出的情况下,join反而是一种负担,会降低整体效率,毕竟是阻塞式等待的
  2. 分离线程后,该线程依旧要使用进程的资源,崩溃时也要退出,只是该线程不再需要join等待了,线程分离后也不会造成“僵尸线程”,系统会自动回收该线程的PAB资源
  3. 分离和join是冲突的,只能存在一个

 

 下面是线程分离的样例代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void *threadRoutine(void *args)
{
    pthread_detach(pthread_self()); // pthread_self()返回自己线程的id
    cout << "新线程:" << (char *)args << " running " << endl;
    sleep(3);
    cout << "新线程退出" << endl;
    pthread_exit((void *)666);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    sleep(5);
    cout << "主线程退出" << endl;
}

七,线程ID和进程地址空间布局

7.1 线程ID与LWP

先阐明一个事实:线程ID和LWP没关系

下面是创建一个线程,然后获取线程ID,再以16进制打印出来,如下代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

std::string toHex(pthread_t tid) // 把tid转16进制
{
    char hex[64];
    snprintf(hex, sizeof(hex), "%p", tid);
    return hex;
}

void *threadRoutine(void *args)
{
    while (true)
    {
        cout << "thread id: " << toHex(pthread_self()) << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    pthread_join(tid, nullptr);
    return 0;
}

 

  • pthread_create函数会产生一个线程ID,存放在第一个参数指向的空间中
  • 这个ID与LWP是两回事,LWP属于进程调度的范畴,因为线程是操作系统调度的最基本单位,所以需要一个数值来唯一表示,类似线程PID
  • pthread_create函数的第一个参数获取的数值与pthread_self()函数返回的数值是一样的

7.2 地址空间共享区中的线程栈

谈论这个要结合上面提到的线程ID

  1. 因为我们用的不是Linux自带的创建线程的接口,我们用的是pthread库中的接口,所以线程的概念是库给我们维护的,而且线程库不用维护线程的执行流,我们用的原生线程库,也是要加载到内存里的,加载到地址空间里
  2. 库的加载,默认是动态链接的,先把磁盘上的pthread库加载到内存里,然后通过页表映射到共享区,当我想调用库,就直接从代码区跳转到共享区,(如果想访问系统接口,就直接跳转到内核区然后通过内核级页表找到内核代码)
  3. 由于OS没有具体的关于线程的概念,所以关于线程的各种属性要由库来管理,而要管理,就要“先描述,再组织”,所以库就在共享区中为每一个线程都创建了对应的用户层线程的数据集合,这个集合里就包含了线程栈,而由于地址空间是线性的,所以为了让每个线程都能快速找到自己的属性集合,就把每个描述线程的结构体的的起始地址充当线程id了
  4. 主线程就用的是内核级栈结构,新线程用的就是共享区内部提供的私有栈结构
  5. 所以,本质上我们说的进程ID,其实就是一个虚拟地址,每一个进程的虚拟地址都是不同的,因此可以用它来区分每一个线程

问题:如何保证栈区是每一个线程独占的呢?
解答:用户要线程,但是OS只提供轻量级进程,所以在中间加了个线程库作为中间软件层,一样的,线程也要被管理起来,所以这个管理OS承担一部分,库承担一部分,OS承担的主要是对轻量级进程的调度和它的内核数据结构的管理,库主要是要为用户提供线程相关的属性字段。(线程也需要又自己私有的一部分属性,而这部分属性有可能无法和进程一样完整地表示线程,所以这部分工作就由库来完成。)

7.3 代码验证几个问题

通过下面的代码可以验证两个线程的周边结论:

①验证每个线程都有独立的栈空间

②主线程能访问每一个线程栈里的数据

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
using namespace std;

#define NUM 3
int *p = NULL;

struct threadData
{
    string threadname;
};

string toHex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "0x%x", tid);
    return buffer;
}

void InitThreadData(threadData *td, int number)
{
    td->threadname = "thread-" + to_string(number);
}

void *threadRoutine(void *args)
{

    int test_i = 0;
    threadData *td = static_cast<threadData *>(args);
    if (td->threadname == "thread-2")
        p = &test_i; // 验证主线程能访问其它线程的栈数据
    int i = 0;
    while (i < 10)
    {
        cout << "pid: " << getpid() << ", tid: " << toHex(pthread_self()) << ", threadname: " << td->threadname
             << ", test_t: " << test_i << ", &test_i: " << &test_i << endl;
        sleep(1);
        i++;
        test_i++;
    }

    delete td;
    return nullptr;
}

int main()
{
    vector<pthread_t> tids;
    for (int i = 0; i < NUM; i++)
    {
        pthread_t tid;
        threadData *td = new threadData;
        InitThreadData(td, i);

        pthread_create(&tid, nullptr, threadRoutine, td);
        tids.push_back(tid);
    }
    sleep(1);
    cout << "main thread get a thread local value, val: " << *p << ", &p: " << p << endl;
    for (int i = 0; i < tids.size(); i++)
    {
        pthread_join(tids[i], nullptr);
    }

    return 0;
}

 打印结果乱序很正常,比较线程本来就是并发执行的,从上面的信息我们看到下面几点:

  1. 每个线程打印的pid是一样的,每个线程的tid也就是地址不同,表示每个线程都有独立的栈结构
  2. 主线程定义全局指针p,p能访问2号线程的test_i值,说明主线程能访问每个线程的栈数据
  3. 每个线程都对test_i++,打印的值一样,再次证明线程有独立的栈结构
  4. 在线程当中,线程有独立的栈结构,但是没有私有的栈结构,其它线程仍然能访问,但是以后编写代码时,非常不建议这样搞

八,pthread_create第四个参数传类的对象

pthread_create第四个参数不仅可以传整数或字符串,也可以传类的对象,这就是void*的好处,如下代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

class Request
{
public:
    Request(int start, int end, const string &threadname)
        : _start(start), _end(end), _threadname(threadname)
    {
    }

public:
    int _start;
    int _end;
    string _threadname;
};

class Response
{
public:
    Response(int result, int exitcode)
        : _result(result), _exitcode(exitcode)
    {
    }

public:
    int _result;
    int _exitcode;
};

void *sumCount(void *args)
{
    Request *rq = static_cast<Request *>(args); // 类似于Request *eq = (Request*)args
    int sum = 0;
    for (int i = rq->_start; i <= rq->_end; i++) // 求start到end的数的和
    {
        cout << rq->_threadname << " is runing... " << i << endl;
        sum += i;
        usleep(60000);
    }
    Response *rsq = new Response(sum, 0);
    delete rq;
    return rsq;
}

int main()
{
    pthread_t tid;
    Request *rq = new Request(1, 100, "thread 1:");
    pthread_create(&tid, nullptr, sumCount, rq); // 这个参数不仅仅只能传整数或字符串,我还可以传类的对象

    void *ret;
    pthread_join(tid, &ret);
    Response *rsp = static_cast<Response *>(ret);
    cout << "rsp->result: " << rsp->_result << endl;
    delete rsp;
    return 0;
}


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

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

相关文章

LeetCode 算法:腐烂的橘子 c++

原题链接&#x1f517;&#xff1a;腐烂的橘子 难度&#xff1a;中等⭐️⭐️ 题目 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b;值 1 代表新鲜橘子&#xff1b;值 2 代表腐烂的橘子。 每分钟&#…

Java版Flink使用指南——定制RabbitMQ数据源的序列化器

大纲 新建工程新增依赖数据对象序列化器接入数据源 测试修改Slot个数打包、提交、运行 工程代码 在《Java版Flink使用指南——从RabbitMQ中队列中接入消息流》一文中&#xff0c;我们从RabbitMQ队列中读取了字符串型数据。如果我们希望读取的数据被自动化转换为一个对象&#x…

JAVA案例ATM系统

一案例要求&#xff1a; 首先完成ATM的用户登录和用户开户两个大功能&#xff0c;用户开户有账户名&#xff0c;性别&#xff0c;账户密码&#xff0c;确认密码&#xff0c;每次取现额度&#xff0c;并且随机生成一个7位数的账号&#xff0c;用户登录功能有查询&#xff0c;存…

k8s 部署 metribeat 实现 kibana 可视化 es 多集群监控指标

文章目录 [toc]环境介绍老(来)板(把)真(展)帅(示)helm 包准备配置监控集群获取集群 uuid生成 api_key配置 values.yaml 配置 es 集群获取集群 uuid 和 api_key配置 values.yaml 查看监控 缺少角色的报错 开始之前&#xff0c;需要准备好以下场景 一套 k8s 环境 k8s 内有两套不同…

Aqara 发布多款智能照明新品,引领空间智能新时代

7月8日&#xff0c;全球 IoT 独角兽品牌 Aqara 以“光&#xff0c;重塑空间想象”为主题&#xff0c;举办了线上智能照明新品沟通会。 会上&#xff0c;Aqara 正式发布一系列引领行业的智能照明新品&#xff0c;包括银河系列轨道灯 V1 以及繁星系列妙控旋钮 V1 等&#xff0c;…

Hospital Management System v4.0 SQL 注入漏洞(CVE-2022-24263)

前言 CVE-2022-24263 是一个影响 Hospital Management System (HMS) v4.0 的 SQL 注入漏洞。这个漏洞允许攻击者通过注入恶意 SQL 代码来获取数据库的敏感信息&#xff0c;甚至可能控制整个数据库。以下是对这个漏洞的详细介绍&#xff1a; 漏洞描述 在 Hospital Management…

使用Keil 点亮LED灯 F103ZET6

1.新建项目 不截图了 2.startup_stm32f10x_hd.s Keil\Packs\Keil\STM32F1xx_DFP\2.2.0\Device\Source\ARM 搜索startup_stm32f10x_hd.s 复制到项目路径&#xff0c;双击Source Group 1 3.项目文件夹新建stm32f10x.h&#xff0c; 新建文件main.c #include "stm32f10x…

OS-HACKNOS-2.1

确定靶机IP地址 扫描靶机开放端口信息 目录扫描 访问后发现个邮箱地址 尝试爆破二级目录 确定为wordpress站 利用wpscan进行漏洞扫描 #扫描所有插件 wpscan --url http://192.168.0.2/tsweb -e ap 发现存在漏洞插件 cat /usr/share/exploitdb/exploits/php/webapps/46537.txt…

Camera Raw:裁剪

Camera Raw 的裁剪 Crop面板提供了裁剪、旋转、翻转、拉直照片等功能&#xff0c;通过它们可以更精确地调整照片的视角和范围&#xff0c;以达到最佳二次构图的视觉效果。 快捷键&#xff1a;C ◆ ◆ ◆ 使用方法与技巧 1、使用预设 选择多种裁剪预设&#xff08;如 1:1、16:…

前端传到后端的data数组中有些属性值为空

将前端输入框中的值全部放入data中传入后端&#xff0c;但是在后端查看发现后端接收到的数据有些属性值为空。 第一种情况&#xff1a;只有第一个属性为空&#xff0c;其余属性接收正常 可能原因&#xff1a;后端用来接收的 比如前端发送数据&#xff1a; 实际上前端发送的数…

防火墙详解(USG6000V)

0、防火墙组网模式 防火墙能够工作在三种模式下分别是路由模式、透明模式、旁路检测模式、混合模式 0.1、路由模式 路由模式&#xff1a;防火墙全部以第三层对外连接&#xff0c;即接口具有IP 地址。一般都用在防火墙是边界的场景下 防火墙需要的部署/配置&#xff1a; 接…

【Excel】 批量跳转图片

目录标题 1. CtrlA全选图片 → 右键 → 大小和属性2. 取消 锁定纵横比 → 跳转高度宽度 → 关闭窗口3. 最后一图拉到最后一单元格 → Alt吸附边框![](https://i-blog.csdnimg.cn/direct/d56ac1f41af54d54bb8c68339b558dd1.png)4. CtrlA全选图片 → 对齐 → 左对齐 → 纵向分布!…

C++初探究

概述 C可以追溯到1979年&#xff0c;C之父Bjarne Stroustrup在在使用C语言研发工作时发现C语言的不足&#xff0c;并想要将其改进&#xff0c;到1983年&#xff0c;Bjarne Stroustrup在C语言的基础上添加了面向对象编程的特性&#xff0c;设计出了C的雏形。 网址推荐 C官方文…

Java面试八股之MySQL主从复制机制简述

MySQL主从复制机制简述 MySQL的主从复制机制是一种数据复制方案&#xff0c;用于在多个服务器之间同步数据。此机制允许从一个服务器&#xff08;主服务器&#xff09;到一个或多个其他服务器&#xff08;从服务器&#xff09;进行数据的复制&#xff0c;从而增强数据冗余、提…

HTTP 请求走私漏洞详解

超详细的HTTP请求走私漏洞教程&#xff0c;看完还不会你来找我。 1. 简介 HTTP请求走私漏洞&#xff08;HTTP Request Smuggling&#xff09;发生在前端服务器&#xff08;也称代理服务器&#xff0c;一般会进行身份验证或访问控制&#xff09;和后端服务器在解析HTTP请求时&…

YASKAWA安川Σ-V系列伺服驱动器AC设计维护手侧

YASKAWA安川Σ-V系列伺服驱动器AC设计维护手侧

C#——序列化和反序列化概念

(1)序列化 在编程中&#xff0c;序列化是指将对象转换为可存储或传输的格式&#xff0c;例如将对象转换为 JSON 字符串或字节流。 (2)反序列化 在编程中&#xff0c;反序列化则是将存储或传输的数据转换回对象的过程。 序列化和反序列化经常用于数据的持久化、数据交换以及…

JAVA基础-----包装类,自动装箱、拆箱

一、包装类&#xff1a; Java中的数据类型总体上分为基本数据类型和引用数据类型。引用类型的数据可以通过对象的属性和方法来进行操作&#xff0c;但对于基本数据类型的数据&#xff0c;我们能不能像操作对象那样来操作呢&#xff1f;为了实现这个目标&#xff0c;Java为8种基…

WebOffice在线编微软Offfice,并以二进制流的形式打开Word文档

在日常办公场景中&#xff0c;我们经常会遇到这种场景&#xff1a;我们的合同管理系统的各种Word,excel,ppt数据都是以二进制数组的形式存储在数据库中&#xff0c;如何从数据库中读取二进制数据&#xff0c;以二进制数据作为参数&#xff0c;然后加载到浏览器的Office窗口&…

华为HCIP Datacom H12-821 卷30

1.单选题 以下关于OSPF协议报文说法错误的是? A、OSPF报文采用UDP报文封装并且端口号是89 B、OSPF所有报文的头部格式相同 C、OSPF协议使用五种报文完成路由信息的传递 D、OSPF所有报文头部都携带了Router-ID字段 正确答案&#xff1a;A 解析&#xff1a; OSPF用IP报…