c++网络编程实战——开发基于协议的文件传输模块(一)如何实现一个简单的tcp长连接

news2024/11/16 9:56:20

前言

在之前的几篇内容中我们已经介绍过基于ftp协议的文件传输模块,而这个系列我们所想实现的就是如何实现基于tcp进行的文件传输模块,话不多说,开坑开坑!

什么是tcp长连接

我们知道tcp在建立连接的时候会通过三次握手与四次挥手来建立tcp连接,而服务端与客户端之间的工作流程一般是这样的:
在这里插入图片描述
它的工作流程如下:

1.客户端向服务端发送连接请求
2.服务端接收客户端连接请求
3.二者之间相互发送报文实现数据的传输
4.断开连接

这种一完成数据交换就断开连接的通讯方式我们称为tcp的短连接。那么现在问题来了: 客户端与服务端连接是需要时间的,同时是否可以立即连接上是不确定的(如果现在服务端可连接的客户端已达到上限),如果我们希望让客户端与服务端始终保持连接状态,应该怎么办呢?这就是我们今天所要探讨的——tcp长连接

什么是tcp长连接?相对于tcp短连接在业务流程结束后就会断开服务端与客户端之间的连接,tcp长连接在不进行通讯的时候也会保持连接, 以便后续可以继续使用该连接进行通信。

tcp长连接的实现机制

其实tcp长连接的实现机制很简单,在之前的文章中我们就已经讲过我们通过进程心跳来告诉进程守护模块进程是否在正常运行,在tcp长连接中我们亦可以通过发送心跳报文来让保证客户端与服务端之间的连接。

思考:
为什么我们要发送心跳报文呢?理论上只要我们不主动断开的话服务端和客户端不就是一直连接,但是由于在等待过程中可能会出现特殊情况导致连接断开,所以我们需要发送心跳报文来进行对tcp连接的监控

tcp长连接的实现

在实现长连接之前我写了一个用来实现tcp短连接的tcp服务端与客户端,我们可以来看一下:

//基于多进程实现的服务端
#include "../public/_public.h"
using namespace idc;

ctcpserver tcpserver;  // 创建服务端对象。
clogfile logfile;            // 服务程序的运行日志。

void FathEXIT(int sig);  // 父进程退出函数。
void ChldEXIT(int sig);  // 子进程退出函数。

string strsendbuffer;   // 发送报文的buffer。
string strrecvbuffer;    // 接收报文的buffer。
int total=1000; //设置的初始余额

bool bizmain();    // 业务处理主函数。

int main(int argc,char *argv[])
{
    if (argc!=3)
    {
      printf("Using:./demo04 port logfile\n");
      printf("Example:./demo04 5005 /log/idc/demo04.log\n\n"); 
      return -1;
    }

    // 关闭全部的信号和输入输出。
    // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
    // 但请不要用 "kill -9 +进程号" 强行终止
    //closeioandsignal(false); 
    signal(SIGINT,FathEXIT); signal(SIGTERM,FathEXIT);

    if (logfile.open(argv[2])==false) { printf("logfile.open(%s) failed.\n",argv[2]); return -1; }

    // 服务端初始化。
    if (tcpserver.Initserver(atoi(argv[1]))==false)
    {
      logfile.write("tcpserver.initserver(%s) failed.\n",argv[1]); return -1;
    }

    while (true)
    {
        // 获取客户端的连接请求。
        if (tcpserver.Accept()==false)
        {
            logfile.write("tcpserver.accept() failed.\n"); FathEXIT(-1);
        }

        logfile.write("客户端(%s)已连接。\n",tcpserver.getclientip());

        if (fork()>0) { tcpserver.Closeconn(); continue; }  // 父进程继续回到accept()。
   
        // 子进程重新设置退出信号。
        signal(SIGINT,ChldEXIT); signal(SIGTERM,ChldEXIT);

        tcpserver.Closelisten();     // 子进程关闭监听的socket。

        while (true)
        {
            // 子进程与客户端进行通讯,处理业务。
            if (tcpserver.Read(strrecvbuffer)==false)
            {
                printf("%d",tcpserver.m_connsock);
                perror("tcpserver.read()");
                logfile.write("tcpserver.read() failed.\n"); ChldEXIT(0);
            }
            logfile.write("接收:%s\n",strrecvbuffer.c_str());

            bizmain();    // 业务处理主函数。

            if (tcpserver.Write(strsendbuffer)==false)
            {
                logfile.write("tcpserver.send() failed.\n"); ChldEXIT(0);
            }
            logfile.write("发送:%s\n",strsendbuffer.c_str());
        }

        ChldEXIT(0);
    }
}

