一、应用层自定义协议与序列化
1. 理解 TCP 协议
在使用 read、write 接口时,数据并非直接写入网络。它们是将用户缓冲区的内容拷贝到内核的 TCP 发送缓冲区中,write 函数执行完毕仅代表数据已到达内核缓冲区。
TCP/IP 协议属于操作系统的一部分。TCP 协议维护发送缓冲区和接收缓冲区。发送端将数据拷贝到内核缓冲区后,由 TCP 协议自主控制何时发送、发送多少以及处理错误。
每台主机上都有 TCP 协议,通信本质是两个进程间的数据传输。数据从发送端的 TCP 发送缓冲区通过网络拷贝到对端的接收缓冲区,最终被上层用户读取。TCP 支持全双工是因为它拥有一对发送和接收缓冲区,通过文件描述符即可实现双向通信。
发送端将数据拷贝到内核缓冲区的过程类似于生产者 - 消费者模型,即用户与内核之间的同步交互。当发送缓冲区写满时,write 会阻塞;当接收缓冲区无数据时,read 会阻塞。
TCP 协议不关心写入数据的语义(如 ls -a -l),只关注字节数量。如果对端接收缓冲区空间不足,TCP 协议仅发送可用空间大小的数据,剩余部分暂存。这种情况称为数据粘包问题。TCP 是面向字节流的,是否读完整数据由用户自行控制维护。

2. 序列化与反序列化
若需通过网络发送结构体数据(如包含两个运算数及结果的整数),要求对端拥有相同的结构体定义。直接发送结构体对象面临内存对齐、大小端转换及跨语言兼容等问题。
因此,应用层通常采用序列化与反序列化。
例如,发送消息时除了数据本身还包含时间等信息,可将多个字段合并为一条字符串(以空格分隔)发送给对端,对端按指定分割符提取。将多个字符串合并成一条字符串的过程称为序列化,对端按指定格式提取的过程称为反序列化。

序列化的优势:
- 方便网络发送:将多段数据合并为单段传输。
- 可扩展性和可维护性:新增字段(如日期)只需增加对应字节。
- 方便上层处理:接收方按固定格式解析即可。
3. TCP 接口
TCP 协议是面向字节流的,读取数据可使用 read 或 recv 函数。
recv(sockfd, buf, len, flags) 参数说明:
sockfd:文件描述符。buf:用户自定义缓冲区。len:缓冲区大小。flags:标志位,0 代表阻塞,MSG_DONTWAIT代表非阻塞。
返回值:成功返回读取字节数,失败返回 -1 并设置错误码,返回 0 代表读到文件末尾。
send(sockfd, buf, len, flags) 参数说明:
sockfd:文件描述符。buf:待发送数据。len:发送数据大小。flags:标志位,0 代表阻塞,MSG_DONTWAIT代表非阻塞。
返回值:成功返回发送字节数,失败返回 -1 并设置错误码。

4. 序列化的现成工具
使用 jsoncpp 库进行 JSON 序列化。
安装命令:
ubuntu: sudo apt install libjsoncpp-dev
centos: sudo yum install jsoncpp-devel
Json::Value 创建的对象称为万能对象,可通过 [] 方式插入键值对,支持不同类型数据。
序列化方法包括:
- 使用
StreamWriterBuilder创建StreamWriter子类对象,调用write函数将内容写入流。 - 创建
FastWriter对象,调用write函数返回序列化后的字符串。 - 创建
StyledWriter对象,用于格式化输出。
反序列化示例:
Json::Reader reader;
Json::Value root;
if (reader.parse(jsonString, root)) {
// 根据键值提取结果
}
5. 守护进程
前台进程与后台进程:系统内通常只有一个前台进程用于获取键盘输入。后台进程不影响 Bash,用户可在进程运行期间继续操作 Linux。
启动一批进程后,其父进程 ID (PPID) 通常为 Bash 进程。进程组 ID (PGID) 一般以组内第一个进程 ID 为准。进程组的目的是共同完成一个任务。任务号用于标识后台作业,可通过 jobs 查看,fg 调至前台,Ctrl + Z 暂停,bg 激活。
进程组生命周期从创建开始到最后一个进程离开为止。只要组内还有进程存在,进程组即存在。
用户登录 Linux 云服务器时,会创建终端文件和 Bash 进程组。Bash 自动继承标准输入输出文件描述符 (0, 1, 2),子进程也会继承这些描述符。在云服务器环境下,这些描述符指向终端文件。
一次会话 (Session) 包含终端文件、进程组等,通过会话 ID (SID) 区分。每个用户登录创建一个独立会话。进程组在会话内部完成任务。如果退出登录,会话结束,进程组释放,任务受影响。
为避免受用户登录影响,需将进程脱离原会话,形成独立的后台会话,这种独立会话的后台进程称为守护进程。
一次会话中只允许一个前台进程组,但可存在多个后台进程组。只有前台进程能从标准输入获取数据,因此 Ctrl + C 只能终止前台进程。
创建守护进程的方法:
- 调用
setsid()创建新会话,新会话 ID 为调用进程的 PID。 - 通过
fork创建子进程,让子进程执行后续代码,父进程退出。此时该进程成为孤儿进程,由 init 系统接管,即为守护进程。 - 将守护进程的标准输入输出重定向至
/dev/null(文件垃圾桶),避免向终端写入数据。 - 建议将守护进程的工作路径设置为根目录。













