【网络】协议与网络版计算器
协议与网络版计算器
文章目录
- 1.协议的概念
-
- 1.1序列化与反序列化
- 2.网络版计算器
-
- 2.1封装套接字
- 2.2协议定制
-
- 2.2.1Jsoncpp
- 2.2.2报文处理
- 2.3会话层:TcpServer
- 2.4应用层:Calculate
- 2.5表示层:Service
- 2.6应用层、表示层和会话层->应用层
1.协议的概念
为了使数据在网络上能够从源到达目的,网络通信的参与方必须遵循相同的规则,我们将这套规则称为协议(protocol),而协议最终都需要通过计算机语言的方式表示出来。只有通信计算机双方都遵守相同的协议,计算机之间才能互相通信交流。
通俗的讲,协议本质就是双方约定好的结构化的数据。
假设我们此时要实现一个网络版计算器,那么很明显我们需要“操作数1”、“操作符”、“操作数2”等多个信息,为了减小服务器的压力,我们一定不能将这些信息分批发送,一定是打包好成为一个数据发送给对端,那么此时就有了以下方案:
定制结构体+序列化和反序列化
- 定制结构体来表示需要交互的信息。
- 发送数据时将这个结构体按照一个规则转换成字符串,接收数据时再按照相同的规则把接收到的字符串转化为结构体。
- 这个过程叫做**“序列化”和“反序列化”**。
1.1序列化与反序列化
在之前实现的聊天室服务器中,用户发送数据实际上发送的不仅仅有“message”,还可能包括用户名、发送时间等信息,很明显这就是一个结构化的数据,那序列化就是将该结构化的数据转化为字符串方便网络发送,反序列化就是把信息一变多,方便上层读取处理。
那么read
、write
或者是recv
、send
函数是在网络中是如何工作起来的呢?
在系统中,我们是知道read
、write
函数的工作过程的,比如write
函数将用户缓冲区中的数据拷贝到文件描述符所指向的文件结构体的内核缓冲区,操作系统会在合适的时间将内核缓冲区的内容刷新到磁盘上。
那在网络中也是一样的,只不过内核缓冲区变成了传输层维护的发送缓冲区和接收缓冲区(实际上也是内核级缓冲区,这块是方便理解),那么什么时候发?怎么发?出错了怎么办?这些问题就是TCP协议需要考虑解决的问题,所以TCP协议即(Transmission Control Protocol)传输控制协议,这个控制就体现在这了。
所以read
、write
或者是recv
、send
函数本质上是拷贝函数,他们完成的工作无非就是将一块区域的数据拷贝到另一块区域,即发送数据的本质就是将自己的发送缓冲区中的数据拷贝到接收方的接收缓冲区,所以也可以说通信的本质就是拷贝,也证明双发的主机通信本质是双方操作系统在进行通信。
TCP协议支持全双工的原因也可以找到了:TCP协议拥有两块缓冲区:发送缓冲区、接收缓冲区,这两块缓冲区互不干扰,仅需一个文件描述符fd
就可以实现,因为写是向发送缓冲区写,读是在接收缓冲区读。
而且这些过程像极了系统中学习的管道、文件部分,比如read
函数为什么会阻塞,因为接收缓冲区中无数据,所以系统对于网络学习是十分重要的。
但既然TCP是面向字节流的,那我们该如何解决数据传输过程中数据缺失的问题呢?毕竟在传输层眼里数据都是字节流,无法识别出字段含义的。
为什么不能将结构体直接发送呢?还需要转化成字符串发送?
- 技术上:跨平台问题,不同操作系统结构体内存对齐方式不同,甚至可能有大小端的问题。
- 业务上:结构体成员可能会随着业务变化而变化,如果在通信过程不转化为字符串,那么在后期维护上会面临诸多问题,每次修改都可能会牵一发而动全身。
2.网络版计算器
2.1封装套接字
首先我们先将套接字进行封装,封装的主要目的是为了简化操作,让我们仅需调用几个函数就可以完成一些对于套接字的初始化工作。
我们可以设计一个父类Socket,内部包含有若干接口,然后再根据具体套接字对父类Socket进行实现,比如TcpSocket继承父类Socket,实现父类Socket的接口。
// 模板方法模式
namespace socket_ns
{class Socket;const static int gbacklog = 8;using socket_sptr = std::shared_ptr<Socket>;enum{SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERROR,USAGE_ERROR};class Socket{public:virtual void CreateSocketOrDie() = 0;virtual void BindSocketOrDie(InetAddr &addr) = 0;virtual void ListenSocketOrDie() = 0;virtual socket_sptr Accepter(InetAddr *addr) = 0;virtual bool Connector(InetAddr &addr) = 0;virtual int SockFd() = 0;virtual int Recv(std::string *out) = 0;virtual int Send(const std::string &in) = 0;// virtual void Other()=0;public:void BuildListenSocket(InetAddr &addr){CreateSocketOrDie();BindSocketOrDie(addr);ListenSocketOrDie();}bool BuildClientSocket(InetAddr &addr){CreateSocketOrDie();return Connector(addr);}// void BuildUdpSocket()// {// CreateSocketOrDie();// BindSocketOrDie();// }};class TcpSocket : public Socket{public:TcpSocket(int fd = -1) : _sockfd(fd){}void CreateSocketOrDie() override{// 1. 创建流式套接字_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error");exit(SOCKET_ERROR);}LOG(DEBUG, "socket create success,sockfd is : %d", _sockfd);}void BindSocketOrDie(InetAddr &addr) override{// 2. 绑定struct sockaddr_in local; // struct sockaddr_in 系统提供的数据类型。local是变量,用户栈上开辟空间。bzero(&local, sizeof(local)); // 将从&local开始的sizeof(local)大小的内存区域置零local.sin_family = AF_INET; // 设置网络通信方式local.sin_port = htons(addr.Port()); // port要经过网络传输给对面,所有需要从主机序列转换为网络序列local.sin_addr.s_addr = inet_addr(addr.Ip().c_str());int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind error");exit(BIND_ERROR);}LOG(DEBUG, "bind success,sockfd is : %d", _sockfd);}void ListenSocketOrDie() override{// 3. tcp是面向连接的,所以通信之前,必须先建立连接,服务器是被链接的// tcpserver启动,未来首先要一直等待客户端的连接,listenint n = listen(_sockfd, gbacklog);if (n < 0){LOG(FATAL, "listen error");exit(LISTEN_ERROR);}LOG(DEBUG, "listen success,sockfd is : %d", _sockfd);}socket_sptr Accepter(InetAddr *addr) override{struct sockaddr_in peer;socklen_t len = sizeof(peer);// accept会阻塞等待,直到有客户端连接int sockfd = ::accept(_sockfd, (struct sockaddr *)&peer, &len);if (sockfd < 0){LOG(WARNING, "accept error");return nullptr;};*addr = peer;socket_sptr sock = std::make_shared<TcpSocket>(sockfd);return sock;}bool Connector(InetAddr &addr) override{// 构建目标主机的socket信息struct sockaddr_in server;memset(&server, 0, sizeof(server)); // bzeroserver.sin_family = AF_INET;server.sin_port = htons(addr.Port());server.sin_addr.s_addr = inet_addr(addr.Ip().c_str());int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){std::cerr << "connect error" << std::endl;return false;}return true;}int Recv(std::string *out){char inbuffer[1024];ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);if (n > 0){inbuffer[n] = 0;*out += inbuffer; // 为什么是+=? 后面会提到}return n;}int Send(const std::string &in){int n = ::send(_sockfd, in.c_str(), in.size(), 0);return n;}int SockFd() override{return _sockfd;}private:int _sockfd;};
}
2.2协议定制
为了保证通信双方能够识别发送接收的数据,这里需要进行协议定制,即设计发送数据结构体、接收数据结构体、包括序列化和反序列化的方案。
请求结构体中需要包括两个操作数,以及对应需要进行的操作。响应结构体中需要包括一个计算结果,除此之外,响应结构体中还需要包括一个状态字段,表示本次计算的状态,因为客户端发来的计算请求可能是无意义的,比如除0。
规定状态字段对应的含义:
- 状态字段为0,表示计算成功。
- 状态字段为1,表示出现除0错误。
- 状态字段为2,表示出现模0错误。
- 状态字段为3,表示非法计算。
上述我们提到过数据的发送取决于TCP协议,即我们只管将数据通过write
函数将数据拷贝到发送缓冲区,剩下的发送工作由传输控制协议TCP完成,那么如何判断从接收缓冲区中读取到的数据是否准确完整呢?
我们知道一个完整的报文应该包含报头和有效载荷。
即:
// "有效载荷的长度"
"有效载荷"// "len"
"_x _op _y"-> len: 有效载荷的长度,约定
是分隔符,不参与统计
也就是说当我们识别到 的时候,我们一定可以得到有效载荷的长度,得到了有效载荷的长度我们就可以根据该长度判断是否得到了完整的报文。
当然这里有效载荷即序列化反序列化的方案我们采用开源的JSON方案。
2.2.1Jsoncpp
Jsoncpp是一个用于处理 JSON 数据的 C++ 库。 它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。 Jsoncpp是开源的, 广泛用于各种需要处理 JSON 数据的 C++ 项目中。
这里我们简要说明如何使用Jsoncpp完成序列化和反序列化的工作。
假设我们设计了一个数据结构为:
struct stu
{std::string name;int age;double weight;public:void debug(){std::cout << name << std::endl;std::cout << age << std::endl;std::cout << weight << std::endl;}
};
(1)序列化
序列化是将数据结构转化为字符串,在序列化之前我们必须先创建一个Json::Value
对象,并将数据结构中的各个成员变量的值赋给该对象,即将C++数据结构转化为Json数据。
就像这样:
struct stu zs = {"张三", 18, 70};
Json::Value root;
root["name"] = zs.name;
root["age"] = zs.age;
root["weight"] = zs.weight;
之后我们需要再创建一个Json::StyledWriter
对象或者Json::FastWriter
对象,这两个对象简要的说就是决定了转化出来的字符串格式。
比如对于Json::StyledWriter
对象来说,转化出来的字符串是这样的:
{"age" : 18,"name" : "张三","weight" : 70
}
对于Json::FastWriter
对象来说,转化出来的字符串是这样的:
{"age":18,"name":"张三","weight":70}
Json::FastWriter
优点就是比StyledWriter
更快,因为它不添加额外的空格和换行符。
言归正传,得到Json::FastWriter
对象后,我们可以调用该对象的write方法,该方法参数为Json::Value
对象,返回转化后的字符串。
完整示例:
#include <iostream>
#include <string>
#include <fstream>
#include <jsoncpp/json/json.h>
int main()
{// 结构化数据struct stu zs = {"张三", 18, 70};// 转换成为字符串Json::Value root;root["name"] = zs.name;root["age"] = zs.age;root["weight"] = zs.weight;// root["self"] = root;Json::FastWriter writer;// Json::StyledWriter writer;std::string str = writer.write(root);std::ofstream out("out.txt");if (!out.is_open()){std::cout << str;return 1;}out << str;out.close();return 0;
}
(2)反序列化
反序列化是将字符串转化为数据结构,同样的我们需要创建两个对象:Json::Value
对象用于接收字符串中的信息,Json::Reader
的parse
方法用于将字符串中的数据赋给Json::Value
对象,第一个参数为json字符串,第二个参数为需要赋给的Json::Value
对象,返回值为成功或者失败,就像这样:
std::string json_string = buffer;
Json::Value root;
Json::Reader reader;
bool res = reader.parse(json_string, root);
需要注意的是Json::Value
对象还需要转化为具体的数据结构,在转化时我们需要指明Json::Value
对象成员的属性,就像这样:
struct stu zs;
zs.name = root["name"].asString();
zs.age = root["age"].asInt();
zs.weight = root["weight"].asDouble();
完整示例:
int main()
{std::ifstream in("out.txt");if (!in.is_open())return 1;char buffer[1024];in.read(buffer, sizeof(buffer));in.close();std::string json_string = buffer;Json::Value root;Json::Reader reader;bool res = reader.parse(json_string, root);(void)res;struct stu zs;zs.name = root["name"].asString();zs.age = root["age"].asInt();zs.weight = root["weight"].asDouble();zs.debug();return 0;
}
所以根据Jsoncpp,我们可以实现对请求响应的序列化和反序列化。
2.2.2报文处理
然后更为重要的是我们如何保证读取到的内容是完整的报文,上面提到过:添加报头,根据报头的长度信息判断,也就是说完整的报文应该是如下结构:
len
{"age":18,"name":"张三","weight":70}
所以该协议定制我们还要实现两个方法:
- Encode添加报头
- Decode判断完整报文并将该报文从缓冲区中清除
协议完整代码如下:
#pragma once
// protocol协议
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>namespace protocol_ns
{const std::string SEP = "
";// 添加报头和分隔符,将json串变为完整的报文格式"len"
"{ }"std::string Encode(const std::string &json_str){int json_str_len = json_str.size();std::string proto_str = std::to_string(json_str_len);proto_str += SEP;proto_str += json_str;proto_str += SEP;return proto_str;}// "len"
"{// "len"
"{ }"// "len"
"{ }"
;// "len"
"{ }"
"len";// "len"
"{ }"
"len"
"{ }";// "len"
"{ }"
"len"
"{ }"// 判断完整报文并将该报文从缓冲区中清除std::string Decode(std::string &inbuffer){auto pos = inbuffer.find(SEP);if (pos == std::string::npos)return std::string();std::string len_str = inbuffer.substr(0, pos);if (len_str.empty())return std::string();int packlen = std::stoi(len_str);// 计算报文总长int total = packlen + len_str.size() + 2 * SEP.size();if (inbuffer.size() < total)return std::string();std::string package = inbuffer.substr(pos + SEP.size(), packlen);inbuffer.erase(0, total); // 从缓冲区中删掉该报文return package;}//请求class Request{public:Request(){}Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper){}bool Serialize(std::string *out) // 序列化{Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::FastWriter writer;*out = writer.write(root);return true;}bool Deserialize(const std::string &in) // 反序列化{Json::Value root;Json::Reader reader;bool res = reader.parse(in, root);if (!res)return false;_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return true;}public:int _x;int _y;char _oper; // "+-*/%" _x _oper _y}; // --- "字符串"//响应class Response{public:Response(){}Response(int result, int code) : _result(result), _code(code){}bool Serialize(std::string *out){Json::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter writer;*out = writer.write(root);return true;}bool Deserialize(const std::string &in){Json::Value root;Json::Reader reader;bool res = reader.parse(in, root);if (!res)return false;_result = root["result"].asInt();_code = root["code"].asInt();return true;}public:int _result; // 结果int _code; // 0:success 1: 除0 2: 非法操作 3. 4. 5}; // --- "字符串"//构建请求与响应,客户端用class Factory{public:Factory(){srand(time(nullptr) ^ getpid());opers = "+-*/%^&|";}std::shared_ptr<Request> BuildRequest(){int x = rand() % 10 + 1;usleep(x * 10);int y = rand() % 5; // [01,2,3,4]usleep(y * x * 5);char oper = opers[rand() % opers.size()];std::shared_ptr<Request> req = std::make_shared<Request>(x, y, oper);return req;}std::shared_ptr<Response> BuildResponse(){return std::make_shared<Response>();}~Factory(){}private:std::string opers;};
}
2.3会话层:TcpServer
我们利用封装好的Socket中的BuildListenSocket()
方法,实现创建初始化服务器,即TcpServer的构造函数,初始化完成后我们需要让服务器不断循环处理业务逻辑,同样的利用Socket中封装的Accepter()
方法用来获取客户端的链接,得到本次提供服务的套接字,将该套接字作为线程参数传递给多线程处理,任务利用包装器封装为io_service_t
由外部传递。
由于任务是由外部传递的,所以我们就完成了TcpServer类与具体业务的解耦,将来只需要改变传递进去的任务就可以改变TcpServer的业务逻辑。
换句话说TcpServer此时就是OSI七层模型中**“会话层”**的实现,它仅负责通信管理、负责建立和断开通信连接(数据流动的逻辑通路)。
TcpServer代码:
using namespace socket_ns;
class TcpServer; // 声明using io_service_t = std::function<void(socket_sptr sockfd, InetAddr client)>;class ThreadData
{public:ThreadData(socket_sptr fd, InetAddr addr, TcpServer *s) : sockfd(fd), clientaddr(addr), self(s){}public:socket_sptr sockfd;InetAddr clientaddr;TcpServer *self;
};class TcpServer
{public:TcpServer(int port, io_service_t service): _localaddr("0", port), _listensock(std::make_unique<TcpSocket>()), _service(service), _isrunning(false){_listensock->BuildListenSocket(_localaddr);}static void *HandlerSock(void *args) // IO和业务进行解耦合{pthread_detach(pthread_self()); // 线程分离ThreadData *td = static_cast<ThreadData *>(args);// 需要调用Service函数,但是Service函数是类内函数,静态成员函数没有this指针无法调用,如何解决?// 将this指针设为ThreadData的类内成员,再通过这个this调用Servicetd->self->_service(td->sockfd, td->clientaddr);::close(td->sockfd->SockFd()); // 文件描述符泄露delete td;return nullptr;}void Loop(){_isrunning = true;// 4. 不能直接接收数据,先获取连接while (_isrunning){InetAddr peeraddr;socket_sptr normalsock = _listensock->Accepter(&peeraddr);if (normalsock == nullptr)continue;// version 2 :采用多线程pthread_t t;ThreadData *td = new ThreadData(normalsock, peeraddr, this);pthread_create(&t, nullptr, HandlerSock, td);}_isrunning = false;}~TcpServer(){}private:InetAddr _localaddr;std::unique_ptr<Socket> _listensock;bool _isrunning;io_service_t _service;
};
2.4应用层:Calculate
具体业务是什么呢?网络版计算器。
非常简单,只需要完成相应的计算并返回结果和状态码即可。
using namespace protocol_ns;class Calculate
{public:Calculate(){}Response Excute(const Request &req){Response resp(0, 0);switch (req._oper){case '+':resp._result = req._x + req._y;break;case '-':resp._result = req._x - req._y;break;case '*':resp._result = req._x * req._y;break;case '/':{if (req._y == 0){resp._code = 1;}else{resp._result = req._x / req._y;}}break;case '%':{if (req._y == 0){resp._code = 2;}else{resp._result = req._x % req._y;}}break;default:resp._code = 3;break;}return resp;}~Calculate(){}private:
};
2.5表示层:Service
以服务器收到请求并响应这一过程举例,完整的流程应为:
- 读取数据
- 分析数据,获取完整报文
- 反序列化
- 业务处理
- 对响应进行序列化
- 对响应添加报头
- 发送数据
这其实就是OSI七层模型中**“表示层”**的实现,表示层负责设备固有数据格式和网络标准数据格式的转换。表示层就是协议。
序列化、反序列化、添加报头、分析数据这些都是数据格式的转换工作。
具体用代码体现就是这样:
using namespace protocol_ns;void Usage(std::string proc)
{std::cout << "Usage:" << proc << " local_port
"<< std::endl;
}using callback_t = std::function<Response(const Request &req)>;class Service
{public:Service(callback_t cb) : _cb(cb){}void ServiceHelper(socket_sptr sockptr, InetAddr client){int sockfd = sockptr->SockFd();LOG(DEBUG, "get a new link ,info %s:%d,fd:%d", client.Ip(), client.Port(), sockfd);std::string clientaddr = "[" + client.Ip() + ":" + std::to_string(client.Port()) + "] ";std::string inbuffer;while (true){sleep(5); // 测试用,人为的让服务端先不处理,积压一部分请求,测试服务器是否能够解决TCP粘包问题Request req;// 1.读取数据int n = sockptr->Recv(&inbuffer); // 如何保证读到的是一个完整的请求?if (n < 0){LOG(DEBUG, "client %s quit", clientaddr.c_str());break;}// 2.分析数据,获取完整报文std::string package;while (true){sleep(1);std::cout << "服务器未处理的请求: " << inbuffer << std::endl;package = Decode(inbuffer);if (package.empty())break; // 证明此时没有一个完整的报文继续读取,这也是为什么Socket类中Recv接口中*out+=inbuffer;是+=的原因// 代码执行到这一定有完整json字符串std::cout << "----------------------begin----------------------" << std::endl;std::cout << "请求json字符串:
"<< package << std::endl;// 3.反序列化req.Deserialize(package);// 4.业务处理Response resp = _cb(req);// 5.对响应序列化std::string send_str;resp.Serialize(&send_str);std::cout << "响应序列化:" << std::endl;std::cout << send_str << std::endl;// 6.添加长度报头send_str = Encode(send_str);std::cout << "响应完整报文:" << std::endl;std::cout << send_str << std::endl;sockptr->Send(send_str); // 本次不对发送做处理 EPOLL}}}private:callback_t _cb;
};
客户端完整流程与服务端类似,只不过反过来,流程如下:
- 构建请求
- 对请求进行序列化
- 添加报头
- 发送数据
- 读取服务器响应
- 分析数据,获取完整报文
- 反序列化
具体代码实现:
void Usage(std::string proc)
{std::cout << "Usage:" << proc << " serverip serverport
"<< std::endl;
}// ./tcp_client 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]);InetAddr serveraddr(serverip, serverport);Factory factory;std::unique_ptr<Socket> cli = std::make_unique<TcpSocket>();bool res = cli->BuildClientSocket(serveraddr);std::string inbuffer;while (res){sleep(1);std::string str;// 一次构建五个请求,测试服务器对积压请求处理for (int i = 0; i < 5; i++){// 1.构建一个请求auto req = factory.BuildRequest();// 2. 对请求进行序列化std::string send_str;req->Serialize(&send_str);std::cout << "请求序列化:
"<< send_str << std::endl;// 3. 添加长度报头send_str = Encode(send_str);std::cout << "请求完整报文:
"<< send_str << std::endl;str += send_str;}// 4. "len"
"{}"cli->Send(str);// 5. 读取服务器响应int n = cli->Recv(&inbuffer);if (n <= 0)break;std::string package = Decode(inbuffer);if (package.empty())continue;// 6. 我能保证package一定是一个完整的响应!auto resp = factory.BuildResponse();// 6.1 反序列化resp->Deserialize(package);// 7. 拿到了结构化的响应std::cout << "计算结果: " << resp->_result << "[" << resp->_code << "]" << std::endl;}return 0;
}
2.6应用层、表示层和会话层->应用层
接下来我们需要将代码捏合在一起,我们通过bind将旧的可调用对象捆绑新的参数成为新的可调用对象层层传递,OSI七层模型的应用层、表示层和会话层全部统称为应用层的原因就是所有的这三层需要实现的全部都由用户自己实现自己定义,就好比用户需要决定报文以什么标准传递,是“xml”还是“json”,网络传输协议是Tcp协议还是Udp协议,这些都需要用户自己决定,所以这三层我们合并为一层应用层。
// ./tcpserver port
// 云服务器的port默认都是禁止访问的。云服务器放开端口8080 ~ 8085
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);// exit(USAGE_ERROR);return 1;}uint16_t port = std::stoi(argv[1]);Calculate cal; // 应用层Service calservice(std::bind(&Calculate::Excute, &cal, std::placeholders::_1)); // 表示层io_service_t service = std::bind(&Service::ServiceHelper, &calservice, std::placeholders::_1, std::placeholders::_2);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, service); // 会话层tsvr->Loop();return 0;
}
你与自己的关系,会奠定下你与其他所有关系的基石。 —罗伯特·霍尔登
参数成为新的可调用对象层层传递,OSI七层模型的应用层、表示层和会话层全部统称为应用层的原因就是所有的这三层需要实现的全部都由用户自己实现自己定义,就好比用户需要决定报文以什么标准传递,是“xml”还是“json”,网络传输协议是Tcp协议还是Udp协议,这些都需要用户自己决定,所以这三层我们合并为一层应用层。
// ./tcpserver port
// 云服务器的port默认都是禁止访问的。云服务器放开端口8080 ~ 8085
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);// exit(USAGE_ERROR);return 1;}uint16_t port = std::stoi(argv[1]);Calculate cal; // 应用层Service calservice(std::bind(&Calculate::Excute, &cal, std::placeholders::_1)); // 表示层io_service_t service = std::bind(&Service::ServiceHelper, &calservice, std::placeholders::_1, std::placeholders::_2);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, service); // 会话层tsvr->Loop();return 0;
}
你与自己的关系,会奠定下你与其他所有关系的基石。 —罗伯特·霍尔登
相关文章:
【网络】协议与网络版计算器
协议与网络版计算器 文章目录 1.协议的概念 1.1序列化与反序列化 2.网络版计算器 2.1封装套接字2.2协议定制 2.2.1Jsoncpp2.2.2报文处理 2.3会话层:TcpServer2.4应用层:Calculate2.5表示层:Service2.6应用层、表示层和会话层->应用层 …...
AI语言模型的技术之争:DeepSeek与ChatGPT的架构与训练揭秘
云边有个稻草人-CSDN博客 目录 第一章:DeepSeek与ChatGPT的基础概述 1.1 DeepSeek简介 1.2 ChatGPT简介 第二章:模型架构对比 2.1 Transformer架构:核心相似性 2.2 模型规模与参数 第三章:训练方法与技术 3.1 预训练与微调…...
【Python爬虫(5)】HTTP协议:Python爬虫的基石
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取ÿ…...
机器学习数学基础:24.随机事件与概率
一、教程目标 本教程致力于帮助零基础或基础薄弱的学习者,全面掌握概率论与数理统计的基础公式,透彻理解核心概念,熟练学会应用解题技巧,最终能够轻松应对期末或考研考试。 二、适用人群 特别适合那些对概率论与数理统计知识了…...
Mongodb数据管理
Mongodb数据管理 1.登录数据库,查看默认的库 [rootdb51~]# mongo> show databases; admin 0.000GB config 0.000GB local 0.000GB> use admin switched to db admin > show tables system.version > admin库:admin 是 MongoDB 的管理…...
vue3响应式丢失解决办法(三)
vue3的响应式的理解,与普通对象的区别(一) vue3 分析总结响应式丢失问题原因(二) 经过前面2篇文章,知道了响应式为什么丢失了,但是还是碰到了丢失情况,并且通过之前的内容还不能解…...
Django中数据库迁移命令
在 Django 中,数据库迁移是确保数据库结构与 Django 模型定义保持一致的重要过程。以下是 Django 中常用的数据库迁移命令: 1. python manage.py makemigrations 功能:此命令用于根据 Django 项目的模型文件(models.pyÿ…...
LLM之循环神经网络(RNN)
在人工智能的领域中,神经网络是推动技术发展的核心力量。今天,让我们深入探讨循环神经网络(RNN) 一、神经网络基础 (1)什么是神经网络 神经网络,又称人工神经网络,其设计灵感源于人…...
TDengine 客户端连接工具 taos-Cli
简介工具获取运行命令行参数 基础参数高级参数 数据导出/导入 数据导出数据导入 执行 SQL 脚本使用小技巧 TAB 键自动补全设置字符列显示宽度其它 错误代码表 简介 TDengine 命令行工具(以下简称 TDengine CLI)是用户操作 TDengine 实例并与之交互最简…...
Express 路由路径正则详解
在 Express 中,使用正则表达式可以定义更加灵活和复杂的路由。 1. 基本语法 在 Express 中,路由路径可以是一个字符串、字符串模式或者正则表达式。当使用正则表达式时,将其作为路由路径传入 app.METHOD() 方法(METHOD 可以是 g…...
快速设置 Docker 网络代理配置
Docker Client - 代理访问远程的 Docker Daemon 在 Client 端设置代理其实就是设置 Linux 系统的代理,从而让系统的命令行可以通过代理连接到外部的网络。一般只需要配置 HTTP_PROXY 与 HTTPS_PROXY 这两个即可。 临时生效: 在命令行中执行下面的命令&…...
JVM ②-双亲委派模型 || 垃圾回收GC
这里是Themberfue 在上节课对内存区域划分以及类加载的过程有了简单的了解后,我们再了解其他两个较为重要的机制,这些都是面试中常考的知识点,有必要的话建议背出来,当然不是死记硬背,而是要有理解的背~~~如果对 JVM …...
内容中台驱动企业数字化内容管理高效协同架构
内容概要 在数字化转型加速的背景下,企业对内容管理的需求从单一存储向全链路协同演进。内容中台作为核心支撑架构,通过统一的内容资源池与智能化管理工具,重塑了内容生产、存储、分发及迭代的流程。其核心价值在于打破部门壁垒,…...
人工智障的软件开发-自动流水线CI/CD篇-docker+jenkins部署之道
指令接收:「需要自动构建系统」 系统检测:目标开发一个软件已完成代码仓库-轻盈的gitea,开始添加自动流水线 启动应急冷却协议:准备承受Java系应用的资源冲击 核心组件锁定:构建老将军Jenkins(虽然年迈但依…...
数字人技术之LatentSync Win11本地部署
#LatentSync技术原理 字节跳动开源的基于音频条件潜在扩散模型的端到端唇同步框架,基于潜在扩散模型,以音频条件潜在扩散模型为基础,利用 Stable Diffusion 强大能力,直接建模复杂的音频与视觉之间的关系,实现高质量的唇形同步. 从而制作虚拟…...
Llama3.0论文学习笔记: The Llama 3 Herd of Models
1. 写在前面 今天分享Llama3.0的论文,2024.7月来自Meta的Llama团队,2025年1月DeepSeek R1出现之后,其风头显然已经盖住了Llama3,这时候整理Llama3感觉有点赶不上潮流了,但是我还是想整理下Llama3.0,原因是…...
C#学习之数据转换
目录 一、创作说明 二、数据类型之间的转换 1.数据类型之间的转换表格 2.代码示例 三、进制之间的转换 1.进制之间的转换表格 2.代码示例 四、ASCII 编码和字符之间的转换 1.ASCII 编码和字符之间的转换表格 2.代码示例 五、总结 一、创作说明 C#大多数时候都是和各…...
POI 和 EasyExcel
前言 将表格信息导出为Excel表格(导出数)将Excel表格信息录入到数据库(导入数据) 操作Excel目前比较流行的就是 Apache POI 和阿里巴巴的 EasyExcel Apache POI Apache POI 官网:https://poi.apache.org/ HSSF&am…...
分布式光纤传感:为生活编织“感知密网”
分布式光纤测温技术虽以工业场景为核心,但其衍生的安全效益已逐步渗透至日常生活。 分布式光纤测温技术(DTS)作为一种先进的线型温度监测手段,近年来在多个领域展现了其独特的优势。虽然其核心应用场景主要集中在工业、能源和基础…...
Web后端 - Maven管理工具
一 Maven简单介绍 Maven是apache旗下的一个开源项目,是一款用于管理和构建java项目的工具。 Maven的作用 二 Maven 安装配置 依赖配置 依赖传递 依赖范围 生命周期 注意事项:在同一套生命周期中,当运行后面的阶段时,前面的阶段都…...
聊一聊vue如何实现角色权限的控制的
大家好,我是G探险者。 关于角色与权限控制,通常是分为两大类:一种是菜单权限;一种是操作权限。 菜单权限是指,每个角色对应着可以看到哪些菜单,至于每个菜单里面的每个按钮,比如增删改查等等这类…...
Java中使用EasyExcel
Java中使用EasyExcel 文章目录 Java中使用EasyExcel一:EasyExcel介绍1.1、核心函数导入数据导出数据 1.2、项目实际应用导入数据导出数据 1.3、相关注解ExcelProperty作用示例 二:EasyExcel使用2.1、导入功能2.2、导出功能 三:EasyExcel完整代…...
LLM:GPT 系列
阅读原文: LLM:Qwen 系列 GPT(Generative Pre-trained Transformer)是生成式预训练语言模型,基于 Transformer 架构,专注于通过自回归的方式生成自然语言文本,即给定一个输入序列 x { x 1 , …...
【已解决】TypeError: AsyncConnectionPool.__init__(), new install bug, httpx==0.24.1
1,参考社区链接,首先降低gradio版本,降低到4以下,但是也不能降太低,也不能太高,要适中,推荐版本3.39.0 pip install gradio3.39.0 2,下载正确的httpx版本 参考社区链接࿰…...
Linux:深入了解进程信号(上)
目录 1. 什么是信号 1.1 引入 1.2 概念 1.3 特性 1.4 信号的三个方面 2. 信号的产生 2.1 键盘按键产生 2.2 signal捕捉信号函数 2.3 发送信号原理 2.4 硬件中断 2.5 指令和函数接口 2.5.1 kill指令 2.5.2 kill函数 2.5.3 raise与abort函数 2.6 软件条件 2.7 异…...
Java小白入门基础知识(二)
1.标识符 概念: 在程序中给类,方法,变量取的名称叫做标识符 规范: 字母,数字,下划线,$符号 注意: 不能以数字开头,也不能是关键字,严格区分大小写(一般定义常量就是大写) 软性建议: 1)类名:每个单词首字母大写(大驼峰) 2)方法名:首字母小写,后面每个单词首字母大写(小驼…...
Servlet中,WebServlet注解的使用方法
案例:声明abc接口,在接口内部获取配置信息 WebServlet(urlPatterns"/abc",loadOnStartup6,initParams {WebInitParam(name"username", value"mmm", description"this is username"),WebInitParam(name"a…...
重新出发的LLM本地部署——DeepSeek加持下的Ollama+OpenWebUI快速部署
DeepSeek 这真的是太惊艳了,发布出来的模型这么能打,在线的版本使用起来也是丝滑连招,感觉效果比起之前一直用智谱 chatglm4 更好用,想着本地化部署一下。 本来以为,会和之前在开发测试 transformers 的模型和代码一样…...
【Python爬虫(1)】专栏开篇:夯实Python基础
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取ÿ…...
低代码组态软件-BY组态
引言 在工业4.0与智能制造浪潮的推动下,组态软件作为工业自动化的核心工具,正逐步从传统单机模式向Web化、智能化方向演进。BY组态作为一款基于Web的嵌入式组态插件工具,凭借其低代码、高灵活性和跨平台特性,成为工业物联网&#…...
【计算机网络】数据链路层数据帧(Frame)格式
在计算机网络中,数据帧(Frame) 是数据链路层的协议数据单元(PDU),用于在物理介质上传输数据。数据帧的格式取决于具体的链路层协议(如以太网、PPP、HDLC 等)。以下是常见数据帧格式的…...
[特殊字符] C语言中打开和关闭文件的两种方法:标准库 VS 系统调用
C语言中对文件打开关闭操作 前言方法一:标准输入输出库(stdio.h)—— 高级文件操作的利器打开文件💡 关闭文件:fclose示例代码📝 个人见解 方法一:系统调用(fcntl.h 和 unistd.h&…...
如何将ubuntu下的一个目录,保存目录结构为一个git仓库并上传
目录 1. 初始化本地Git仓库 2. 添加文件到仓库 3. 提交更改 4. 创建并关联远程仓库 5. 推送代码到远程仓库 完整流程总结 要将Ubuntu下的一个目录(例如rpc)保存为一个Git仓库并上传到远程仓库,您可以遵循以下步骤: 1. 初始…...
应用分层、三层架构和MVC架构
前言 在前面中,我们已经学习了Spring MVC 的一些基础操作,那么后面就用一些简单的案例来巩固一下。 在开始学习做案例之前,我们先来了解一下在软件开发中常见的设计模式和架构。 应用分层 含义 应用分层是一种软件开发设计思想࿰…...
前端实现防抖功能的详细解读
在前端开发中,防抖(Debounce) 是一种优化技术,用于限制某个函数在短时间内被频繁调用的次数。它的核心思想是:在一定时间内,无论触发多少次事件,只执行最后一次操作。防抖通常用于处理用户输入、…...
VUE3环境搭建
最近准备用Vue编写一点前端页面,我在前端一直是个小白,之前用的Vue2写了几个页面,现在已经是VUE3了,重新安装下环境开始。 1.npm安装 Vue需要用npm安装,npm是nodejs的package manager,这里我们安装下node…...
1-16 tortoiseGit分支与Git操作
1-1 创建分支 什么时候需要开分支? - 隔离线上版本和开发版本 - 大功能开发,不想影响到其他人,自己独立开个分支去开发 SVN经典目录结构: - trunk-------------------------开发中的文件 - bran…...
【VB语言】EXCEL中VB宏的应用
【VB语言】EXCEL中VB宏的应用 文章目录 [TOC](文章目录) 前言一、EXCEL-VB1.实验过程2.代码 二、EXCEL-VB 生成.c.h文件1.实验过程2.代码 四、参考资料总结 前言 1.WPS-VB扩展包 提示:以下是本篇文章正文内容,下面案例可供参考 一、EXCEL-VB 1.实验过…...
前端优化可以从哪些方面下手及优化方案
前端优化是提升网页性能、提升用户体验和降低服务器负担的重要手段。可以从多个角度入手,以下是一些常见的优化方向和方案: 1. 性能优化 减少请求数量:尽量减少页面加载时发起的 HTTP 请求,例如使用合并文件(CSS 和 …...
类和对象(5)——抽象类和接口
目录 1. 抽象类 1.1 抽象类的概念 1.2 抽象类语法:abstract关键字 1.3 抽象类的特性 1.4 抽象类的作用 2. 接口 2.1 接口的概念 2.2 接口语法:interface关键字 2.3 接口的实现:implements关键字 2.4 接口的特性 2.5 实现多个接口 …...
海康摄像头IPV6模式,手动,自动,路由公告
海康摄像头DS-2DC7220IW-A 网络设置中的IPv6配置选项。IPv6是互联网协议(IP)的第六版,用于替代IPv4,提供更多的IP地址和改进的网络功能。图片中的选项允许用户选择如何配置设备的IPv6网络连接: 手动:用户可…...
LabVIEW与USB设备开发
开发一台USB设备并使用LabVIEW进行上位机开发,涉及底层驱动的编写、USB通信协议的实现以及LabVIEW与设备的接口设计。本文将详细介绍如何开发USB设备驱动、实现LabVIEW与USB设备的通信以及优化数据传输,帮助用户顺利完成项目开发。下面是一个详细的说明&…...
BY组态:工业自动化的未来,触手可及
在工业4.0的浪潮下,智能化、数字化已成为制造业发展的核心驱动力。作为工业自动化领域的重要工具,组态软件在实现设备监控、数据采集、流程控制等方面发挥着不可替代的作用。然而,传统的组态软件往往存在开发周期长、学习成本高、灵活性不足等…...
深入理解Python多进程编程 multiprocessing
深入理解Python多进程编程 multiprocessing flyfish Python 的 multiprocessing 模块允许创建多个进程,从而可以利用多核处理器的能力来并行执行任务。这意味着程序的不同部分可以在不同的CPU核心上同时运行,极大地提高了处理效率,特别是在…...
使用DeepSeek建立一个智能聊天机器人0.12
为了确保这段代码能够在Windows和Linux系统上都能正常运行,我考虑以下几个方面: 路径分隔符:在Windows和Linux中,文件路径的分隔符不同。Windows使用反斜杠(\),而Linux使用正斜杠(/)。我们可以使用 os.path.join 来处理路径,以确保跨平台兼容性。 消息框:tkinter.…...
基于VLC的Unity视频播放器(三)
关于UMP插件 UMP插件不更新了,我测试在Ubuntu24.04上编辑器和运行时都无法正常播放,在替换lib之后编辑器可以播放,但打包后不行……很奇怪 继续更新了一下UnityVLC 添加了对Linux的支持,勉强都可以播放了…… Win截图 Ubuntu2…...
每日一题——把数字翻译成字符串
把数字翻译成字符串 题目描述示例示例1示例2 题解动态规划代码实现复杂度分析 总结 题目描述 有一种将字母编码成数字的方式:‘a’->1, ‘b’->2, … , ‘z’->26。 现在给一串数字,返回有多少种可能的译码结果。 数据范围:字符串…...
基于状态观测器和物联网基础设施的智能电网高速孤岛检测
论文标题 中文标题: 基于状态观测器和物联网基础设施的智能电网高速孤岛检测 英文标题: High-Speed Islanding Detection in Smart Grids Using a State Observer and IoT Infrastructure 作者信息 Shahid Karim<sup>1,2, *</sup>, Prajo…...
FPGA的星辰大海
编者按 时下风头正盛的DeepSeek,正值喜好宏大叙事的米国大统领二次上岗就业,OpenAI、软银、甲骨文等宣布投资高达5000亿美元“星际之门”之际,对比尤为强烈。 某种程度上,,是低成本创新理念的直接落地。 包括来自开源社区的诸多赞誉是,并非体现技术有多“超越”,而是…...
【Black Mesa】黑山起源用服务器开服多人联机教程
1、登录服务器(百度莱卡云游戏面板) 进入控制面板后会出现正在安装的界面,安装大约10分钟(如长时间处于安装中请联系我们的客服人员) 2、修改端口 看到一下图片的界面时说明服务器已经安装完成,服务器需要…...