// 父进程退出函数。
void FathEXIT(int sig)  
{
    // 以下代码是为了防止信号处理函数在执行的过程中被信号中断。
    signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);

    logfile.write("父进程退出,sig=%d。\n",sig);

    tcpserver.Closelisten();    // 关闭监听的socket。

    kill(0,15);     // 通知全部的子进程退出。

    exit(0);
}

// 子进程退出函数。
void ChldEXIT(int sig)  
{
    // 以下代码是为了防止信号处理函数在执行的过程中被信号中断。
    signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);

    logfile.write("子进程退出,sig=%d。\n",sig);

    tcpserver.Closeconn();    // 关闭客户端的socket。

    exit(0);
}

void biz001();   // 登录。
void biz002();   // 查询余额。
void biz003();   // 转帐。

bool bizmain()    // 业务处理主函数。
{
    int bizid;  // 业务代码。
    getxmlbuffer(strrecvbuffer,"bizid",bizid);

    switch(bizid)
    {
        case 1:    // 登录。
            biz001();
            break;
        case 2:    // 查询余额。
            biz002();
            break;
        case 3:    // 转帐。
            biz003();
            break;
        default:   // 非法报文。
            strsendbuffer="<retcode>9</retcode><message>业务不存在。</message>";
            break;
    }

    return true;
}

void biz001()   // 登录。
{
    string username,password;
    getxmlbuffer(strrecvbuffer,"username",username);
    getxmlbuffer(strrecvbuffer,"password",password);
    logfile.write("用户名:%s,密码:%s。\n",username.c_str(),password.c_str());

    if ( (username=="test") && (password=="123456") )
        strsendbuffer="<retcode>0</retcode><message>成功。</message>";
    else
        strsendbuffer="<retcode>-1</retcode><message>用户名或密码不正确。</message>";
}

void biz002()   // 查询余额。
{
    strsendbuffer=sformat("<retcode>0</retcode><message>成功</message><query>%d</query>",total);
}

void biz003()   // 转帐。
{
    int query;
    getxmlbuffer(strrecvbuffer,"query",query);
    logfile.write("转帐金额:%d。\n",query);
    total+=query;
    strsendbuffer="<retcode>0</retcode><message>成功。</message>";
}
//客户端
    #include "../public/_public.h"

using namespace idc;

ctcpclient tcpclient;

string strsendmessage;  //发送数据的报文
string strrecvmessage;  //接收数据的报文

void biz001(); //登录
void biz002(); //查询余额
void biz003(); //转账

int main(int argc, char *argv[])
{
    if(argc!=3)
    {
        printf("using: ./server ip port\n");
        return -1;
    }
    if(tcpclient.Connect(atoi(argv[2]),argv[1]) == false)
    {
        printf("Connect failed.\n");
    }
    biz001(); //登录
    biz002(); //查询余额
    biz003(); //转账
    biz002(); //查询余额
    return 0;
}

void biz001()
{
    strsendmessage="<bizid>1</bizid><username>test</username><password>123456</password>";
    if(tcpclient.Write(strsendmessage) == false)
    {
        printf("Write failed.\n");
        return;
    }
    printf("Send message:\n%s\n",strsendmessage.c_str());
    if(tcpclient.Read(strrecvmessage)== false)
    {
        printf("Read failed.\n");
        return;
    }
    printf("Recv message:\n%s\n",strrecvmessage.c_str());
    if(strrecvmessage.find("<error>0</error>") != -1)
    {
        printf("Login failed.\n");
        return;
    }
    printf("Login success.\n");
}

void biz002()
{
    strsendmessage="<bizid>2</bizid>";
    if(tcpclient.Write(strsendmessage) == false)
    {
        printf("Write failed.\n");
        return;
    }
    if(tcpclient.Read(strrecvmessage)== false)
    {
        printf("Read failed.\n");
        return;
    }
    printf("Recv message:\n%s\n",strrecvmessage.c_str());
    if(strrecvmessage.find("<retcode>-1</retcode>") == 1)
    {
        printf("Query failed.\n");
        return;
    }
    int query;
    getxmlbuffer(strrecvmessage,"query",query);
    printf("Query success,query=%d\n",query);
}

