




已阅读5页,还剩4页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Linux环境下如何恰当的使用TCP协议编写网络传输程序【转】 Linux环境下如何恰当的使用TCP协议编写网络传输程序2010-05-11 08:55转载自 xiangpengmeng最终编辑 xiangpengmeng声明本文属于转载!学习中!摘 要:本文介绍了Linux环境下TCP协议编写网络传输程序的几点考虑。作者提出了在使用此协议时,构造消息边界、区分程序控制数据与实际数据的设计思 路。作者将客户与服务器之间的交互信息抽象成不同的命令,实现了一个文件传输协议和基于命令式交互的网络服务器、客户的架构模型。关键词: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,剩下的数据还留在接收方缓 冲区中。三、文件传输程序的设计思路1、区分消息边界在链路层的协议中,区分消息边界用的是位填充与字符填充法。在本程序中,使用另外一种办法。不使用填充法是因为无论是字符填充还是位填充,都要扫描 一遍所有的数据,寻找出与边界相冲突的模式。这在链路层是比较合理的,因为要计算桢检验信息,必然要做全部的扫描,这时就可以顺便找出冲突并进行填充。但 是在应用层的程序中,如果每次都要把所有的数据都扫描一遍,会减慢处理数据的速度。作者的办法是先发4字节定长的数据表示将来要发送的消息的长度,再一次把整个消息都推送给send()函数,放到缓冲区中,让TCP协议自己决定何 时从缓冲区中取出数据、取多少数据发送到网络上。接收方每次先接收4字节的数据,确定将来接收数据的长度,根据这个信息,再不停的调用recv()函数, 并累积已经收到的字节数,直到达到发送方一开始通告的数量为止。这期间收到的数据视为一个消息,下次再接收数据时还是先收4个字节。这个方法不需要做额外的计算,就解决了区分消息边界的问题,在发送大量数据(G以上)时,效率要比把数据全扫描一遍并填充的效率高。2、恰当的使用发送函数在调用发送函数时,不是一个字节一个字节的发送,而是一次发送一块数据。数据块的大小只要适当即可,推送到缓冲区后,TCP协议会自行决定最佳的发送策略。3、区分控制信息和数据在这之前,我们已经解决了区分消息边界的问题。一个消息实际上是一块数据,我们可以在数据的头部划分出一定的空间存放控制信息,在其余的部分存放数 据。在本程序中,作者提出了“命令”的概念。一个命令包含命令的名字和命令的数据。命令名既用来区分不同的命令,又用来表示各命令特定的语义;命令的数据 根据命令的不同,其含义不同。在本程序中,有如下三个命令:命令REQ_FILE,表示客户向服务器请求一个文件,命令名为“REQ_FILE”,数据部分是要请求文件的NULL结尾的字符串路径;命令RESP_DATA,表示服务器向客户发送的文件的一块数据,命令名为“RESP_DATA”,数据部分是文件的数据块;命令RESP_FIN,这个命令在服务器发送完一个文件的所有数据后发送,表示发送文件结束,命令名为“RESP_FIN”,数据部分为空。4、文件传输协议 作者设计的传输协议的基础就是这些命令。每个命令被分解成有两个消息在网络上传送,首先传送包含命令名的消息,接着传送命令的数据消息。客户端连接到服务 器后,向服务器传送一个REQ_FILE命令,服务器打开对应的文件,并向客户发送一系列的RESP_DATA命令,从文件里读一块数据,就把这块数据放 到RESP_DATA命令的数据区域中发送出去,最后发送一个RESP_FIN命令表示传送完毕。在此,由于任务比较简单,所以命令的种类比较少。事实 上,可以很容易拓展这种机制,比如增加一个RESP_NOFILE命令表示服务器对客户端通知请求的文件不存在。再增加一些登录、退出、谈话命令和线程同 步机制,就可以很容易的实现一个多用户聊天程序了。这个架构可以作为一个实用的Internet程序的基础。5、任务要求客户端向服务器发送一个文件名(服务器存有该文件),服务器收到报文后进行解析,从本地读取该文件,把该文件的内容发送给客户端,客户端将接收到的数据存在本地的硬盘上。在实验中,并发服务器要接受2个以上客户端的连接。每个客户端要求的文件名应该不同。四、文件传输的实现1、公共部分首先以符号常量的形式,定义几个命令的名字:#define REQ_FILE REQ_FILE#define RESP_DATA RESP_DATA#define RESP_FIN RESP_FIN接着定义一次处理数据块的大小:#define MAXSIZE 1024再用一个结构体定义命令:typedef struct /0结尾的字符串形式的命令名字 char* cmd; /不同的命令对应的数据的意思可能不同 char* data; /数据的长度 int datalength;Command;结构体中的变量都是指针变量,其包含的数据在运行时动态加载,分配的内存空间在使用完后释放。接着是实现发送和接收消息的两个函数:/用TCP协议,发送一个消息,并且保留消息边界fd的socket描述符,D是字节数组,n是数组长度,返回的是已发送的字节数extern int sndmsg(int fd,char * D,int n);/从网络上获取一个消息,返回一个指针指向收到的消息,fd是socket描述符,n是输出参数,代表收到的数据的大小extern char* getmsg(int fd,int * n);再接着是发送、接收命令的函数,这些函数通过恰当的调用发送、接收消息函数来实现自己的功能:/发送一个命令,名字和数据分开成两个消息发送extern int sndcmd(int fd,Command cmd);/将接收到的数据解析成程序可理解的命令extern Command handlecmd(int fd);最后是发送和接收文件的两个函数,这两个函数通过操作发送、接收命令的函数来完成自己的任务:extern int sndbigf(int fd,char* filename);extern int rcvbigf(int fd,char* filename);以上的定义包含在fileoperations.h文件中,在fileoperations.c实现其功能。2、服务器的结构有了fileoperations.c中的实现,服务器的结构就很简单了,首先建立一个socket,接着绑定到某一端口并开始监听,在 accept()到客户端连接后,利用fork()函数建立子进程处理客户的请求,主进程则继续监听。方便叙述起见,省略了错误处理代码。其结构可以用自然语言+伪代码的形式表示如下:skid= socket(AF_INET, SOCK_STREAM, 0);bind(skid,填写好的本机地址结构, 地址结构大小);listen(skid, 1);while(1) newid=accept(skid, &对方地址, 地址结构体大小) pid=fork(); if(pid=0) 我们在子进程中,调用sendbigf(new_fd);else if(pid0) 我们在父进程里,打印客户和子进程信息;else打印错误;close(newfd);close(skid);3、客户端的结构方便叙述起见,省略了错误处理代码。/建立套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);/连接服务器connect(sockfd, &对方地址, 地址结构体大小);/调用下面自定义函数向服务器请求一个文件,此函数向服务器发送REQ_FILE命令reqfile(sockfd,getf); /getf是需要的文件的文件名或者路径/调用自定义函数接收文件,savef是要存放的文件名或者路径rcvbigfile(sockfd,savef);/关闭套接字close(sockfd);4、其他建立套接字和获取网络信息要用到gethostname()、gethostbyname()、inet_ntoa()等函数和hostent结构 体,构造地址信息要用sockaddr_in结构体,转换字节顺序要用到htons等函数,这些都是socket编程的基础,在此并不进行讨论。5、实验在本机和机房都进行了实验,实验多次,第一次传送了两个100MB以上的文件,第二次传送了两个几十MB的RAR文件并在传送完毕后对数据进行了检验。
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 矿井防灭工成本预算考核试卷及答案
- 海南省卫生健康委员会2025年医师资格考试口腔执业医师复习题库及答案
- 2025年自考《天然药物化学》试题及答案
- 货运业务信息员成本预算考核试卷及答案
- 拖拉机底盘部件装试工专业知识考核试卷及答案
- 病程记录考试试题及答案
- 2025年上海电梯考试题目及答案
- 广播电视线务员技能巩固考核试卷及答案
- 2025年天车工设备维修技术考试试卷及答案
- 实验动物养殖员培训考核试卷及答案
- 九年级英语第1-3单元测试题(含答案)
- 充电桩工程-资料目录
- 血透病人远期并发症
- 有限空间作业气体检测记录表
- 锁骨骨折的护理查房
- 武夷山国家公园文旅景区 LOGOVI设计方案
- 《仓储与配送实务》教案-17物流高技
- 快递转让合同
- JBT 7946.2-2017 铸造铝合金金相 第2部分:铸造铝硅合金过烧
- 新12123交管学法减分考试题库及答案
- DB32T3728-2020工业炉窑大气污染物排放标准
评论
0/150
提交评论