【应用层协议】自定义协议 {定义结构化数据;数据格式转换:序列化和反序列化,使用json库进行数据格式交换;分包和解包:为报文内容添加报头}

一、简单了解TCP协议(引子)

在这里插入图片描述

1.1 三次握手

三次握手就是客户端向服务端发起连接的过程

在这里插入图片描述

服务器初始化

  1. 调用socket,创建套接字文件

  2. 调用bind,将当前的文件描述符和ip/port绑定在一起;如果这个端口已经被其他进程占用了,就会bind失败

  3. 调用listen,将套接字文件设置为监听状态,为后面的accept做好准备

  4. 调用accept,阻塞等待客户端的链接请求

客户端发起连接

  1. 调用socket,创建套接字文件

  2. 调用connect,向服务器发起连接请求:

    • connect会发出SYN并阻塞等待服务器应答 (第一次)

    • 服务器收到客户端的SYN,会应答一个SYN+ACK段表示"同意建立连接" (第二次)

    • 客户端收到SYN+ACK后会从connect返回,同时应答一个ACK段 (第三次)

    • 最后服务端收到ACK段后从accept返回,建立连接成功。

这个建立连接的过程, 通常称为“三次握手”


1.2 数据传输

双方建立好连接之后就可以进行数据传输了

在这里插入图片描述

TCP协议提供全双工通信服务

在这里插入图片描述

  • tcp是全双工通信,因为每个链接(每个套接字文件)都有对应的发送和接收缓冲区,收发可以并发执行(但是多线程单独收或发必须串行执行)
  • read, write只负责应用层和传输层缓冲区之间数据的拷贝,而传输层缓冲区和网络之间数据的收发(发多少,何时发)则完全是由tcp协议负责,所以TCP协议称为传输控制协议。

TCP是面向字节流协议

  • 当tcp在传输层发送消息时,一个tcp报文可能会被分割成多条消息转发给网络层。我们不能认为一条消息就是一个tcp报文,所以tcp是面向字节流的协议
  • 由于一条消息对应的不是一个tcp报文,如果接受方不知道一个tcp报文的长度或者分割的边界在哪里,就会无法组装成一个单独完整的报文,这就形成了粘包问题
  • TCP协议只负责适时收发缓冲区中的数据,至于数据包是否完整(可能只发送了一部分),是否是单独的数据包(可能一次读到多份数据),数据包内容的解析等则需要由应用层协议负责,这些内容都会在后面的自定义协议(应用层)当中体现。

1.3 四次挥手

如果不想通信了,双方就要断开连接

在这里插入图片描述

断开连接的过程

  1. 如果客户端没有更多的请求了,就调用close关闭连接, 客户端会向服务器发送FIN段 (第一次)

  2. 此时服务器收到FIN后, 会回应一个ACK,同时read会返回0 (第二次)

  3. read返回之后,服务器就知道客户端关闭了连接,也调用close关闭连接,这个时候服务器会向客户端发送一个FIN (第三次)

  4. 客户端收到FIN,再返回一个ACK给服务器 (第四次)

这个断开连接的过程, 通常称为 ”四次挥手“


二、传输结构化数据

2.1 当前存在的问题

在之前的socket编程当中, 读写数据时都是按 “字符串” 的方式来发送接收的,如果我们要传输一些"结构化的数据" 怎么办呢?

什么是结构化的数据?

比如QQ聊天,就不能单纯发送消息过去,还要把头像url、时间昵称等打包形成一个报文,把这个报文的数据一起发送给对方,这个打包形成的报文就是一个结构化的数据

能不能将将结构体直接发送给对方呢?

不可以,不同的编程语言和编译环境对同一个结构体的描述可能不同,如内存对齐规则和数据的大小端模式等。如果选择直接收发二进制形式的结构体数据,可能会导致数据读写不一致的问题。

如何保证读取到完整、单独的数据?

在前面TCP协议的数据传输部分,我们就提到了这个问题,解决该问题的方法就是明确报文的大小和边界:

  1. 对报文进行定长
  2. 用特殊符号划分每条报文
  3. 自描述方式

2.2 序列化和反序列化