void biz003()
{
    strsendmessage="<bizid>3</bizid><query>100</query>";
    if(tcpclient.Write(strsendmessage) == false)
    {
        printf("Write failed.\n");
        return;
    }
    if(tcpclient.Read(strrecvmessage)== false)
    {
        printf("Read failed.\n");
        return;
    }
    printf("Recv message:\n%s\n",strrecvmessage.c_str());
    if(strrecvmessage.find("<retcode>-1</retcode>") == 1)
    {
        printf("Query failed.\n");
        return;
    }
    printf("Query success.\n");
}

有关于tcpclienttcpserver的封装可以参考我之前的文章:
c++实战篇(三) ——对socket通讯服务端与客户端的封装

现在如果我们想实现tcp的长连接就需要实现发送心跳报文,心跳报文的实现流程如下:

  • 我们设置心跳的超时时间
  • 添加发送报文的函数以及对对应报文的接收与处理函数

具体的代码实现如下:

//客户端
    #include "../public/_public.h"

using namespace idc;

ctcpclient tcpclient;

string strsendmessage;  //发送数据的报文
string strrecvmessage;  //接收数据的报文
int timeout;

void biz000(); //发送心跳报文
void biz001(); //登录
void biz002(); //查询余额
void biz003(); //转账

int main(int argc, char *argv[])
{
    if(argc!=4)
    {
        printf("using: ./server ip port timeout\n");
        return -1;
    }
    timeout = atoi(argv[3]);
    if(tcpclient.Connect(atoi(argv[2]),argv[1]) == false)
    {
        printf("Connect failed.\n");
    }
    biz001(); //登录
    biz002(); //查询余额
    sleep(10);
    biz000(); //发送心跳报文
    biz003(); //转账
    biz002(); //查询余额
    return 0;
}

void biz000()
{
    strsendmessage="<bizid>0</bizid>";
    if(tcpclient.Write(strsendmessage) == false)
    {
        printf("Write failed.\n");
        return;
    }
    printf("Send message:\n%s\n",strsendmessage.c_str());
    if(tcpclient.Read(&strrecvmessage,timeout) == false)
    {
        printf("Read failed.\n");
        return;
    }
    printf("Recv message:\n%s\n",strrecvmessage.c_str());
}

void biz001()
{
    strsendmessage="<bizid>1</bizid><username>test</username><password>123456</password>";
    if(tcpclient.Write(strsendmessage) == false)
    {
        printf("Write failed.\n");
        return;
    }
    printf("Send message:\n%s\n",strsendmessage.c_str());
    if(tcpclient.Read(strrecvmessage,timeout) == false)
    {
        printf("Read failed.\n");
        return;
    }
    printf("Recv message:\n%s\n",strrecvmessage.c_str());
    if(strrecvmessage.find("<error>0</error>") != -1)
    {
        printf("Login failed.\n");
        return;
    }
    printf("Login success.\n");
}

void biz002()
{
    strsendmessage="<bizid>2</bizid>";
    if(tcpclient.Write(strsendmessage) == false)
    {
        printf("Write failed.\n");
        return;
    }
    if(tcpclient.Read(strrecvmessage,timeout) == false)
    {
        printf("Read failed.\n");
        return;
    }
    printf("Recv message:\n%s\n",strrecvmessage.c_str());
    if(strrecvmessage.find("<retcode>-1</retcode>") == 1)
    {
        printf("Query failed.\n");
        return;
    }
    int query;
    getxmlbuffer(strrecvmessage,"query",query);
    printf("Query success,query=%d\n",query);
}

void biz003()
{
    strsendmessage="<bizid>3</bizid><query>100</query>";
    if(tcpclient.Write(strsendmessage) == false)
    {
        printf("Write failed.\n");
        return;
    }
    if(tcpclient.Read(strrecvmessage,timeout) == false)
    {
        printf("Read failed.\n");
        return;
    }
    printf("Recv message:\n%s\n",strrecvmessage.c_str());
    if(strrecvmessage.find("<retcode>-1</retcode>") == 1)
    {
        printf("Query failed.\n");
        return;
    }
    printf("Query success.\n");
}
//服务端
#include "../public/_public.h"
using namespace idc;

ctcpserver tcpserver;  // 创建服务端对象。
clogfile logfile;            // 服务程序的运行日志。

void FathEXIT(int sig);  // 父进程退出函数。
void ChldEXIT(int sig);  // 子进程退出函数。

