
目录
TCP的发送和接收过程理解
如下图所示:

之前我们写的EchoServer和DictServer都属于应用层的编写,传输层往上,都属于用户区域,传输层及其往下的部分,都属于内核区域。
通信时,用户可以使用read、write等函数,以TCP通信的一个文件描述符为例,在传输层会为通信双方提供一个发送缓冲区和接收缓冲区。当发送者发送数据时,并不是直接将相关数据直接发送到网络中,而是通过write函数将用户空间拷贝到发送缓冲区中,并返回;而发送缓冲区的数据如何发送、何时发送以及出错的处理方式,都是由传输层TCP协议自己维护;因此,write函数本质是一个拷贝函数,将用户的数据拷贝到对应的发送缓冲区中;随后,TCP协议将发送缓冲区中的数据发送到对方的接收缓冲区中,接收方收到数据会将数据拷贝到自己的接收缓冲区中,当用户调用read接口时,如果接收缓冲区中有数据,read就将数据拷贝到用户空间中,因此,read函数本质上也是一个拷贝函数;如果没有数据,read就会阻塞;如果在发送方发送数据过快,发送缓冲区被写满了,此时write就会阻塞;因此,在通信时,双方使用的函数都是在进行拷贝,真正在网络中通信的,是双方的操作系统。
这也是TCP为什么支持全双工通信的原因,因为通信双方有一对缓冲区,进行发送的时候可以接收,接受的时候也可以发送;同时,用户拷贝数据到内核对应的发送缓冲区,用户拷贝接收缓冲区中的数据到用户空间,是个"生产者-消费者"模型,这也是read和write会什么会阻塞的原因,阻塞的原因是用户在和内核进行同步。操作系统可能会收到很多个报文,也会有还没来得及放入到缓冲区当中的报文,因此,操作系统也要对收到的报文进行管理,管理的方式同样是"先描述,再组织",每个报文都会有对应的数据结构。
但是在这个发送过程中,也会存在一定的问题,例如,当用户发送命令"ls -l -a"到服务器中,TCP可能只会先将"ls"发送,过一段时间才发送"-l -a",这就会导致命令执行出现问题,读数据时,有可能读到的数据只是发送的一部分,读取的过程和写入的过程不一定是完全匹配的,这种读取不完整的报文叫做数据粘包问题。保证完整新的过程是要上层用户自己来实现的,这时就需要定义用户协议了,比如,规定读到'\0'才算结束等规则。
序列化与反序列化
除了传基本数据进行通信,还可以定义结构体来表示我们需要交互的信息,但是在应用层,不推荐直接使用结构体变量的方式进行通信,这是因为客户端和服务端可能在不同的系统行运行,可能存在不同程度的对齐问题以及大小端问题,此时传结构体必须要人为固定大小才能正常通信,同时,通信的双方不一定是同一门编程语言,这时传结构体也不方便通信,最好的实现方式是序列化与反序列化。
序列化与反序列化的大致过程如下所示:

在聊天的过程中,发送消息时,可将不同消息变成一条,将信息由多条变为一条,是序列化的过程,对方收到信息,在应用层将收到的信息转换成多条信息,是反序列化的过程;序列和反序列化可以让我们写出来的代码具有很好的扩展性。
Jsoncpp
简介
Jsoncpp是⼀个用于处理JSON数据的C++库。它提供了将JSON数据序列化为字符串以及从字符串 反序列化为C++数据结构的功能。Jsoncpp是开源的,广泛用于各种需要处理JSON数的C++项目中。可使用如下命令进行安装:
bash
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
序列化
Json中的Value是一个万能对象,通过toStyledString函数可以将对象直接转成格式化的Json字符串。如下所示:
cpp
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
std::string s = root.toStyledString();
std::cout << s << std::endl;
return 0;
}
$ ./test.exe
{
"name" : "joe",
"sex" : "男"
}
也可以使用FastWriter和StyleWriter对象进行序列化,如下面两个程序:
cpp
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
Json::FastWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}
$ ./test.exe
{"name":"joe","sex":"男"}
cpp
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
// Json::FastWriter writer;
Json::StyledWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}
$ ./test.exe
{
"name" : "joe",
"sex" : "男"
}
同时,也可以使用StreamWriter进行序列化,如下程序所示:
cpp
#include <iostream>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "zhangsan"; //key:value
root["sex"] = "man";
root["age"] = 18;
Json::StreamWriterBuilder wbuilder;
std::unique_ptr<Json::StreamWriter> swriter(wbuilder.newStreamWriter());
std::stringstream ss;
swriter->write(root, &ss);
std::cout << ss.str() << std::endl;
return 0;
}
// ./a.out
{
"age" : 18,
"name" : "zhangsan",
"sex" : "man"
}
由于JsonValue串是个万能对象,不仅可以接收基本类型,还可以接收自己,如下程序所示:
cpp
#include <iostream>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value sub_root1;
sub_root1["name"] = "zhangsan";
sub_root1["sex"] = "man";
sub_root1["age"] = 18;
Json::Value sub_root2;
sub_root2["math"] = 88;
sub_root2["chinese"] = 98;
Json::Value root;
root["who"] = sub_root1;
root["score"] = sub_root2;
Json::StreamWriterBuilder wbuilder;
std::unique_ptr<Json::StreamWriter> swriter(wbuilder.newStreamWriter());
std::stringstream ss;
swriter->write(root, &ss);
std::cout << ss.str() << std::endl;
return 0;
}
// ./test.exe
{
"score" :
{
"chinese" : 98,
"math" : 88
},
"who" :
{
"age" : 18,
"name" : "zhangsan",
"sex" : "man"
}
}
反序列化
反序列化的方式也有多种,这里我们介绍Json::Reader,得到序列化的字符串后,可以通过parse函数将JSON串解析成value对象,读取成功后通过key访问数据即可。如下程序所示:
cpp
#include <iostream>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>
int main()
{
std::string json_string = "{\"name\":\"张三\", \"age\":30, \"city\":\"北京\"}";
std::cout << json_string << std::endl;
Json::Reader reader;
Json::Value root;
bool parseok = reader.parse(json_string,root);
if(!parseok)
{}
std::string name = root["name"].asString();
int age = root["age"].asInt();
std::string city = root["city"].asString();
std::cout << name << std::endl;
std::cout << age << std::endl;
std::cout << city << std::endl;
return 0;
}
自定义协议格式
除了解决序列化的问题,还要定制完整的协议格式,以确保接收方接收的信息是完整的,报文格式如下图所示:

报文前面4字节表示简单的报头,代表有效载荷长度,中间的有效载荷为序列化之后的字符串,当接收方读取报文时首先读取有效载荷的长度,并读取对应大小的内容作为有效载荷的内容,以此来解决粘包问题。