Linuxest 2009-12-21
摘 要:本文介绍了Linux环境下TCP协议编写网络传输程序的几点考虑。作者提出了在使用此协议时,构造消息边界、区分程序控制数据与实际数据的设计思 路。作者将客户与服务器之间的交互信息抽象成不同的命令,实现了一个文件传输协议和基于命令式交互的网络服务器、客户的架构模型。
转自http://hi.baidu.com/%B2%BB%D5%FD%D6%B1%B5%C4%C8%CB/blog/item/70a7b04a12e4b02609f7efa8.html
关键词:Linux,TCP,socket
一、背景
在2008年夏季小学期里,作者学习了Linux的下的并发网络服务器的设计理论,根据老师的要求,编制了并发文件传输的服务器、客户端程序。
在编写网络程序的过程中,作者考虑到了TCP协议的原理和一般网络环境的特点,提出了平时大家不太注意的几个问题,并给出了解决方案,实现了自己的文件传输协议。
二、使用TCP协议编写文件传输程序的三个问题
1、无法区分消息边界
平时在谈论TCP时,我们都能够注意到它与UDP的一点区别,那就是TCP的传输是流式的,而UDP的传输是面向数据报的,但是一般的同学在第一次 编写传输程序时,却没有意识到这一点。实际上,TCP的流式传输,体现在编程上,就是TCP不为应用程序保留消息边界。也就是说在一端调用send(), 发送一定的字节数,譬如2K个字节,在另一端调用recv()时,也许只收到其中的前1K个字节,需要再调用一次recv()才能把所有的字节收完整。这 是因为在接收端的TCP程序收到合适数量的字节数之后或者缓存满之后,就会把缓冲区中的数据提交给应用程序,这时recv()函数返回,但实际上还有一些 字节在网络上传送。若发送端调用两次send()分别发送了0.5K和2K个字节,接收端第一次接收可能会接收到1K个字节,第二次会接受到剩下的 1.5K个字节。如果发送的0.5K和2K个字节分别代表服务器发送给客户的两个文件,接收方就无法区分两次收到的数据分别是哪个文件中的内容了。
这种特点在客户与服务器需要进行一定数量的交互应答时显得非常不方便,传送的消息全部杂糅在一起,区分不出边界,需要程序员自己处理边界的问题。
2、不恰当的使用发送函数
另外一个问题是一些同学使用循环,按一个字节一个字节的发送文件的内容,这样做是非常有害的。虽然程序把数据推送到缓冲区后,TCP会尽量把数据集 合成MSS长度发送出去,但是若数据到达的速率比较慢,即使不到一个MSS的长度,TCP等待超过一段时间后,也会导致一次发送。另外,在关闭了 Nagle算法后,这种一次要求TCP发送一个字节的函数调用的效率会更低,如果文件比较大,速度会慢得不能忍受。消费IP和TCP的较长头部,却只带走 很少数量字节的净荷,对带宽的利用率就变低了。
3、控制信息与数据无法区分
最后一个问题是,在文件较大的时候,无法一次全部读入内存,也无法一次就发送完毕,需要边读边发,这样就导致了多次send()函数的调用,客户与服务器进行请求应答会话时,传输的控制信息也要使用send(),由于没有消息边界,这些内容在recv()时就无法区分。
在本次任务中,程序的要求比较简单,因此几乎没有控制信息,在更复杂的场合,这个问题是不可回避的。
4、为什么平时都没有发现问题
这些问题大家平时都不容易考虑到,是因为每次编写网络程序都比较简单,实验时也只发送小文件,看不到错误的发生。为了引发这些错误,我编写了一个程 序,其逻辑很简单,先把文件全部读到一个一维字节数组中,再一次调用send()函数全部发出去,在接收方调用一次recv()函数收取数据,再将数据写 到文件中。这个程序在发送小文件时工作得很好,在发送一个2.1K的文件时,接收方只调用一次recv(),仅收到了1.4K,剩下的数据还留在接收方缓 冲区中。