string strsendbuffer;   // 发送报文的buffer。
string strrecvbuffer;    // 接收报文的buffer。
int total=1000; //设置的初始余额
int timeout;

bool bizmain();    // 业务处理主函数。

int main(int argc,char *argv[])
{
    if (argc!=4)
    {
      printf("Using:./demo04 port logfile timeout\n");
      printf("Example:./demo04 5005 /log/idc/demo04.log 30\n\n"); 
      return -1;
    }

    timeout=atoi(argv[3]);

    // 关闭全部的信号和输入输出。
    // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
    // 但请不要用 "kill -9 +进程号" 强行终止
    //closeioandsignal(false); 
    signal(SIGINT,FathEXIT); signal(SIGTERM,FathEXIT);

    if (logfile.open(argv[2])==false) { printf("logfile.open(%s) failed.\n",argv[2]); return -1; }

    // 服务端初始化。
    if (tcpserver.Initserver(atoi(argv[1]))==false)
    {
      logfile.write("tcpserver.initserver(%s) failed.\n",argv[1]); return -1;
    }

    while (true)
    {
        // 获取客户端的连接请求。
        if (tcpserver.Accept()==false)
        {
            logfile.write("tcpserver.accept() failed.\n"); FathEXIT(-1);
        }

        logfile.write("客户端(%s)已连接。\n",tcpserver.getclientip());

        if (fork()>0) { tcpserver.Closeconn(); continue; }  // 父进程继续回到accept()。
   
        // 子进程重新设置退出信号。
        signal(SIGINT,ChldEXIT); signal(SIGTERM,ChldEXIT);

        tcpserver.Closelisten();     // 子进程关闭监听的socket。

        while (true)
        {
            // 子进程与客户端进行通讯,处理业务。
            if (tcpserver.Read(strrecvbuffer,timeout)==false)
            {
                printf("%d",tcpserver.m_connsock);
                perror("tcpserver.read()");
                logfile.write("tcpserver.read() failed.\n"); ChldEXIT(0);
            }
            logfile.write("接收:%s\n",strrecvbuffer.c_str());

            bizmain();    // 业务处理主函数。

            if (tcpserver.Write(strsendbuffer)==false)
            {
                logfile.write("tcpserver.send() failed.\n"); ChldEXIT(0);
            }
            logfile.write("发送:%s\n",strsendbuffer.c_str());
        }

        ChldEXIT(0);
    }
}

// 父进程退出函数。
void FathEXIT(int sig)  
{
    // 以下代码是为了防止信号处理函数在执行的过程中被信号中断。
    signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);

    logfile.write("父进程退出,sig=%d。\n",sig);

    tcpserver.Closelisten();    // 关闭监听的socket。

    kill(0,15);     // 通知全部的子进程退出。

    exit(0);
}

// 子进程退出函数。
void ChldEXIT(int sig)  
{
    // 以下代码是为了防止信号处理函数在执行的过程中被信号中断。
    signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);

    logfile.write("子进程退出,sig=%d。\n",sig);

    tcpserver.Closeconn();    // 关闭客户端的socket。

    exit(0);
}

void biz001();   // 登录。
void biz002();   // 查询余额。
void biz003();   // 转帐。

bool bizmain()    // 业务处理主函数。
{
    int bizid;  // 业务代码。
    getxmlbuffer(strrecvbuffer,"bizid",bizid);

    switch(bizid)
    {
        case 0:
            strsendbuffer="<retcode>0</retcode>";
            break;
        case 1:    // 登录。
            biz001();
            break;
        case 2:    // 查询余额。
            biz002();
            break;
        case 3:    // 转帐。
            biz003();
            break;
        default:   // 非法报文。
            strsendbuffer="<retcode>9</retcode><message>业务不存在。</message>";
            break;
    }

    return true;
}

void biz001()   // 登录。
{
    string username,password;
    getxmlbuffer(strrecvbuffer,"username",username);
    getxmlbuffer(strrecvbuffer,"password",password);
    logfile.write("用户名:%s,密码:%s。\n",username.c_str(),password.c_str());

    if ( (username=="test") && (password=="123456") )
        strsendbuffer="<retcode>0</retcode><message>成功。</message>";
    else
        strsendbuffer="<retcode>-1</retcode><message>用户名或密码不正确。</message>";
}

