AF_UNIX套接字收发数据包三种实现方案的最优选择咨询
AF_UNIX流套接字数据包收发方案分析
首先直接给结论:修正笔误后的选项2是唯一合理的可用方案,三个方案的具体问题如下:
各方案的问题说明
- 选项3完全不可用
你定义的Packet结构体里message是指针类型,直接把结构体整块发送的话,实际上传的是message这个指针本身的内存地址,不是它指向的"hello"字符串内容。客户端和服务端是独立的进程,地址空间完全隔离,服务端拿到这个地址根本访问不到对应的字符串,轻则读不到数据,重则直接触发段错误。
另外C语言的结构体存在内存对齐规则,你手动算的size是两个int加字符串长度,但实际结构体占用的内存大小可能比你算的大,直接整块发送会多出无效的对齐填充字节,服务端解析必然出错。 - 选项1可用性差、效率低
首先SOCK_STREAM是流协议,本身没有消息边界,虽然AF_UNIX是本地套接字,小数据量下三次write大概率会被内核合并发送,但如果数据量变大或者系统负载高,还是可能出现半包问题,服务端要对应三次read才能拼出完整包,逻辑冗余,耦合性极高,只要一端调整字段顺序就会直接解析失败。
其次多次write对应多次系统调用,哪怕本地套接字系统调用开销小,并发量上来之后性能差距也会很明显。而且你现在的代码完全没判断write的返回值,万一单次write没写完所有字节也没有重试逻辑,很容易出现数据丢失。 - 选项2只需要修正两个小笔误就是最优解
现有代码的两个小问题:- malloc参数写错了,
malloc(sizeof(size))实际只申请了int类型大小的内存(通常是4字节),应该改成malloc(size)才是申请整个数据包需要的内存 - 手动写偏移量
buffer + 4、buffer + 8是默认int占4字节,兼容性差,改成buffer + sizeof(int)、buffer + 2 * sizeof(int)更稳妥
修正后的优势非常明显: - 只需要两次系统调用,性能比选项1好,还可以用
writev进一步把两次write合并成一次,继续降低系统调用开销 - 先发送整个包的长度,服务端可以固定先读4字节拿到包的总大小,再一次性读对应长度的内容,完美解决流协议的消息边界问题,鲁棒性很强
- 手动控制内存布局,没有结构体对齐的问题,也不会出现发送指针的低级错误
- 可扩展性好,后续要新增字段只需要调整size计算和memcpy的位置即可,不需要修改收发的核心逻辑
- malloc参数写错了,
额外优化建议
不管用哪个方案,都要注意:
- 必须检查
write、read的返回值,处理读写不完整的情况,特别是系统负载高的时候,单次读写很可能不会处理完所有字节 - 示例代码里用到的
N宏没有定义,实际使用时要注意缓冲区大小,避免越界
内容的提问来源于stack exchange,提问作者Samuele Calugi
相关产品推荐
相关产品推荐

