个人虚拟机做网站,一个微信小程序需要多少钱,电子商务网站建设作业,乐清网站建设公司欢迎来到Cefler的博客#x1f601; #x1f54c;博客主页#xff1a;折纸花满衣 #x1f3e0;个人专栏#xff1a;题目解析 #x1f30e;推荐文章#xff1a;C【智能指针】 前言 在正式代码开始前#xff0c;会有一些前提知识引入 目录 #x1f449;#x1f3fb;序列… 欢迎来到Cefler的博客 博客主页折纸花满衣 个人专栏题目解析 推荐文章C【智能指针】 前言 在正式代码开始前会有一些前提知识引入 目录 序列化和反序列化三次握手和四次挥手一些概念知识全双工TCP和UDP对比send和recv TCP通信-使用相同结构化字段传输数据但未真正的序列化和反序列化Protocol.hpp(协议内容)Socket.hpp(封装socket通信的功能)TcpServer.hpp(封装服务端功能)TcpServerMain.ccTcpClientMain.cc实现效果 序列化和反序列化
在网络应用层中序列化Serialization和反序列化Deserialization是将数据转换为可在网络上传输的格式并从网络接收的数据恢复为本地数据结构的过程。
序列化Serialization
序列化是将数据对象转换为一系列字节的过程以便在网络上传输或存储到磁盘上。序列化的目标是创建一个可以被发送到其他系统或进程并且能够被正确解释和还原的数据表示形式。在网络应用中常见的序列化格式包括JSONJavaScript Object Notation、XMLeXtensible Markup Language、Protocol Buffers、MessagePack等。
序列化的过程通常涉及以下步骤
选择序列化格式根据数据的特性和需求选择合适的序列化格式。定义数据结构确定要序列化的数据对象的结构并为其定义序列化规则。将数据转换为字节流根据所选的序列化格式将数据对象转换为字节流。传输或存储将序列化后的字节流发送到网络或者存储到磁盘上。
反序列化Deserialization
反序列化是将序列化后的数据流转换回原始数据对象的过程。在接收到网络传输的数据后需要对其进行反序列化以还原成原始数据对象。
反序列化的过程通常包括以下步骤
接收数据流从网络或者磁盘读取序列化后的数据流。解析数据根据所选的序列化格式解析字节流并将其转换为数据对象。数据还原根据序列化规则和数据结构将解析后的数据转换为原始数据对象。应用数据将还原后的数据对象用于应用程序的后续处理。
序列化和反序列化在网络通信中扮演着重要的角色它们允许不同系统之间以统一的方式进行数据交换同时也提供了数据传输的可靠性和可扩展性。 一个小故事理解序列化和反序列化
故事标题糖果工厂的序列化奇遇
一天糖果工厂的老板决定向全球各地拓展市场他决定使用一种特殊的糖果序列化器来包装他的糖果以确保它们在长途运输中保持新鲜和美味。
序列化器Serializationizer是一台神奇的机器它可以将任何形状、口味的糖果转换成一种特殊的串口糖果这种串口糖果可以轻松地传输到世界各地并在需要时还原为原始的糖果。
老板向工厂的工程师们解释了他的计划然后开始了序列化器的操作。首先他们把一袋五彩斑斓的糖果放进了序列化器中它发出了一声“嘟噜嘟噜”的声音然后从另一端输出了一串光滑而有序的串口糖果。
工程师们快乐地向老板展示他们的成果老板也很满意。于是他们将这些串口糖果装进了特殊的包装盒准备发往全球各地的客户。
然而一名新来的工程师在整理文件时不小心碰到了序列化器的控制台他误触了一个按钮导致序列化器的设置发生了改变。
于是下一个批次的糖果被转换成了一种奇怪的形状颜色也变得混乱不堪。这些串口糖果被送到了全球各地但客户们收到后都表示了不满称他们从未见过如此奇特的糖果。
老板赶紧调查了原因发现了新工程师的失误。他们及时纠正了序列化器的设置重新开始了正常的生产。这次他们确保了每个糖果被正确序列化而不是变成了像乱七八糟的串口糖果。
结局 糖果工厂重新获得了客户的信任全球各地的人们再次享用到了美味的糖果。而那位新工程师也从这个经历中学到了重要的教训在操作序列化器时一定要小心否则可能会引发一场糖果灾难 三次握手和四次挥手
当建立和终止TCP连接时通常会执行三次握手Three-Way Handshake和四次挥手Four-Way Handshake的过程以确保通信的可靠性和正确性。
三次握手Three-Way Handshake 客户端发送同步序列号SYN报文 客户端首先向服务器发送一个带有SYN标志的报文表示客户端请求建立连接并选择一个初始的序列号Sequence Number。 服务器确认连接请求 服务器收到客户端的SYN报文后会发送一个带有SYN和ACK标志的报文表示同意建立连接并确认收到了客户端的连接请求并选择自己的初始序列号。 客户端确认连接 客户端收到服务器的SYNACK报文后会发送一个带有ACK标志的报文给服务器表示确认连接建立。
这样客户端和服务器之间的TCP连接就建立起来了可以开始进行数据传输。 四次挥手Four-Way Handshake 客户端发送关闭连接请求 客户端发送一个带有FIN结束标志的报文给服务器表示客户端不再发送数据但仍愿意接收数据。 服务器确认关闭请求并关闭数据传输 服务器收到客户端的FIN报文后会发送一个带有ACK标志的报文给客户端表示确认关闭请求并停止向客户端发送数据但仍可以接收数据。 服务器发送关闭连接请求 服务器发送一个带有FIN标志的报文给客户端表示服务器也准备关闭连接。 客户端确认关闭请求并关闭连接 客户端收到服务器的FIN报文后会发送一个带有ACK标志的报文给服务器表示确认关闭请求并关闭连接。
这样客户端和服务器之间的TCP连接就完全关闭了。四次挥手的过程中双方都可以发送数据并且在关闭连接后都不能再发送数据。 一个小例子理解三次握手和四次挥手 好的让我们用一种有趣的方式来理解三次握手和四次挥手。 三次握手Three-Way Handshake
想象一下你和朋友约好去吃披萨。这里有个名叫小明的朋友客户端和一个叫披萨店的地方服务器。 小明 “嗨披萨店老板我想要一份披萨”发送SYN 披萨店老板 “好的小明你想要什么口味的披萨”发送SYNACK 小明 “我想要意大利香肠披萨”发送ACK
现在小明和披萨店之间建立了连接披萨店知道了小明的口味准备开始制作披萨。 四次挥手Four-Way Handshake
披萨终于做好了大家都吃得很开心然后就是结束这次美好的披萨时光。 小明 “披萨店老板谢谢你的披萨我不想再点了”发送FIN 披萨店老板 “不客气小明欢迎下次再来披萨店休息了”发送ACK 披萨店老板 “好了披萨店打烊了我们关门了”发送FIN 小明 “明白了披萨店老板再见”发送ACK
这样小明和披萨店之间的交流就结束了披萨店可以关门休息了而小明也满足地离开了。
一些概念知识
全双工
全双工Full Duplex是指数据通信系统中能够同时实现双向通信的能力即在同一时间点上可以同时进行发送和接收数据的操作。这种模式下通信双方能够同时进行双向数据传输而不需要等待对方完成发送或接收操作。
在全双工通信中发送和接收数据的通道是完全独立的彼此之间互不干扰。这意味着通信双方可以在不同的频率或者不同的频道上同时进行通信而不会造成碰撞或数据丢失。
全双工通信通常用于需要高速、实时双向数据传输的场景比如电话通话、视频会议、网络通信等。相比于半双工通信Half Duplex全双工通信具有更高的通信效率和更低的延迟因为它允许发送和接收数据同时进行而不需要等待切换操作。
在网络通信中全双工模式通常通过使用不同的通信频率如Wi-Fi、蓝牙等无线通信、不同的通信信道如以太网的双绞线或者使用不同的时隙如时分多址技术来实现。这种模式在现代通信技术中被广泛应用为用户提供了更流畅、更高效的通信体验。
TCP和UDP对比
TCP传输控制协议和UDP用户数据报协议是两种常用的网络传输协议它们在数据传输时有着不同的特点和适用场景 连接性 TCP是面向连接的协议它在通信双方建立连接后才能进行数据传输确保数据的可靠性和顺序性。UDP是无连接的协议通信双方无需建立连接即可直接发送数据因此不保证数据的可靠性和顺序性。 可靠性 TCP提供可靠的数据传输通过序号、确认和重传机制来确保数据的完整性和可靠性保证数据不会丢失或损坏。UDP不提供可靠性保证数据包可能会丢失、重复或者乱序因此在一些实时性要求高、但对数据完整性要求较低的场景下使用较多。 流量控制(面向字节流)和拥塞控制 TCP通过流量控制和拥塞控制机制来调节数据传输速率以避免网络拥塞和数据丢失。UDP不提供流量控制和拥塞控制数据传输速率由发送方直接决定可能会导致网络拥塞。 适用场景 TCP适用于需要可靠数据传输和顺序传输的场景如文件传输、网页浏览、电子邮件等。UDP适用于实时性要求高、但对数据完整性要求较低的场景如音频和视频流、在线游戏、实时通信等。 开销 TCP的头部开销较大包含了序号、确认、窗口大小等信息因此在传输小量数据时可能会存在较大的开销。UDP的头部开销较小只包含了源端口、目标端口、长度和校验和等基本信息因此在传输小量数据时开销较小。
总的来说TCP提供了可靠的数据传输和顺序传输适用于对数据完整性要求高的场景而UDP提供了更快速的数据传输和更低的开销适用于实时性要求高、但对数据完整性要求较低的场景。选择使用哪种协议取决于具体的应用需求和性能要求。
send和recv
当编写网络程序时常用的函数之一是send和recv它们通常用于在TCP连接上发送和接收数据。
send 函数
功能 用于在已建立的连接上发送数据。语法 send(socket, data, flags) socket指定发送数据的套接字。data要发送的数据。flags指定发送操作的可选标志。
recv 函数
功能 用于从已建立的连接上接收数据。语法 recv(socket, buffersize, flags) socket指定接收数据的套接字。buffersize指定接收缓冲区的大小。flags指定接收操作的可选标志。
这两个函数在TCP编程中非常常见它们允许程序在客户端和服务器之间进行双向通信。 与sendto 函数和 recvfrom 函数的区别
sendto 函数 用于在无连接的套接字上发送数据。通常用于UDP套接字。 它需要指定目标地址和端口。 recvfrom 函数 用于从无连接的套接字上接收数据。通常用于UDP套接字。 它返回发送数据的源地址和端口。 在网络编程中write和read函数通常用于TCP套接字因此它们也是基于已连接的。 总的来说send和recv函数适用于TCP连接而sendto和recvfrom函数适用于UDP套接字。前者是基于连接的后者是无连接的。
TCP通信-使用相同结构化字段传输数据但未真正的序列化和反序列化
代码目录
Protocol.hpp(协议内容)
#pragma once#includeiostream
#includememory
using namespace std;class Request
{
public:Request(){}Request(int x,int y,char op):_data_x(x),_data_y(y),_oper(op){}void Inc(){_data_x;_data_y;}void Debug(){cout_data_x: _data_xendl;cout_data_y: _data_yendl;cout_oper: _operendl;}private:int _data_x;int _data_y;char _oper;//操作数
};class Response
{
public:Response(){}Response(int result,int code):_result(result),_code(code){}
private:int _result;int _code;
};//工厂模式建造类设计模式,直接返回指针对象
class Factory
{
public:shared_ptrRequest BuildRequest(){shared_ptrRequest req make_sharedRequest();return req;}shared_ptrRequest BuildRequest(int x,int y,char op){shared_ptrRequest req make_sharedRequest(x,y,op);return req;}shared_ptrResponse BuildResponse(){shared_ptrResponse resp make_sharedResponse();return resp;}shared_ptrResponse BuildResponse(int result,int code){shared_ptrResponse resp make_sharedResponse(result,code);return resp;}};Socket.hpp(封装socket通信的功能)
#pragma once #includeiostream
#includestring
#includecstring
#includeunistd.h
#includesys/types.h
#includesys/socket.h
#includenetinet/in.h
#includearpa/inet.h#define Convert(addrptr) ((struct sockaddr*)addrptr)using namespace std;namespace Net_Work
{const static int defaultsockfd -1;const int backlog 5;enum{SocketError 1,BindError,ListenError,};//封装一个基类Socket接口类class Socket{public:virtual ~Socket(){}virtual void CreateSocketOrDie() 0;//创建一个套接字virtual void BindSocketOrDie(uint16_t port) 0;//套接字进行绑定网络信息virtual void ListenSocketOrDie(int backlog) 0;//进行监听virtual Socket* AcceptConnection(string * peerip,uint16_t* peerport)0;//接收连接并返回一个新的套接字virtual bool ConnectServer(string peerip,uint16_t peerport)0;//连接服务端virtual int GetSockFd() 0;//返回套接字描述符virtual void SetSockFd(int sockfd) 0;//virtual void CloseSocket() 0;//关闭套接字public:void BuildListenSocketMethod(uint16_t port,int backlog)//创建一个监听服务{//1.创建套接字CreateSocketOrDie();//2.套接字进行绑定网络信息BindSocketOrDie(port);//3.开始监听ListenSocketOrDie(backlog);}bool BuildConnectSocketMethod(string serverip,uint16_t serverport)//创建一个连接服务{//1.创建套接字CreateSocketOrDie();return ConnectServer(serverip,serverport);}void BuildNormalSocketMethod(int sockfd){SetSockFd(sockfd);}};//实现Tcp套接字class TcpSocket:public Socket{public:TcpSocket(int sockfd defaultsockfd ):_sockfd(sockfd){}~TcpSocket(){}/void CreateSocketOrDie() override//创建一个套接字{_sockfd socket(AF_INET,SOCK_STREAM,0);if(_sockfd0)exit(SocketError);}void BindSocketOrDie(uint16_t port) override//套接字进行绑定网络信息{//本地网络信息初始化struct sockaddr_in local;memset(local,0,sizeof(local));local.sin_family AF_INET;local.sin_addr.s_addr INADDR_ANY;//服务端的ip由本地随机绑定local.sin_port htons(port);//开始绑定int n bind(_sockfd,Convert(local),sizeof(local));if(n0) exit(BindError);}void ListenSocketOrDie(int backlog) override//进行监听{int n listen(_sockfd,backlog);if(n0) exit(ListenError);}Socket* AcceptConnection(string * peerip,uint16_t* peerport)override//接收连接{struct sockaddr_in peer;//用来存储客户端的地址信息socklen_t len sizeof(peer);int newsockfd accept(_sockfd,Convert(peer),len);if(newsockfd0)return nullptr;*peerport ntohs(peer.sin_port);//网络序列本地化*peerip inet_ntoa(peer.sin_addr);Socket* s new TcpSocket(newsockfd);return s;}bool ConnectServer(string serverip,uint16_t serverport)override//连接服务端{struct sockaddr_in server;//存储服务端的地址信息memset(server,0,sizeof(server));server.sin_family AF_INET;server.sin_port htons(serverport);server.sin_addr.s_addr inet_addr(serverip.c_str());//ip网络字节序化,4字节int n connect(_sockfd,Convert(server),sizeof(server));if(n0)return true;elsereturn false;}int GetSockFd() override//返回套接字描述符{return _sockfd;}void SetSockFd(int sockfd) override//{_sockfd sockfd;}void CloseSocket() override//关闭套接字{if(_sockfddefaultsockfd)close(_sockfd);}private:int _sockfd;};
};TcpServer.hpp(封装服务端功能)
#pragma once#includeSocket.hpp
#includepthread.h
#includefunctionalusing func_t functionvoid(Net_Work::Socket* sockp);class TcpServer;class ThreadData
{
public:ThreadData(TcpServer* tcp_this,Net_Work::Socket* sockp):_this(tcp_this),_sockp(sockp){}
public:TcpServer* _this;//TcpServer的指针对象Net_Work::Socket* _sockp;//套接字指针对象
};class TcpServer
{
public:TcpServer(uint16_t port,func_t handler_request):_port(port),_listensocket(new Net_Work::TcpSocket()),_hanlder_request(handler_request){_listensocket-BuildListenSocketMethod(_port,Net_Work::backlog);//开启监听事务}static void * ThreadRun(void* args)//因为pthread_create要求方法参数中的参数必须只有一个void*//所以必须变为静态否则成员函数第一个参数默认隐式为this指针{//因为执行的是多线程这里我们也没有封装线程的自动回收//所以为了不发生线程阻塞我们要让当前线程与主线程分离不影响主线程并且自己做完任务自己回收pthread_detach(pthread_self());ThreadData* td static_castThreadData*(args);td-_this-_hanlder_request(td-_sockp);//执行_hanlder_request方法td-_sockp-CloseSocket();//关闭accept的新套接字delete td-_sockp;//销毁指针delete td;return nullptr;}void Loop(){while(true){string peerip;uint16_t peerport;Net_Work::Socket* newsocket _listensocket-AcceptConnection(peerip,peerport);//接收客户端信息if(newsocketnullptr) continue;cout获取一个新连接,sockfd:newsocket-GetSockFd()client info: peerip peerportendl;//用完后关闭newsocket//newsocket-CloseSocket(); //使用多线程进行处理任务pthread_t tid;ThreadData* td new ThreadData(this,newsocket);pthread_create(tid,nullptr,ThreadRun,td);//线程创建并执行相对应任务}}~TcpServer(){delete _listensocket;}private:uint16_t _port;Net_Work::Socket* _listensocket;public:func_t _hanlder_request;//request执行方法};TcpServerMain.cc
#includeProtocol.hpp
#includeSocket.hpp
#includeTcpServer.hpp
#includememoryusing namespace Net_Work;
void HandlerRequest(Socket* sockp)
{while(true){struct Request req;//用来存储客户端发来的需求信息recv(sockp-GetSockFd(),req,sizeof(req),0);//接收req.Debug();//打印信息}
}int main(int argc,char* argv[])
{if(argc ! 2){cout Usage : argv[0] port std::endl;return 0;}uint16_t localport stoi(argv[1]);unique_ptrTcpServer svr (new TcpServer(localport,HandlerRequest));//unique_ptr只能支持移动构造svr-Loop();//server开始不断获取新连接return 0;
}TcpClientMain.cc
#includeProtocol.hpp
#includeSocket.hppint main(int argc,char* argv[])
{if(argc ! 3){cout Usage : argv[0] serverip serverport std::endl;return 0;}string serverip argv[1];uint16_t serverport stoi(argv[2]);Net_Work::Socket* s new Net_Work::TcpSocket();if(!s-BuildConnectSocketMethod(serverip, serverport)){cerr connect serverip : serverport failed std::endl;}cout connect serverip : serverport success std::endl;unique_ptrFactory factory make_uniqueFactory();//创建一个工厂对象指针(后续可以生产需求和回应)工厂只能有一个所以用unique_ptr指针shared_ptrRequest req factory-BuildRequest(10,20,);while(true){req-Inc();send(s-GetSockFd(),(*req),sizeof(*req),0);//将需求信息发送给服务端sleep(1);}s-CloseSocket();//关闭套接字return 0;
}实现效果 如上便是本期的所有内容了如果喜欢并觉得有帮助的话希望可以博个点赞收藏关注❤️ ,学海无涯苦作舟,愿与君一起共勉成长