void biz002()   // 查询余额。
{
    strsendbuffer=sformat("<retcode>0</retcode><message>成功</message><query>%d</query>",total);
}

void biz003()   // 转帐。
{
    int query;
    getxmlbuffer(strrecvbuffer,"query",query);
    logfile.write("转帐金额:%d。\n",query);
    total+=query;
    strsendbuffer="<retcode>0</retcode><message>成功。</message>";
}

这样我们就实现一个简单的可以依托于心跳报文来实现tcp长连接的客户端与服务端了,我们这里只要一直能够超时时间前发送心跳报文给服务端,服务端就课题一致运行下去。

tcp长连接的优点与应用场景

1.减少连接建立和断开的开销
长连接减少了频繁建立和断开连接带来的额外开销,如三次握手和四次挥手过程的时间消耗和资源消耗。
2.提高通信效率
由于连接已经建立好,因此后续的数据传输可以更快地开始,从而提高了数据传输的效率。
3.支持连续的数据流
长连接非常适合需要连续发送数据的应用场景,如视频流媒体传输、实时聊天应用等。

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

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

相关文章

用uniapp 及socket.io做一个简单聊天app 4

界面如下&#xff1a; <template><view class"container"><input v-model"username" placeholder"用户名" /><input v-model"password" type"password" placeholder"密码" /><butto…

探秘北京崇文门中医医院卫景沛医生:为何深受患者信赖?

卫景沛是北京崇文门中医医院特聘专家&#xff0c;深受患者信赖&#xff01; 北京崇文门中医医院卫景沛主任毕业于兰州大学医学院&#xff0c;拥有医学硕士学位。他的硕士导师是天坛医院的王拥军教授&#xff0c;主要研究方向为脑血管病及脑血管病介入治疗。 为了提升自己在缺血…

对称加密:数据安全的保障

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Qt对象树的介绍

目录 创建项目&#xff08;此处我就不多介绍了&#xff09; 按钮 对象树 创建项目&#xff08;此处我就不多介绍了&#xff09; QMainWidow带菜单栏的 QWidget空白的 QDialog对话框 创建功能时注意&#xff1a; 项目工程名称一般不要有标点&#xff0c;不要带中文 按钮 /…

计算机基础(Windows 10+Office 2016)教程 —— 第8章 多媒体技术及应用

多媒体技术及应用 8.1 多媒体技术的概述8.1.1 多媒体技术的定义和特点8.1.2  多媒体的关键技术8.1.3 多媒体技术的发展趋势8.1.4 多媒体文件格式的转换8.1.5 多媒体技术的应用 8.2 多媒体计算机系统的构成8.2.1 多媒体计算机系统的硬件系统8.2.2 多媒体计算机系统的软件系统…

Python教程(十一):单元测试与异常捕获

目录 专栏列表前言一、Python中的测试1.1 单元测试1.1.1 定义测试类1.2.1 安装 pytest1.2.2 编写测试1.2.3 运行测试 二、Python中的异常捕获2.1 常规代码2.2 异常基础 三、抛出异常&#xff08;异常传播&#xff09;四、 自定义异常 专栏列表 Python教程&#xff08;一&#…

赛蓝企业管理系统 AuthToken/Index 身份认证绕过漏洞复现

0x01 产品简介 赛蓝企业管理系统是一款为企业提供全面管理解决方案的软件系统&#xff0c;它能够帮助企业实现精细化管理&#xff0c;提高效率&#xff0c;降低成本。系统集成了多种管理功能&#xff0c;包括但不限于项目管理、财务管理、采购管理、销售管理以及报表分析等&am…

【WPF开发】如何将工程打包成单独的EXE安装包

一、安装NSIS与HM NIS Edit 1、下载和安装NSIS NSIS官网 2、下载和安装HM NIS Edit HM NIS Edit官网 点击下载后等待几秒&#xff0c;就会弹出下载提示 双击下载的安装包&#xff0c;点击“OK” 点击“下一步” 点击“我接受” 更改路径后&#xff0c;点击安装即可 二、打包软…

SpringSecurity-1(认证和授权+SpringSecurity入门案例+自定义认证+数据库认证)

SpringSecurity 1 初识权限管理1.1 权限管理的概念1.2 权限管理的三个对象1.3 什么是SpringSecurity 2 SpringSecurity第一个入门程序2.1 SpringSecurity需要的依赖2.2 创建web工程2.2.1 使用maven构建web项目2.2.2 配置web.xml2.2.3 创建springSecurity.xml2.2.4 加载springSe…