什么是序列化和反序列化?

  • 序列化是将数据结构或对象转换为字节流的过程。在序列化过程中,对象的状态信息被转换为字节序列,可以将其存储在文件中或通过网络传输
  • 反序列化是将字节流或其他存储形式转换回数据结构或对象的过程。在反序列化过程中,字节序列被重新转换为对象的状态信息,以便可以重新创建对象并使用其数据

序列化和反序列化的目的

  • 数据持久化:通过序列化,可以将对象的状态保存到文件或数据库中,以便在程序重新启动或重新加载时可以从中恢复对象的状态
  • 数据传输:通过序列化,可以将对象转换为字节流,以便在网络传输中进行传递
  • 跨平台和跨语言交互:通过序列化,可以将对象转换为通用的字节流格式,使得不同平台和不同编程语言之间可以进行数据交换和共享。无论是Java、Python、C++还是其他编程语言,只要能够进行序列化和反序列化操作,就可以实现跨平台和跨语言的数据交互

发送报文到网络时候,报文首先需要进行序列化,然后再发送,报文通过协议栈发送给对方后,接收报文的一方也需要对报文进行反序列化,才能正常使用该报文

在这里插入图片描述


2.3 为报文内容添加报头

我们将序列化后的结构化数据称为报文内容,为了保证能够读取到完整、单独的数据,我们需要:

  1. 用特殊符号划分每条报文:在报文和报文之间添加换行符进行划分(方便后期调试,也可以用其他符号)
  2. 自描述:为报文内容添加报头,其中可以包含协议类型、报文内容的长度等信息。

报头中包含协议类型:可以使用多态技术将应用层协议拓展出多种数据类型以适应各种应用需求。此时就需要报头中包含协议类型,使通信双方使用同一子类型协议。

报头中包含报文内容的长度:保证读取到完整、单独的数据

对比UDP协议

  • UDP协议的特点是面向数据包传输,系统会自动进行数据的格式处理,将数据和数据分开。发送几次就需要接收几次。
  • TCP协议的特点是面向字节流传输,系统不会对数据进行分包,需要用户自己在应用层定制协议进行分包和解包操作。

三、网络计算器程序

3.1 封装socket API

首先我们要对socket API进行封装,方便后续客户端和服务端使用socket API进行网络通信。

#pragma once#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include "logmessage.hpp"class Socket
{int _sockfd;static const int s_backlog = 20;public:Socket(){_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd == -1){LogMessage(FATAL, "socket(%d): %s", errno, strerror(errno));exit(errno);}LogMessage(DEBUG, "create socket success!");}void Bind(const std::string &ip, uint16_t port){sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = inet_addr(ip.c_str());if (bind(_sockfd, (sockaddr *)&local, sizeof(local)) == -1){LogMessage(FATAL, "bind(%d): %s", errno, strerror(errno));exit(errno);}LogMessage(DEBUG, "bind socket success!");}void Listen(){if (listen(_sockfd, s_backlog)){LogMessage(FATAL, "listen(%d): %s", errno, strerror(errno));exit(errno);}LogMessage(DEBUG, "listen socket success!");}int Accept(std::string *ip, uint16_t *port){sockaddr_in src;socklen_t len;int svcsock = accept(_sockfd, (sockaddr *)&src, &len);if (svcsock == -1){LogMessage(ERROR, "accept(%d): %s", errno, strerror(errno));return -1;}if (ip != nullptr)*ip = inet_ntoa(src.sin_addr);if (port != nullptr)*port = ntohs(src.sin_port);return svcsock;}void Connect(const std::string &ip, uint16_t port){sockaddr_in dst;memset(&dst, 0, sizeof(dst));dst.sin_family = AF_INET;dst.sin_port = htons(port);dst.sin_addr.s_addr = inet_addr(ip.c_str());if (connect(_sockfd, (sockaddr *)&dst, sizeof(dst)) == -1){LogMessage(FATAL, "connect(%d): %s", errno, strerror(errno));exit(errno);}}int getsock(){return _sockfd;}~Socket(){if (_sockfd >= 0){close(_sockfd);}}
};

3.2 自定义协议

定制的协议,必须保证通信双方(客户端、服务端)能够遵守协议的约定。

