西安网站建设 翼驰,室内设计网站排行榜前十名知乎,做网站会用到的代码单词,辽宁省建设厅网站更新认识协议
协议#xff08;Protocol#xff09; 是一种通信规则或标准#xff0c;用于定义通信双方或多方之间如何交互和传输数据。在计算机网络和通信系统中#xff0c;协议规定了通信实体之间信息交换的格式、顺序、定时以及有关同步等事宜的约定。简易来说协议就是通信…认识协议
协议Protocol 是一种通信规则或标准用于定义通信双方或多方之间如何交互和传输数据。在计算机网络和通信系统中协议规定了通信实体之间信息交换的格式、顺序、定时以及有关同步等事宜的约定。简易来说协议就是通信双方所约定好的结构化字段。 序列化与反序列化的认识
但在网络传输中由于不同主机和不同系统对于数据存储的大小端差异所以在传输结构化字段的时候并不能保证每个结构化的成员数据都能够准确的对应上。
所以一般会采用将结构化的字段内容进行序列化成一个字符串然后再通过网络发送出去接收端再将数据反序列化接收也就是将各个序列化的结构字段数据提取出来。 自定义协议实现网络版本计算器
在自定义协议时必须让客户端与服务器都能够看到同一份协议字段这样才能在接收数据的时候按照规定的格式进行准确无误的接收。
对于序列化的字段格式采用空格作为分隔符。而反序列化的时候就可以通过空格分隔符将数据提取出来。
自定义协议序列化与反序列化和报头
对于我们实现的协议不仅仅有序列化结构体字段其实还有包头的封装因为tcp是有连接的数据是流式传输的所以传输过程并不是一发一收的形式所以报头数据就可以分离每一次的收发因为报头中有一个字段是存放序列化字段的长度。
#pragma once
#include iostream
#include string
#include jsoncpp/json/json.h
using namespace std;// 模拟定协议const string sepa ;
const string line_break \n;// 添加报头数据和解报头
void Encode(string mes)
{int len mes.size();string ret to_string(len) line_break mes line_break;mes ret;
}bool Decode(string package, string ret)
{//len\n123 wuwu\n// 先判断收到的数据是否完整int len package.size();int pos package.find(line_break); // 指向第一个换行符if (pos string::npos)return false;int pos1 package.find(line_break, pos line_break.size()); // 指向第二个换行符if (pos1 string::npos)return false;// 解包后的数据ret package.substr(pos line_break.size(), pos1 - pos - 1);// 去掉被读走的数据package package.substr(pos1 line_break.size());return true;
}class Request
{friend class Cal;public:Request() {}Request(int x, int y, char op): _x(x), _y(y), _oper(op){}void info(){cout _x _oper _y ? endl;}void Serialize(string out) // 序列化{out to_string(_x) sepa _oper sepa to_string(_y);}//x yvoid Deserialize(const string s) // 反序列化{int begin 0;int end s.find(sepa, begin);_x stoi(s.substr(begin, end - begin));begin end sepa.size(); // 加的1其实就是 的长度end s.find(sepa, begin);_oper s.substr(begin, end - begin)[0];begin end sepa.size();_y stoi(s.substr(begin));}private:int _x;int _y;char _oper;
};class Response
{
public:Response(){}Response(int re, string ret_info): _result(re), _ret_info(ret_info){}void Info(){cout result _result ( _ret_info ) endl;}void Serialize(string out) // 序列化{out to_string(_result) sepa _ret_info;}//_result _ret_infovoid Deserialize(const string s) // 反序列化{int begin 0;int end s.find(sepa, begin);_result stoi(s.substr(begin, end - begin));begin end sepa.size(); // 加的1其实就是分隔符的长度_ret_info s.substr(begin);}private:int _result; // 保存结果string _ret_info; // 结果信息
};
封装套接字
封装套接字就是实现代码分离使得可读性更高还有就是省的以后再写。
#pragma once
#include iostream
#include cstdint
#include string
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include unistd.h
#include stdlib.h
#include string.h
#include thread
#include functional
#include memory
using namespace std;#define default_backlog 5// 设计模式模版方法类
class my_socket // 抽象类
{
public:virtual void Creat_socket() 0; // 纯虚函数,必须重写virtual void Bind(int port) 0;virtual void Listen(int backlog) 0;virtual my_socket *Accept(string ip, uint16_t port) 0;virtual void Connect(string ip, uint16_t port) 0;virtual int Get_sockfd() 0;virtual void Close() 0;virtual void Recv(string ret, int len) 0;public:void tcpserver_socket(uint16_t port, int backlog default_backlog){Creat_socket();Bind(port);Listen(backlog);// 因为服务会返回的执行accept获取连接所以选择分离}void tcpclient_socket(string ip, uint16_t port){Creat_socket();Connect(ip, port);}
};class tcp_socket : public my_socket // 继承并重写虚函数
{
public:tcp_socket(){}tcp_socket(int sockfd): _sockfd(sockfd){}virtual void Creat_socket(){_sockfd socket(AF_INET, SOCK_STREAM, 0);if (_sockfd 0){cerr 创建套接字失败 endl;exit(-1);}}virtual void Bind(int port){struct sockaddr_in local;local.sin_family AF_INET;local.sin_port htons(port);local.sin_addr.s_addr INADDR_ANY;int n bind(_sockfd, (sockaddr *)local, sizeof(local));if (n 0){cerr 绑定套接字失败 endl;exit(-1);}}virtual void Listen(int backlog){int n listen(_sockfd, backlog);if (n -1){cerr 监听套接字失败 endl;exit(-1);}}virtual my_socket *Accept(string ip, uint16_t port){while (1){struct sockaddr_in client;socklen_t len sizeof(client);int newsockfd accept(_sockfd, (sockaddr *)client, len); // 监听套接字不关闭可以用来接收多个客户端的连接if (newsockfd 0){cerr 获取连接失败 endl;}port ntohs(client.sin_port);char buffer[64];inet_ntop(AF_INET, client.sin_addr, buffer, sizeof(buffer)); // 1.网络转本机 2.4字节ip转字符串ipip buffer;if (newsockfd 0){cerr 接收套接字失败 endl;}elsecout 接收套接字成功 endl;return new tcp_socket(newsockfd);}}virtual void Connect(string ip, uint16_t port){struct sockaddr_in server;server.sin_family AF_INET; // socket inet(ip) 协议家族,绑定网络通信的信息server.sin_port htons(port); // 将主机端口号转成网络// server.sin_addr.s_addr inet_addr(ip.c_str()); // 转成网络序列的四字节ipinet_pton(AF_INET, ip.c_str(), server.sin_addr); // 转成网络序列的四字节ipint n connect(_sockfd, (sockaddr *)server, sizeof(server)); // 自动bindif (n ! 0){cerr 连接失败 endl;exit(-1);}elsecout 连接成功 endl;}virtual int Get_sockfd(){return _sockfd;}virtual void Close(){if (_sockfd 0)close(_sockfd);}virtual void Recv(string ret, int len){char stream_buffer[len];int n recv(_sockfd, stream_buffer, len - 1, 0);if (n 0){stream_buffer[n] 0;ret stream_buffer; // ret在读取之前可能还有内容残留}else{exit(0);}}private:int _sockfd;
};计算器代码
计算器类实现的功能就是服务于服务端的将客户端发送的请求进行计算并且同时将计算出的结果与返回信息都存到协议中的response类中所以服务端就可以直接进行序列化从而将数据发送给客户端。
#pragma once
#include protocol.hclass Cal
{
public:Cal(Request req): _x(req._x), _y(req._y), _oper(req._oper){}Response cal(){switch (_oper){case :_result _x _y;break;case -:_result _x - _y;break;case *:_result _x * _y;break;case /:{if (_y 0)_retinfo 除数为0,结果无意义;else_result _x / _y;}break;case %:{if (_y 0)_retinfo 模0,结果未定义;else_result _x % _y;}break;default:_retinfo 结果无效,未录入该运算符;break;}return {_result, _retinfo};}string Answer(){return result to_string(_result) ret_info _retinfo;}private:int _x;int _y;char _oper;int _result;string _retinfo 结果无误;
};服务端代码
服务端就是负责接收客户端的信息并进行处理然后将处理结果发送回去为了满足多客户端的请求服务端会采用创建线程的方式来进行与客户端对接而服务端的主线程就负责实现accept接受客户端发送的连接请求。
#pragma once
#include socket.h
#include calculate.husing func_t functionvoid(my_socket *); // 执行任务的方法class tcp_server
{
public:tcp_server(uint16_t port, func_t f): _port(port), _sv(new tcp_socket()), _func(f){_sv-tcpserver_socket(port);}void thread_run(my_socket *socket) // 线程执行区域{_func(socket);socket-Close(); // 运行完毕就直接关闭accept返回的套接字描述符}void loop(){while (1){string client_ip;uint16_t client_port;my_socket *socket _sv-Accept(client_ip, client_port); // 接收套接字cout 获取新连接,ip client_ip port client_port endl;// sleep(3);// _sv-Close();//监听套接字就是用来接收多个客户端的连接// 创建线程执行任务thread t(std::bind(tcp_server::thread_run, this, placeholders::_1), socket);t.detach(); // 线程分离// t.join();}}~tcp_server(){delete _sv;}private:my_socket *_sv;uint16_t _port;func_t _func;
};
#include tcp_server.h
#include protocol.hvoid deal(my_socket *socket) // 存的套接字描述符就是accept返回值
{string buffer;while (1){// 1.数据读取socket-Recv(buffer, 100); // 将每一次序列化的数据都读进buffer里string msg;string total_info;// 2.解包装(将所有独读到的数据都解包最后完成后一起再发送出去)while (Decode(buffer, msg)) // 此时buffer会存在残留数据{// 3.反序列化buffer,Request rq;rq.Deserialize(msg);// 4.数据读取完毕可以进行处理Cal c(rq);Response rsp c.cal(); // 计算结果存到rsp里// 5.将处理结果返回给客户端(需要进行序列化和加包)string s;rsp.Serialize(s);Encode(s);total_info s;}send(socket-Get_sockfd(), total_info.c_str(), total_info.size(), 0); // 任务发送给服务器}
}
int main(int argc, char *argv[])
{if (argc ! 2){cout 格式错误\n正确格式: argv[0] port endl;}uint16_t port atoi(argv[1]);// tcp_server tsv(port);unique_ptrtcp_server tsv(new tcp_server(port, deal));tsv-loop(); // accept客户端套接字
}客户端代码
客户端也同样采用创建线程的方式来进行发送数据与接收数据这其中一个线程专门发送数据一个线程专门接收数据这其中的好处就是不会受到干扰如果都通过一个线程来完成的话就会导致数据必须是一发一收的方式并不满足数据流式传输。
#include socket.h
#include protocol.hstring ops -*/%|^;void thread_run(my_socket *clt)
{while (1){// 1.读取服务端处理后的信息string buffer;string msg;clt-Recv(buffer, 100); // 将每一次序列化的数据都读进buffer里// 2.解包装Decode(buffer, msg);// 3.反序列化msg,Response rsp;rsp.Deserialize(msg);rsp.Info();sleep(3);}
}
int main(int argc, char *argv[])
{srand((unsigned int)time(nullptr));if (argc ! 3){cout 格式错误\n正确格式: argv[0] ip port endl;}string ip argv[1];uint16_t port atoi(argv[2]);my_socket *clt new tcp_socket();clt-tcpclient_socket(ip, port); // 连接服务端套接字//创建线程专门负责接收信息thread reciver(thread_run,clt);reciver.detach();while (1){int x rand() % 100;int y rand() % 100;char oper ops[rand() % ops.size()];Request rq(x, y, oper);rq.info(); // 向客户端打印任务// 1.进行序列化并打包 发送数据string s;rq.Serialize(s);Encode(s);send(clt-Get_sockfd(), s.c_str(), s.size(), 0); // 任务发送给服务器sleep(1);}delete clt;
} 认识JSON
JSON是一种成熟的序列化反序列化方案。需要使用的话要安装JSON库
#include iostream
#include string
#include jsoncpp/json/json.h
using namespace std;
// using namespace Json;int main()
{// Json::Value 万能类型Json::Value root;root[a] 10;root[b] 20;root[哈哈哈] 嘎嘎嘎;Json::Value tmp;tmp[who] cr;tmp[age] 20;root[id] tmp;// Json::FastWriter writer;//行式风格序列化Json::StyledWriter writer; // 样式风格序列化string s writer.write(root); // 将root结构化字段进行序列化操作cout s endl;cout ------------------------------------- endl;// 反序列化Json::Value rets;Json::Reader reader;bool ret reader.parse(s, root); // 调用反序列化方法将序列化的数据s反序列到root里if (ret) // 解析root{int a root[a].asInt();int b root[b].asInt();string st root[哈哈哈].asString();tmp root[id];cout a b st tmp endl;}
} 目录
认识协议 序列化与反序列化的认识
自定义协议实现网络版本计算器
自定义协议序列化与反序列化和报头
封装套接字
计算器代码
服务端代码
客户端代码
认识JSON