【leetcode详解】寻找两个正序数组的中位数:最简单的【困难】题?

简评&#xff1a; 可以说&#xff0c;要做出来这道题&#xff0c;实际上是非常简单的 //这也是笔者目前唯一解出来的唯一一道【困难】题哈哈哈哈 思路解析&#xff1a; 将两个向量合并 class Solution { public:double findMedianSortedArrays(vector<int>& nums1…

c# MetroForm 和 IntPtr unsafe

一、NuGet安装框架 修改代码 效果&#xff1a; 结果&#xff1a; TopLevel与TopMost属性 frm.TopLevel false; //Form.TopLevel 获取或设置一个值&#xff0c;该值指示是否将窗体显示为顶级窗口。frm.TopMost false; //Form.TopMost 获取或设置一个值&#xff0c;指示该窗体…

嵌入式人工智能(43-基于树莓派4B的刷卡模块射频识别RFID-RC522)

1、RFID 射频识别&#xff08;RFID&#xff0c;Radio Frequency Identification&#xff09;是一种无线通信技术&#xff0c;用于自动识别和追踪标签上的信息。这项技术基于射频信号的传输和接收&#xff0c;通过将标签上的数据存储在特定的芯片中&#xff0c;实现物体的识别和…

上市公司绿色信息披露质量评分数据(2008-2023年)

数据来源&#xff1a;基础数据来源于上市公司年报/社会责任报告/环境报告以及ZJ会及统计局 时间跨度&#xff1a;2008-2023年 数据范围&#xff1a;企业及行业层面 数据指标&#xff1a; 按照是否货币化分类企业对于环境信息的披露:对于货币化的信息&#xff0c;定量和定性…

共襄恰青赛马节盛事 共享农业产业园成果

恰青赛马节是那曲一年一度的草原盛事&#xff0c;是藏北规模盛大的传统节日&#xff0c;承载着那曲悠久的文化底蕴&#xff0c;体现了藏北各族群众丰富的传统习俗&#xff0c;更是深受民众欢迎。今年的赛马节上&#xff0c;色尼区国家现代农业产业园紧抓机遇&#xff0c;设置农…

Android设备发送蓝牙文件到电脑笔记本失败解决

Android设备发送蓝牙文件到电脑笔记本失败解决 文章目录 Android设备发送蓝牙文件到电脑笔记本失败解决一、前言二、解决1、比较旧的电脑2、大部分新的电脑 三、其他1、发送蓝牙文件到Window电脑端小结2、可传输的蓝牙文件的文件类型 一、前言 普通手机之间蓝牙配对后&#xf…

软件测试开发

软件测试的职业发展 起点&#xff1a;功能测试 走管理 业务专家行业业务专家行业业务发展专家 走技术 测试开发资深测试开发测试架构师/全栈测试工程师 软件开发模型 瀑布模型 V模型和W模型 W模型和V模型都把软件的开发视为需求&#xff0c;设计&#xff0c;编码&#x…

SolarMarker 正在使用水坑攻击与伪造的 Chrome 浏览器更新进行攻击

在过去的三个月里&#xff0c;eSentire 的安全研究团队发现信息窃密恶意软件 SolarMarker 都没有发动攻击&#xff0c;却在最近忽然重返舞台。此前&#xff0c;SolarMarker 的运营者使用 SEO 投毒或者垃圾邮件来引诱受害者&#xff0c;受害者试图下载一些文档的免费模板&#x…

非对称加密:数据安全的双重保障

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

每日OJ_牛客HJ73 计算日期到天数转换

目录 牛客HJ73 计算日期到天数转换 解析代码 牛客HJ73 计算日期到天数转换 计算日期到天数转换_牛客题霸_牛客网 解析代码 用一个数组存放每月的累积天数输入的日期天数 当月的天数 当月之前的累积天数&#xff0c;如果包含二月&#xff0c;再去判断是否为闰年&#xff0c;…

听专家的,不如听国家的,网络安全究竟值不值得报?

考学选专业&#xff0c;或者跳槽选行业的&#xff0c;看这篇&#xff01; 如果你什么都不懂&#xff0c;家里也没有矿&#xff0c;那就紧跟国家大事和地方政策。 关于网络安全专业究竟是否值得报考? 要知道“二十大”、“十四五”等大会一直在提一个词叫做“数字中国建设”…