应用层协议需要解决的问题:

  1. 定义结构化数据:定义通信双方都能够解释的结构化数据,数据可以分为请求数据(request)和响应数据(response),因此我们分别需要对请求数据和响应数据进行约定,可以用两个结构体进行封装数据的请求和响应
  2. 数据格式转换:序列化和反序列化,正确的传输结构化数据
  3. 分包和解包:为报文内容添加报头,保证读取到完整、单独的数据

提示:客户端和服务端要包含同一份协议代码,这样才能保证双方使用的是同一个协议。

protocol.hpp

#pragma once
#include <sstream>#define SEP "|"
#define SPACE " "struct Request
{int _l;int _r;char _op;Request() {}Request(int l, int r, char op): _l(l), _r(r), _op(op){}//序列化std::string Serialize(){std::stringstream oss;oss << _l << SPACE << _op << SPACE << _r;return oss.str();}//反序列化bool Deserialize(const std::string &str){std::size_t left = str.find(SPACE);if (left == std::string::npos)return false;std::size_t right = str.rfind(SPACE);if (right == std::string::npos)return false;_l = std::stoi(str.substr(0, left));_r = std::stoi(str.substr(right + strlen(SPACE)));if (left + strlen(SPACE) > str.size())return false;else_op = str[left + strlen(SPACE)];return true;}
};struct Response
{int _ret;int _code;Response() {}Response(int ret, int code): _ret(ret), _code(code){}//序列化std::string Serialize(){std::stringstream oss;oss << _ret << SPACE << _code;return oss.str();}//反序列化bool Deserialize(const std::string &str){std::size_t pos = str.find(SPACE);if (pos == std::string::npos)return false;_ret = std::stoi(str.substr(0, pos));_code = std::stoi(str.substr(pos + strlen(SPACE)));return true;}
};//报头的添加和解析是以上两个结构通用的,所以定义成全局函数。
//解析报头
std::string Decode(std::string &buffer)
{size_t pos = buffer.find(SEP);if (pos == std::string::npos)return "";int len = std::stoi(buffer.substr(0, pos)); //报头中的报文内容长度int content_len = buffer.size() - pos - 2 * strlen(SEP); //减去报头和2个sep的剩余长度if (content_len >= len){//报文完整,获取报文内容,将完成解析的报文从缓冲区中去除buffer.erase(0, pos+strlen(SEP));std::string package;package = buffer.substr(0, len);buffer.erase(0, len+strlen(SEP));return package;}else{//报文不完整,返回空串return ""; }
}//添加报头和报文间分隔符
std::string Encode(const std::string &str)
{std::stringstream oss;oss << std::to_string(str.size()) << SEP << str << SEP;return oss.str();
}//也封装以下读写函数
bool Receive(int sock, std::string *out)
{char buffer[1024];ssize_t s = recv(sock, buffer, sizeof(buffer), 0);if (s > 0){buffer[s] = 0;*out += buffer;return true;}else{return false;}
}void Send(int sock, const std::string &str)
{send(sock, str.c_str(), str.size(), 0);
}

3.2.1 send、recv函数

send函数,用于TCP发送数据

函数原型:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数说明:

  • sockfd:特定的文件描述符,表示将数据写入该文件描述符对应的套接字。
  • buf:需要发送的数据。
  • len:需要发送数据的字节个数。
  • flags:发送的方式,一般设置为0,表示阻塞式发送。

返回值说明:

  • 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置。

提示:该函数与write函数功能一致

recv函数,用于TCP接收数据

函数原型:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数说明:

  • sockfd:特定的文件描述符,表示从该文件描述符中读取数据。
  • buf:数据的存储位置,表示将读取到的数据存储到该位置。
  • len:数据的个数,表示从该文件描述符中读取数据的字节数。
  • flags:读取的方式,一般设置为0,表示阻塞式读取。

返回值说明:

  • 如果返回值大于0,则表示本次实际读取到的字节个数。
  • 如果返回值等于0,则表示对端已经把连接关闭了。
  • 如果返回值小于0,则表示读取时遇到了错误。

提示:该函数的功能与read一致


3.2.2 使用json库进行数据格式交换

