96SEO 2026-02-19 12:37 0
。

在前面我们说过父亲和儿子约定打电话的例子#xff0c;不过这是感性的认识序列化反序列化
“约定”。
在前面我们说过父亲和儿子约定打电话的例子不过这是感性的认识今天我们理性的认识一下协议。
结构化的数据就比如说我们在使用QQ群聊时除了消息本身、还能看见头像、时间、昵称。
这些东西都要发给对方。
这些东西都是一个个字符串难道是把消息、头像、时间、昵称都单独发给对方吗那分开发的时候未来群里有成百上千名人大家都发全都分开发接收方还要确定每一部分是谁的进行匹配那这样太恶心了。
实际上这些信息可不是一个个独立个体的而是一个整体。
为了理解暂时当作多个字符串。
把多个字符串形成一个报文或者说打包成一个字符串(方便理解其实是一个字节流)然后在网络中发送。
多变一方便未来在网络里整体发送。
而把多变一的过程我们称之为序列化。
经过序列化的过程变成一个整体后发到网络里经过网络传输发送给对方发是整体当作一个字符串发的。
接收方收的也是整体收的所以收到一个报文或者说字符串。
但是收到的字符串有什么东西我怎么知道qq作为上层要的是谁发的、什么时候、发的什么具体的信息所以接收方收到这个整体字符串后必须把它转成多个字符串这种一变多的过程我们称之为反序列化。
业务结构数据在发送网络中的时候先序列化在发送收到的一定是序列字节流要先进行反序列化然后才能使用。
刚才说过这里用多个字符串不太对只是为了理解实际上未来多个字符串实际是一个结构体。
是以结构体(结构化的数据)作为体现的然后把这个结构体转成一个字符串同理对方收到字符串然后转成对应的结构化的数据。
为什么要把字符串转成结构化数据呢未来这个结构化的数据一定是一个对象然后使用它的时候直接对象.url
未来我们在应用层定协议就是这种结构体类型目的就是把结构化的对象转换成序列化结构发送到网络里然后再把序列化结构转成对应的结构体对象然后上层直接使用对象进行操作
这样光说还是不太理解下面找一个应用场景加深理解刚才的知识。
所以我们写一个网络版计数器。
里面体现出业务协议序列化反序列化在写TCP时要注意TCP时面向字节流的接收方如何保证拿到的是一个完整的报文呢而不是半个、多个这里我们都通过下面写代码的时候解决。
而UDP是面向数据报的接收方收到的一定是一个完整的报文因此不考虑刚才的问题。
_op(op){}public://这里就是我们的约定未来形成
_result(result){}public://约定int
};以前我们写过服务器的代码有些东西就直接用了这里服务器是多进程版本。
如果有新链接来了我们就进行处理因此给一个handlerEntry函数这里没写在类里主要是为了解耦。
并且也把业务逻辑进行解耦给一个回调函数handlerEntry函数你做你的序列化反序列化等一系列工作和我没关系。
我只做我的工作就行了。
resp...;//callback(req,resp);//
error);continue;}logMessage(NORMAL,
这里就是一个sock,未来通信我们就用这个sock,tcp面向字节流的,后序全部都是文件操作!//
0){close(_listensock);handlerEntry(sock,
func);close(sock);exit(0);}close(sock);}}//。
。
。
private:uint16_t
根据req进行业务处理填充resp不用管理任何读取和写入序列化和反序列化等任何细节
2){Usage(argv[0]);exit(USAGG_ERR);}uint16_t
atoi(argv[1]);unique_ptrCalServer
CalServer(serverport));tsv-initServer();tsv-start(Cal);return
}整体就是这样的逻辑我们现在把软件分成三层。
第一层获取链接进行处理第二层handlerEntery进行序列化反序列化等一系列工作第三层进行业务处理callback。
【一个】完整的请求因为TCP是面向字节流的我们保证不了所以要明确
TCP有自己内核级别的发送缓冲区和接收缓冲区而应用层也有自己的缓冲区我们自己写的代码调用readwrite发送读取使用的buffer就是对应缓冲区。
其实我们调用的所有的发送函数根本就不是把数据发送到网络中
write只是把数据从应用层缓冲区拷贝到TCP发送缓冲区由TCP协议决定什么时候把数据发送到网络发多少出错了怎么办。
所以TCP协议叫做传输控制协议
最终数据经过网络发送被服务端放到自己的接收缓冲区里然后我们在应用层调用read实际在等接收缓冲区里有没有数据有数据就把数据拷贝应用层的缓冲区。
没有数据就是说接收缓冲区是空的read就会被阻塞。
tcp发送的本质其实就是将数据从c的发送缓冲区拷贝到s的接收缓冲区。
tcp发送的本质其实就是将数据从s的发送缓冲区拷贝到c的接收缓冲区。
c-s发并不影响s-c发因为用的是不同的成对的缓冲区所以tcp是全双工的
这里主要想说的是tcp在进行发送数据的时候发收方一直发数据但是对方正在做其他事情来不及读数据所以导致接收方的接收缓冲区里面存在很多的报文因为是TCP面向字节流的所以这些报文是挨在一起最终读的时候怎么保证读到的是一个完整的报文交给上层处理而不是半个多个。
就是因为我们有接收缓冲区的存在因此首先我们要解决读取的问题。
我们给每个报文前面带一个有效载荷长度的字段未来我先读到这个长度根据这个长度在读取若干字节这样就能读取到一个报文一个能读到n个也能读到。
有效载荷里面是请求或者响应序列化的结果。
还有不管是请求和响应未来都需要做序列化和反序列化因此在这两个类中都要包含这两个函数。
};关于这个序列化我们可以自己写也可以用现成的不过我们是初学先自己写感受一下等都写完我们在介绍现成的。
req_str;//代表报文分离之后读取到的字符串(有效载荷)//
(!req.deserialize(req_str))return;//
根据req进行业务处理填充resp不用管理任何读取和写入序列化和反序列化等任何细节
{//req已经有结构化完成的数据啦你可以直接使用resp._exitcode
DIV_ERR;elseresp._resultreq._x/req._y;}break;case
MOD_ERR;elseresp._resultreq._x%req._y;}break;default:resp._exitcode
OPER_ERR;break;}}现在对响应进行序列化反序列化
req_str;//代表报文分离之后读取到的字符串(有效载荷)//
(!req.deserialize(req_str))return;//
(!resp.serialize(resp_str))return;//
首先得是一个报文因此我们把序列化形成的字符串加上特定的格式形成一个报文
to_string(text.size());send_string
req_str;//代表报文分离之后读取到的字符串(有效载荷)//
(!req.deserialize(req_str))return;//
(!resp.serialize(resp_str))return;//
}新的接口函数send和write一模一样不过多了一个参数flags发送方式默认为0后面解释。
y\r\n我们在写一个recvpackge读取函数让它进行处理。
只要这个函数返回了走到下面一定是读取到了一个完整的报文。
然后对这个报文进行处理只要有效载荷。
inbuffer;//每次从缓冲区拿到的数据放到inbuffer里while
【一个】完整的请求//把从sock读取的数据最后放到inbuffer里从inbuffer里面拿到一个完整的请求放到req_textif
我们保证我们req_text里面一定是一个完整的请求content_len\r\nx
buffer;//可能一次没用读到完整的报文,这里使用的是auto
没读到一个完整报文continue;//inbuffer.size()
//如果inbuffer.size()大于或等于一个完整报文的长度说明inbuffer里面至少有一个完整报文string
total_len)//也没有读到一个完整报文continue;//
total_len);//拿到一个完整报文inbuffer.erase(0,
total_len);//把拿走的报文从inbuffer缓冲区里减去break;}else{return
这里我们增加一些打印信息最后运行可以看的到序列化反序列的过程。
to_string(text.size());send_string
buffer;//可能一次没用读到完整的报文,这里使用的是auto
没读到一个完整报文continue;//inbuffer.size()
//如果inbuffer.size()大于或等于一个完整报文的长度说明inbuffer里面至少有一个完整报文string
total_len);//拿到一个完整报文inbuffer.erase(0,
total_len);//把拿走的报文从inbuffer缓冲区里减去cout
1,SOCKET_ERR,BIND_ERR,LISTEN_ERR
我们保证我们req_text里面一定是一个完整的请求content_len\r\nx
(!req.deserialize(req_str))return;//
(!resp.serialize(resp_str))return;cout
error);exit(SOCKET_ERR);}logMessage(NORMAL,
sizeof(local));local.sin_family
htons(_port);local.sin_addr.s_addr
error);exit(BIND_ERR);}logMessage(NORMAL,
error);exit(LISTEN_ERR);}logMessage(NORMAL,
error);continue;}logMessage(NORMAL,
这里就是一个sock,未来通信我们就用这个sock,tcp面向字节流的,后序全部都是文件操作!//
0){close(_listensock);handlerEntery(sock,
func);close(sock);exit(0);}close(sock);}}~CalServer(){}private://
根据req进行业务处理填充resp不用管理任何读取和写入序列化和反序列化等任何细节
req已经有结构化完成的数据啦你可以直接使用resp._exitcode
DIV_ERR;elseresp._resultreq._x/req._y;}break;case
MOD_ERR;elseresp._resultreq._x%req._y;}break;default:resp._exitcode
2){Usage(argv[0]);exit(USAGG_ERR);}uint16_t
atoi(argv[1]);unique_ptrCalServer
CalServer(serverport));tsv-initServer();tsv-start(Cal);return
sizeof(server));server.sin_family
htons(_serverport);server.sin_addr.s_addr
inet_addr(_serverip.c_str());if
reqParseLine(msg);//构建Request对象string
req_str;req.serialize(req_str);//序列化string
send_stringEnlenth(req_str);//加报头send(_sockfd,send_string.c_str(),send_string.size(),0);//
recvpackge里我们是按照特殊格式进行读取的因此这里直接用//
resp_text,resp_str;if(!recvpackge(_sockfd,inbuffer,resp_text))continue;if(!Delenth(resp_text,resp_str))continue;//
resp;resp.deserialize(resp_str);coutexitcode:
resp._resultendl;}}}//这里有各种方法可以选自己喜欢的处理方式Request
i0;imsg.size();i){if(isdigit(msg[i])
req;req._xstoi(left);req._ystoi(right);req._opmsg[pos];return
};现在服务端和客户端都写好了运行一下这里我们打印出一些信息能看到序列化和反序列化的过程。
TCP是面向字节流的需要考虑保证读到的是一个完整报文、获取有效载荷、序列化、反序列化。
上面是我们手写序列化和反序列化和协议帮助我们理解这里序列化和反序列化自己写的有的挫。
对于序列化和反序列化有现成的解决方案绝对不会自己去写。
但是没说协议不能自己定
里面属性是以K和V的形式呈现出来的键值对未来我们可以以KV形式设置提取可以以KV形式提取。
//安装c的json库下面在代码里我们使用了条件编译方便自己用Json和自己序列化反序列方案切换。
编译的时候想用Json方案-DMYSELF不想用#-DMYSELF
calservercalclient:CalClient.ccg
root;//Json::Value是一个万能对象用来接收任意类型//Json是KV的格式因此我们要给它设置KV方便后面提取它//x虽然是个整型但是实际在保存到Json里它会把所有内容转成字符串root[first]
_op;//Json形成字符串有两种风格我们选其中一种Json::FastWriter
write.write(root);//里面自动组序列化返回值是一个string
root);//从in这个流中做反序列化放到root里//根据K提取V//不过Json默认把所有数据当成字符串_x
root[first].asInt();//把字符串转成对于的类型_y
root[oper].asInt();//char本来就是按ASCII码存的这里也把当它当成整数#endifreturn
自定义协议说人话就是定义一个结构化的对象有了这个结构化的对象未来客户端和服务端可以进行来回的发送。
约定体现在这个结构化对象里面的成员变量都代表了什么意思。
为什么一定是这样的格式而不能是其他格式。
如op为什么一定是±*/不能是其他这些都是约定好的。
拿到结果先看哪一个后看哪一个。
exitcode为0是什么意思不为0是什么意思。
都是规定好的。
这就是协议。
我们今天就只写了一种协议RequestResponse未来如果想用Request1Response1等等定义100对协议都是可以的。
每一对协议做不同的工作。
y\r\n未来解析协议的时候可以把协议编号拿到然后根据编号区分清楚用的是那个RequestResponse对象。
目前基本socket写完一般服务器设计原则和方式(多进程、多线程、线程池)常见的各种场景自定义协议序列化和反序列化都已经学了。
所以未来我们就可以用这三大构成自己自由去写服务器了。
有没有人已经针对常见场景早就已经写好了常见的协议软件供我们使用呢
当然了最典型的HTTP/HTTPS。
未来它们做的事情和我们以前做的事情是一样的只不过HTTP是结合它的应用场景来谈的。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback