51个人网站,哈尔滨营销型网站制作,品牌建设运营方案,网络开发是什么自定义协议和序列化面向字节流全双工自定义协议序列化IO模块socket封装ServerserviceCalClient完整代码接下来我们实现一个网络计算器功能。自定义协议和序列化
前面我们的UDP套接字编程和TCP套接字编程都实现了应用层#xff0c;但没有实现协议。是否会疑惑有没有协议有何不…自定义协议和序列化面向字节流全双工自定义协议序列化IO模块socket封装ServerserviceCalClient完整代码接下来我们实现一个网络计算器功能。自定义协议和序列化前面我们的UDP套接字编程和TCP套接字编程都实现了应用层但没有实现协议。是否会疑惑有没有协议有何不同事实上我们这里要详细谈一谈何为面向字节流以及引出的为什么read、write、recv、send等函数支持全双工。毕竟我们之前实现的TCP的echo时都是read之后直接write你们不觉得很诡异吗难道不会覆盖别的用户的数据面向字节流我们之前说过所谓协议就是大家都约定好的、都看得懂的结构化数据。但是我们传输过程是面向字节流的因此这些结构化数据要全部转化成字节串这个过程称为序列化。随后获取数据之后也要从序列化数据反序列化得到相应的结构化数据这个过程称为反序列化。全双工如上图这些系统调用能支持全双工的根本原因就是他们有两个内核缓冲区。接收和发送调用的是不同的缓冲区因此不会发生相互覆盖的现象。所以我们得出以下结论read、write、recv、send本质就是拷贝函数发送数据的本质是从发送方的发送缓冲区把数据通过协议栈和网络拷贝给接收方的接收缓冲区tcp协议支持全双工和传输控制的原因如上每个缓冲区都有人写入和读取这其实就是一个生产者消费者模型那么IO函数阻塞目的就是维持同步自定义协议我们的发送和接收消息过程是这样但我们有个问题。这些信息在缓冲区中什么时候发送、发送多少、出错了怎么办这些都是由TCP控制的。但是这样面向字节流传输数据就会出现一些问题因为我们写入缓冲区的都是一些字节流TCP不会知道完整报文是多大所以读取的时候会出现报文不完整的现象。因此我们要确保读取的时候是完整的报文否则不做处理。那么这样该如何实现呢这就要依靠我们应用层的协议了我们要给报文加上报头通过报头确定我们的报文大小然后读取报文。例如我们设计一个很简单的报头“len”\r\n这里len是有效载荷的大小\r\n则是标识符。Find到标识符证明读取到了报头那么我们再根据len来读取有效载荷根据上面逻辑我们写入数据和读取数据前应该做序列化上面添加报头和去报头都是针对字节流的那么我们如何将结构化数据序列化呢这个过程并不困难我们也可以自己实现。当然也可以调用已经实现的库函数这里调用jsoncpp来序列化和反序列化。Jsoncpp 是一个用于处理JSON数据的C库。它提供了将JSON数据序列化为字符串以及从字符串反序列化为C数据结构的功能。Jsoncpp是开源的广泛用于各种需要处理JSON数据的C项目中。特性简单易用Jsoncpp提供了直观的API使得处理JSON数据变得简单。高性能Jsoncpp的性能经过优化能够高效地处理大量JSON数据。全面支持支持JSON标准中的所有数据类型包括对象、数组、字符串、数字、布尔值和null。错误处理在解析JSON数据时Jsoncpp提供了详细的错误信息和位置方便开发者调试。那么我们要现在Ubuntu上安装这个第三方库sudoapt-getinstalllibjsoncpp-dev具体函数调用接口我们以后再详谈这里直接使用吧。可以看到我们序列化的逻辑非常直观。反序列化逻辑依旧显然就这样我们实现结构化的发送数据和接收数据封装Request:classRequest{public:Request(){}Request(intx,inty,charoper):_x(x),_y(y),_oper(oper){}~Request(){}boolSerialize(std::string*out){Json::Value root;root[x]_x;root[y]_y;root[oper]_oper;Json::FastWriter writer;std::string swriter.write(root);*outs;returntrue;}boolDeserialize(conststd::stringin){Json::Value root;Json::Reader reader;boolresreader.parse(in,root);_xroot[x].asInt();_yroot[y].asInt();_operroot[oper].asInt();returnres;}voidPrint(){std::cout_xstd::endl;std::cout_ystd::endl;std::cout_operstd::endl;}intX(){return_x;}intY(){return_y;}charOper(){return_oper;}voidSetValue(intx,inty,charoper){_xx;_yy;_operoper;}private:int_x;int_y;char_oper;};Response:classResponse{public:Response():_result(0),_code(0),_desc(success){}boolSerialize(std::string*out){Json::Value root;root[result]_result;root[code]_code;root[desc]_desc;Json::FastWriter writer;std::string swriter.write(root);*outs;returntrue;}boolDeserialize(conststd::stringin){Json::Value root;Json::Reader reader;boolresreader.parse(in,root);_resultroot[result].asInt();_coderoot[code].asInt();_descroot[desc].asString();returnres;}voidPrintResult(){std::coutresult: _result, code: _code, desc: _descstd::endl;}~Response(){}public:int_result;int_code;std::string _desc;};这里为了方便操作我们将Response的成员变量设成公有的。然后我们还可以实现一个工厂模式快速生成智能指针IO模块socket封装在实现IO模块前我们可以先对Socket进行封装毕竟我们用socket很多都是固定的方法。我们这里采用模板方法类的形式封装不同协议下的套接字类其中using SockSPtr std::shared_ptrSocket;接下来我们实现Tcp的套接字封装CreateSocketOrDievoidCreateSocketOrDie()override{_sockfd::socket(AF_INET,SOCK_STREAM,0);if(_sockfd0){LOG(FATAL,socket create error\n);exit(SOCKET_ERROR);}LOG(INFO,socket create success,sockfd:%d\n,_sockfd);}CreateBindOrDievoidCreateBindOrDie(uint16_tport)override{structsockaddr_inlocal;memset(local,0,sizeof(local));local.sin_familyAF_INET;local.sin_porthtons(port);local.sin_addr.s_addrINADDR_ANY;if(::bind(_sockfd,(structsockaddr*)local,sizeof(local))0){LOG(FATAL,bind error\n);exit(BIND_ERROR);}LOG(INFO,bind success,sockfd:%d\n,_sockfd);}CreateListenOrDievoidCreateListenOrDie(intbackloggblcklog)override{if(::listen(_sockfd,gblcklog)0){LOG(FATAL,listen error\n);exit(LISTEN_ERR);}LOG(INFO,listen success\n);}AccepterSockSPtrAccepter(InetAddr*cliaddr)override{structsockaddr_inclient;socklen_t lensizeof(client);intsockfd::accept(_sockfd,(structsockaddr*)client,len);if(sockfd0){LOG(WARNING,accept error\n);returnnullptr;}*cliaddrInetAddr(client);LOG(INFO,get a new link ,client info :%s,sockfd is :%d\n,cliaddr-AddrStr().c_str(),sockfd);returnstd::make_sharedTcpSocket(sockfd);}ConntecorboolConntecor(conststd::stringpeerip,uint16_tpeerport)override{structsockaddr_inserver;memset(server,0,sizeof(server));server.sin_familyAF_INET;server.sin_porthtons(peerport);::inet_pton(AF_INET,peerip.c_str(),server.sin_addr);if(::connect(_sockfd,(structsockaddr*)server,sizeof(server))0){returnfalse;}returntrue;}Recvssize_tRecv(std::string*out)override{charbuffer[4096];ssize_t n::recv(_sockfd,buffer,sizeof(buffer)-1,0);if(n0){buffer[n]0;// 注意是对应读取报文的逻辑*outbuffer;}returnn;}需要注意是对应读取报文的逻辑。Sendssize_tSend(conststd::stringin)override{return::send(_sockfd,in.c_str(),in.size(),0);}然后我们还可以实现一些集成的方法Server封装完Socket就可以实现服务端逻辑了。这个和以前我们实现的类似就是改用我们封装过的Socket罢了。首先还是简单的初始化然后我们实现的是多线程版本还需要封装一个内部类classThreadData{public:SockSPtr _sockfd;TcpServer*_self;InetAddr _addr;public:ThreadData(SockSPtr sockfd,TcpServer*self,constInetAddraddr):_sockfd(sockfd),_self(self),_addr(addr){}};最后就是执行逻辑很好那么我们待会实现具体的service逻辑。先处理一下Main函数逻辑那么接下来我们先实现service的读取报文逻辑。service首先我们要确定的我们的回调函数传入指令然后返回对应的结果。然后根据我们刚才实现的顺序进行iovoidIOExcute(SockSPtr sock,InetAddraddr){std::string packagestreamqueue;while(true){//1.负责读取ssize_t nsock-Recv(packagestreamqueue);if(n0){LOG(INFO,client %s quit or recv error\n,addr.AddrStr().c_str());break;}//2.报文解析提取报头和有效载荷std::string packageDecode(packagestreamqueue);if(package.empty())continue;//此时读到了完整报文autoreqFactory::BuildRequestDefault();//3.反序列化req-Deserialize(package);//4.业务处理autoresp_process(req);//5.序列化应答std::string respjson;resp-Serialize(respjson);//6.添加报头respjsonEncode(respjson);//7.发送回去sock-Send(respjson);}}Cal接下来就是业务处理逻辑。这个其实很简单我们学c语言的时候就会做了。classNetCal{public:NetCal(){}~NetCal(){}std::shared_ptrResponseCalculator(std::shared_ptrRequestreq){autorespFactory::BuildResponseDefault();switch(req-Oper()){case:resp-_resultreq-X()req-Y();break;case-:resp-_resultreq-X()-req-Y();break;case*:resp-_resultreq-X()*req-Y();break;case/:{if(req-Y()0){resp-_code1;resp-_descdiv zero;}else{resp-_resultreq-X()/req-Y();}}break;case%:{if(req-Y()0){resp-_code2;resp-_descmod zero;}else{resp-_resultreq-X()%req-Y();}}break;default:{resp-_code3;resp-_descillegal operation;}break;}returnresp;}};Client最后我们实现客户端代码也是很简单的逻辑#includeiostream#includememory#includeunistd.h#includeSocket.hpp#includeProtocol.hppusingnamespacesocket_ns;intmain(intargc,char*argv[]){if(argc!3){std::cerrUsage:argv[0]server-ip server-portstd::endl;exit(0);}std::string serveripargv[1];uint16_tserverportstd::stoi(argv[2]);SockSPtr sockstd::make_sharedTcpSocket();if(!sock-BuildClientSocket(serverip,serverport)){std::cerrconnect errorstd::endl;exit(1);}srand(time(nullptr)^getpid());conststd::string opers-*/%^!;intcnt3;std::string packagestreamqueue;while(true){// 构建数据intxrand()%10;usleep(x*1000);intyrand()%10;usleep(x*y*100);charoperopers[y%opers.size()];// 构建请求autoreqFactory::BuildRequestDefault();req-SetValue(x,y,oper);// 1. 序列化std::string reqstr;req-Serialize(reqstr);// 2. 添加长度报头字段reqstrEncode(reqstr);std::cout####################################std::endl;std::coutrequest string: \nreqstrstd::endl;// 3. 发送数据sock-Send(reqstr);while(true){// 4. 读取应答responsessize_t nsock-Recv(packagestreamqueue);if(n0){break;}// 5. 报文解析提取报头和有效载荷std::string packageDecode(packagestreamqueue);if(package.empty())continue;std::coutpackage: \npackagestd::endl;// 6. 反序列化autorespFactory::BuildResponseDefault();resp-Deserialize(package);// 7. 打印结果resp-PrintResult();break;}sleep(1);}sock-Close();return0;}运行结果完整代码完整代码奉上