我们一般不用自己编写序列和反序列化,因为有现成的库支持像这样的数据格式交换。

常见的序列化和反序列化的库:

  • json

  • protobuf

  • xml

其中,json 是简单易上手的,C++、Java,Python等都支持,protobuf 和 json 是C++常用的

安装json库

yum install -y jsoncpp-devel

注意:普通用户需要 sudo 提权

安装完成,在系统默认路径下查看头文件和库文件

img

使用json需要包含头文件

#include <jsoncpp/json/json.h>

编译时需要指明链接jsoncpp库

img

使用json库进行数据格式交换

序列化

  1. 定义Json::Value对象,可以嵌套定义
  2. 使用value["x"] = n的方式添加键值对
  3. 定义Json::StyledWriter(序列化结果方便查看)或Json::FastWriter对象(常用于数据传输),两者唯一的区别就是序列化字符串的格式不同。
  4. 调用writer.write(value)成员函数返回Json::Value对象的序列化结果(类型string)

反序列化

  1. 定义Json::Value对象
  2. 定义Json::Reader对象
  3. 调用reader.parse(str,value)函数反序列化,将键值对填充到Json::Value对象中
  4. 取用键值对,使用value["x"].asInt()的方法按类型将value取出。
#pragma once
#include <sstream>
#include <jsoncpp/json/json.h>#define SEP "|"
#define SPACE " "struct Request
{int _l;int _r;char _op;Request() {}Request(int l, int r, char op): _l(l), _r(r), _op(op){}//序列化std::string Serialize(){//定义万能对象Json::ValueJson::Value root; //填充对象 key-valueroot["l"] = _l;root["r"] = _r;root["op"] = _op;//定义序列化对象Json::FastWriter或StyledWriterJson::FastWriter writer;//调用Json::FastWriter::write将Value对象序列化return writer.write(root);}//反序列化bool Deserialize(const std::string &str){//定义万能对象Json::ValueJson::Value root;//定义反序列化对象Json::ReaderJson::Reader reader;//调用Json::Reader::parse将字节流反序列化reader.parse(str, root);//按类型取出Value对象中的数据_l = root["l"].asInt();_r = root["r"].asInt();_op = root["op"].asInt();return true;}
};

3.3 服务端代码

tcp_server.hpp

#pragma once#include <string>
#include <vector>
#include <pthread.h>
#include <cstdio>
#include <functional>
#include "mysocket.hpp"
#include "protocol.hpp"
#include "logmessage.hpp"class TcpServer
{using func_t = std::function<void(int)>;std::string _ip;uint16_t _port;Socket _listensock;std::vector<func_t> _funcs; //任务队列,可以提供多项服务struct ThreadData{int _sock;TcpServer *_self;ThreadData(int sock, TcpServer *self): _sock(sock), _self(self){}};public:TcpServer(const std::string &ip, uint16_t port): _ip(ip), _port(port){_listensock.Bind(_ip, _port);_listensock.Listen();}void AddService(func_t func){_funcs.push_back(func);}void Excute(int sock){for (auto f : _funcs){f(sock);}}void Start(){while (true){// 1.获取新链接std::string client_ip;uint16_t client_port;int svcsock = _listensock.Accept(&client_ip, &client_port);if (svcsock == -1)continue;LogMessage(NORMAL, "[%s:%d] join, accept a new link!", client_ip.c_str(), client_port);// 2.创建新线程为连接提供服务pthread_t tid;ThreadData *ptd = new ThreadData(svcsock, this);pthread_create(&tid, nullptr, ThreadRoutine, ptd);}}static void *ThreadRoutine(void *args){pthread_detach(pthread_self());// ThreadData *td = (ThreadData *)args;ThreadData *td = static_cast<ThreadData *>(args);td->_self->Excute(td->_sock); //执行任务队列中的任务LogMessage(NORMAL, "client quit!");close(td->_sock);delete td;}
};

tcp_server.cc

#include <memory>
#include <signal.h>
#include <iostream>
#include "tcp_server.hpp"
#include <unistd.h>void Usage(const char *proc)
{printf("\nUsage: %s port\n", proc);
}Response calchelper(const Request &req)
{Response resp(0, 0);switch (req._op){case '+':resp._ret = req._l + req._r;break;case '-':resp._ret = req._l - req._r;break;case '*':resp._ret = req._l * req._r;break;case '/':if (req._r == 0)resp._code = 1;elseresp._ret = req._l / req._r;break;case '%':if (req._r == 0)resp._code = 2;elseresp._ret = req._l % req._r;break;default:resp._code = 3;break;}return resp;
}void *calculator(int sock)
{std::string inbuffer; //应用层缓冲区while (true){// 1.读取成功bool ret = Receive(sock, &inbuffer);if (!ret)break;// std::cout << inbuffer << std::endl;// 2.协议解析,得到一个完整的报文std::string package = Decode(inbuffer);if (package.empty()) //报文不完成,继续读取continue;// std::cout << package << std::endl;// std::cout << inbuffer << std::endl;LogMessage(NORMAL, "%s", package.c_str());// 3.反序列化,字节流->结构化数据Request req;req.Deserialize(package);// 4.业务逻辑Response resp = calchelper(req);// 5.序列化业务的处理结果std::string respstr = resp.Serialize();// std::cout << respstr << std::endl;// 6.添加报头(长度信息),形成一个完成的报文respstr = Encode(respstr);// std::cout << respstr << std::endl;Send(sock, respstr);}
}int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(1);}uint16_t port = atoi(argv[1]);// server在编写时要有较为严谨的判断逻辑// 一般服务器是要忽略SIGPIPE信号的,防止在运行过程中出现非法写入的问题std::unique_ptr<TcpServer> psvr(new TcpServer("0.0.0.0", port));psvr->AddService(calculator);daemon(0,0); //守护进程化psvr->Start();return 0;
}

3.4 客户端代码

tcp_client.hpp

#pragma once#include <iostream>
#include <string>
#include "mysocket.hpp"
#include "protocol.hpp"
#include "logmessage.hpp"class TcpClient
{Socket _sockfd;std::string _svrip;uint16_t _svrport;public:TcpClient(const std::string &ip, uint16_t port): _svrip(ip), _svrport(port){_sockfd.Connect(ip, port);}void Run(){std::string inbuffer; //应用层缓冲区bool quit = false;while (!quit){// 1.获取需求Request req;std::cin >> req._l >> req._op >> req._r;// 2.序列化std::string reqstr = req.Serialize();std::string temp = reqstr;// 3.添加长度报头reqstr = Encode(reqstr);// 4.向服务端发送数据Send(_sockfd.getsock(), reqstr);while (true){// 5.从服务端接收数据bool ret = Receive(_sockfd.getsock(), &inbuffer);if (!ret){quit = true;break;}// 6.获取一个完成的报文std::string respstr = Decode(inbuffer);if (respstr.empty()) //报文不完整,继续接收continue;// 7.反序列化Response resp;resp.Deserialize(respstr);// 8.输出结果// std::cout << "ret: " << resp._ret << std::endl;// std::cout << "code: " << resp._code << std::endl;std::string err;switch (resp._code){case 1:err = "除0错误";break;case 2:err = "模0错误";break;case 3:err = "非法操作";break;default:std::cout << temp << " = " << resp._ret << " [success]" << std::endl;break;}if (!err.empty())std::cerr << err << std::endl;break;}}}
};

tcp_client.cc

#include "tcp_client.hpp"
#include <memory>
#include <string>void Usage(const char *proc)
{printf("\nUsage: %s svrip svrport\n", proc);
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string svrip = argv[1];uint16_t svrport = atoi(argv[2]);std::unique_ptr<TcpClient> pcli(new TcpClient(svrip, svrport));pcli->Run();return 0;
}

四、重谈协议分层

在这里插入图片描述
我们设计的网络服务器自然而然就完成了OSI七层模型中的上三层功能:

  1. 应用层:解释结构体中的每个字段,并完成了网络计算器的服务
  2. 表示层:定义固有的结构化数据,并通过序列化和反序列化完成了数据格式的转换,通过添加报头保证了报文的单独性和完整性。
  3. 会话层:服务器接收到新链接后通过创建新线程为每个链接提供服务,同时新线程也负责管理链接的断开

在这里插入图片描述

在TCP/IP五层模型中,将OSI模型中的上三层归为一层应用层。这是因为对于不同的网络服务上三层所对应的具体工作可能不同,操作系统不能统一设计一种协议适用于所有服务,因此将这三层归为一层交给用户自行实现或选择。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

外贸12年,通过6个方法,成交几千万订单

做外贸有12个年头了&#xff0c;各种各样的方法都有试过&#xff0c;我成交的这几千万订单&#xff0c;大部分都是通过这6个方法来的&#xff0c;下面我来给大家整理分享一下。 1.谷歌搜索 谷歌搜索算是做外贸入门级的基础技能了&#xff0c;要做好外贸&#xff0c;这个技能一…

86.小米相机修改拍照(尺寸,画幅,比例)的方法

目录 1.打开相机&#xff0c;拍照模式&#xff0c;上面有个箭头或三个点&#xff0c;点击 2.点击画幅 3.点击你想要的画幅即可。 想要修改手机照片的&#xff08;尺寸&#xff0c;画幅&#xff0c;比例&#xff09;时&#xff0c;总会去找分辨率&#xff0c;其实并不是&…

Leetcode - 周赛410

目录 一&#xff0c;3248. 矩阵中的蛇 二&#xff0c;3249. 统计好节点的数目 三&#xff0c;3250. 单调数组对的数目 I dfs记忆化 dfs记忆化1&#xff1a;1改递推 四&#xff0c;3251. 单调数组对的数目 II 一&#xff0c;3248. 矩阵中的蛇 本题就是一道纯模拟题&#x…

django高校毕业生就业推荐系统-计算机毕业设计源码26096

摘 要 当前就业市场竞争激烈&#xff0c;高校毕业生面临着就业难的问题&#xff0c;同时企业也面临招聘难、选人难的挑战。为了更好地对接高校毕业生和企业之间的需求&#xff0c;为毕业生提供个性化的就业求着信息&#xff0c;开发一套充分利用Django和Python技术实现的毕业生…

中科院TOP“灌水神刊”合集!年发文量动辄数千篇,TOP的地位,4区的录用率!

【SciencePub学术】本期&#xff0c;给大家推荐几本环境领域的“灌水神刊”&#xff01;均隶属于中科院TOP刊之列&#xff0c;但是每年庞大的发文量致使投稿接收率极高&#xff01;话不多说&#xff0c;想“灌水”的建议收藏&#xff01; 01 年刊文量4000 Journal of Cleaner …

【sgCreateAPIFunction】自定义小工具:敏捷开发→自动化生成API接口方法代码片段脚本(接口方法代码生成工具)

sgCreateAPIFunction源码 <template><!-- 前往https://blog.csdn.net/qq_37860634/article/details/141159084 查看使用说明 --><div :class"$options.name"><div class"sg-head">接口方法生成工具<el-dropdown:show-timeou…

Redis操作--RedisTemplate(一)介绍

一、介绍 1、简介 RedisTemplate 是 Spring Data Redis 提供的一个高级抽象&#xff0c;由 Spring 官方提供的方便操作 Redis 数据库的一个工具类&#xff0c;支持模板设计模式&#xff0c;使得操作 Redis 更加符合 Spring 的编程模型。还支持序列化机制&#xff0c;可以处理…

第二证券:虚拟现实概念强势,博士眼镜三连板,星星科技涨停

虚拟现实概念14日盘中再度走强&#xff0c;到发稿&#xff0c;硕贝德、博士眼镜、星星科技“20cm”涨停&#xff0c;亚世光电、亿道信息、卓翼科技亦涨停&#xff0c;佳禾智能涨超9%。 值得注意的是&#xff0c;博士眼镜已连续3个交易日涨停。公司昨日在出资者互动途径表示&am…

电脑开机后出现bootmgr is missing原因及解决方法

最近有网友问我为什么我电脑开机后出现bootmgr is missing&#xff0c;这个提示意思是:意思是启动管理器丢失&#xff0c;说明bootmgr损坏或者丢失&#xff0c;系统无法读取到这个必要的启动信息导致无法启动。原因有很多&#xff0c;比如我们采用的是uefi引导&#xff0c;而第…

离职保密协议是什么?怎么样才是合法的?如何维护公司权益?

“商贾之道&#xff0c;在于诚信&#xff1b;机密之重&#xff0c;犹胜千金。” 在历史的长河中&#xff0c;商业机密一直是商家兴衰成败的关键。 时至今日&#xff0c;随着科技的飞速发展&#xff0c;信息时代的浪潮更是将商业秘密的保护推向了新的高度。 离职保密协议&…

思科CCIE最新考证流程

CCIE CCIE&#xff0c;全称Cisco Certified Internetwork Expert,是美国Cisco公司于1993年开始推出的专家级认证考试。被全球公认为IT业最权威的认证&#xff0c;是全球Internetworking领域中最顶级的认证证书。 CCIE方向 CCIE主要有六大方向&#xff1a;企业基础架构Enterp…

重修设计模式-行为型-状态模式

重修设计模式-行为型-状态模式 先了解一下状态机的概念&#xff0c;状态机是软件编程中对一种状态场景的抽象表达&#xff0c;构成状态机三要素是&#xff1a;状态&#xff08;State&#xff09;、事件&#xff08;Event&#xff09;、动作&#xff08;Action&#xff09;&…

【测试】趣味五子棋项目测试报告

一、项目概述以及本次测试的目标 本项目是基于Web的五子棋实时对战应用&#xff0c;为用户提供多人实时游戏体验&#xff1b;项目采用了前后端分离的方法来实现&#xff0c;使用了数据库来存储相关的数据&#xff1b;前端主要有四个页面构成&#xff1a;登录页面&#xff0c;注…

多重背包问题

文章目录 朴素算法基本思想代码 二进制优化算法基本思想代码 单调队列优化多重背包基本思想代码 多重背包我们其实可以看成为01背包和完全背包的组合。也可以把多重背包问题只转换成01背包问题&#xff0c;我们一起来看看解题思路。 朴素算法 基本思想 比如第i件物品有s个,我…

作业08.13

一、TCP机械臂测试 通过w(红色臂角度增大)s&#xff08;红色臂角度减小&#xff09;d&#xff08;蓝色臂角度增大&#xff09;a&#xff08;蓝色臂角度减小&#xff09;按键控制机械臂 注意&#xff1a;关闭计算机的杀毒软件&#xff0c;电脑管家&#xff0c;防火墙 1&#x…

CRC校验算法详解、C语言实现

一、前言 1.1 CRC算法介绍 CRC&#xff08;Cyclic Redundancy Check&#xff09;校验算法是一种广泛应用于数据通信和存储系统中的错误检测方法&#xff0c;主要用于检测数据在传输过程中是否发生了改变。CRC算法通过计算一个固定长度的校验码&#xff0c;将该校验码附加到原…

Linux:进程

先了解一下这篇的基础知识 操作系统简述-CSDN博客还有这篇 ok我们来说进程 进程是什么&#xff1f; 在Windows下我们按下EscCtrlShift召唤任务管理器&#xff0c;查看Windows下的进程 我们的进程也是由操作系统管理的&#xff0c;操作系统对进程的管理也是先描述再组织。 …

Docker Hub 镜像代理加速

因为未知原因&#xff0c;docker hub 已经不能正常拉取镜像&#xff0c;可以使用以下代理服务来进行&#xff1a; "https://docker.m.daocloud.io", "https://noohub.ru", "https://huecker.io", "https://dockerhub.timeweb.cloud"…

更深层的理解视觉Transformer, 对视觉Transformer的剖析

写在前面&&笔者的个人理解 目前基于Transformer结构的算法模型已经在计算机视觉&#xff08;CV&#xff09;领域展现出了巨大的影响力。他们在很多基础的计算机视觉任务上都超过了之前的卷积神经网络&#xff08;CNN&#xff09;算法模型&#xff0c;下面是笔者找到的…

无字母绕过webshell

目录 代码 payload构造 php7 php5 构造payload 代码 不可以使用大小写字母、数字和$然后实现eval的注入执行 <?php if(isset($_GET[code])){$code $_GET[code];if(strlen($code)>35){die("Long.");}if(preg_match("/[A-Za-z0-9_$]/",$code))…