文件传输协议的C语言实现.docx

上传人:b****4 文档编号:4083363 上传时间:2023-05-06 格式:DOCX 页数:31 大小:633.30KB
下载 相关 举报
文件传输协议的C语言实现.docx_第1页
第1页 / 共31页
文件传输协议的C语言实现.docx_第2页
第2页 / 共31页
文件传输协议的C语言实现.docx_第3页
第3页 / 共31页
文件传输协议的C语言实现.docx_第4页
第4页 / 共31页
文件传输协议的C语言实现.docx_第5页
第5页 / 共31页
文件传输协议的C语言实现.docx_第6页
第6页 / 共31页
文件传输协议的C语言实现.docx_第7页
第7页 / 共31页
文件传输协议的C语言实现.docx_第8页
第8页 / 共31页
文件传输协议的C语言实现.docx_第9页
第9页 / 共31页
文件传输协议的C语言实现.docx_第10页
第10页 / 共31页
文件传输协议的C语言实现.docx_第11页
第11页 / 共31页
文件传输协议的C语言实现.docx_第12页
第12页 / 共31页
文件传输协议的C语言实现.docx_第13页
第13页 / 共31页
文件传输协议的C语言实现.docx_第14页
第14页 / 共31页
文件传输协议的C语言实现.docx_第15页
第15页 / 共31页
文件传输协议的C语言实现.docx_第16页
第16页 / 共31页
文件传输协议的C语言实现.docx_第17页
第17页 / 共31页
文件传输协议的C语言实现.docx_第18页
第18页 / 共31页
文件传输协议的C语言实现.docx_第19页
第19页 / 共31页
文件传输协议的C语言实现.docx_第20页
第20页 / 共31页
亲,该文档总共31页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

文件传输协议的C语言实现.docx

《文件传输协议的C语言实现.docx》由会员分享,可在线阅读,更多相关《文件传输协议的C语言实现.docx(31页珍藏版)》请在冰点文库上搜索。

文件传输协议的C语言实现.docx

文件传输协议的C语言实现

 

文件传输协议的C语言实现

第ChpNum章文件传输协议的C语言实现

1设计目的

本设计旨在利用Winsock2.0简单实现FTP(FileTransferProtocol,文件传输协议)的客户端和服务器端程序。

通过完成此设计,了解WinsockAPI函数调用方法和一般网络应用程序的编程方法,理解FTP协议,掌握C语言设计FTP协议软件的基本技术,为将来开发其他通信协议软件打下坚实基础。

2设计准备

(1)连入同一局域网的PC,每人一台。

(2)PC装有Windows操作系统、VisualC++6.0编译器及开发手册MSDN6.0。

3关键技术

3.1文件传输协议介绍

FTP是FileTransferProtocol(文件传输协议)的英文简称,用于Internet上的控制文件的双向传输。

在实现的层面上,FTP又可理解为一个可用于文件传输的客户机/服务器系统,该系统包括客户机端程序和服务器端程序,客户端和服务器端通信规则为FTP协议。

用户通过客户机程序向服务器程序发出命令请求,服务器程序执行用户所发出的命令,并将执行的结果返回到客户机。

比如说,用户发出一条命令,要求服务器向用户传送某一个文件的一份拷贝,服务器会响应这条命令,将指定文件送至用户的机器上。

客户机程序接收到这个文件,将其存放在用户目录中。

在通信协议的分层模型中,文件传输协议是在TCP(TransmissioncontrolProtocol,传输控制协议)之上的一个应用层协议,应用程序之间的通信需要用到传输层提供的字节流透明无误传输服务。

Windows操作系统具有TCP/IP协议栈,应用程序可通过WinsockAPI函数的调用实现端到端透明数据链接的建立。

3.2WinsockAPI介绍

因特网(Internet)最初是基于Unix的,而Sockets(套接字)是Unix第一个支持TCP/IP协议栈的网络API,最早于1982年8月随4.2BSD版Unix推出,常被称为Berkeleysockets(伯克利套接字)。

Winsock(WindowsSocketsAPI)是从Sockets移植过来的TCP/IP编程的低级WindowsAPI。

Winsock分1.1版和2.x版,从Windows98开始使用2.x版。

Winsock与windows操作系统的关系如图ChpNum-1所示。

操作系统实现了TCP/IP协议栈,(包括传输层协议TCP及UDP;网络层协议IP、ICMP及IGMP;链路层协议ARP和RAR),该模块的相关功能以动态链接库的形式被应用程序调用。

操作系统接受网卡驱动程序的注册,网卡驱动程序本质上是一套控制网卡硬件收发报文的函数,也是以动态链接库的形式被调用。

物理通信介质是指网卡驱动芯片及其外围电路,完成链路层数据帧的封装/解封、发送/接收等功能。

图ChpNum1Winsock与操作系统的关系

套接字可看作是不同主机间的进程进行双向通信的虚拟管道端点:

网络中两台主机各自在自己机器上建立通信的端点--套接字,然后使用套接字进行数据通信。

一个套接字包含五个基本元素:

协议类型、本地IP地址、本地端口、远端IP地址和远端端口。

在操作系统中,套接字是一种系统资源,应用程序使用时应向操作系统申请或注册,使用结束后应用程序应释放该该套接字。

和其他系统资源一样,操作系统为套接字分配一个唯一的ID(在Windows中被称作句柄)。

根据网络通信的特征,套接字分为三类:

流套接字(SOCK_STREAM)、数据报套接字(SOCK_DGRAM)和原始套接字(SOCK_RAW)。

流套接字是面向连接的,它提供双向的、有序的、无差错、无重复并且无记录边界的数据流服务,适用于处理大量数据,提供可靠的服务。

数据报套接字是无连接的,它支持双向的数据传输,具有开销小、数据传输效率高的特点,但不保证数据传输的可靠性、有序性和无重复性,适合少量数据传输、以及时间敏感的音/视频等多媒体数据传输。

原始套接字(SOCK_RAW)可以用作对底层协议(如IP或ICM)的直接访问。

Winsock网络应用程序利用API函数(如accept、send、recv等函数)进行I/O操作时有阻塞和非阻塞两种模式。

若要获取的资源还没有到达(如:

接收缓冲区中没有数据提供给recv函数),在阻塞模式下,执行I/O操作的Winsock函数在I/O操作完成前会一直等待下去,不会立即返回;而在非阻塞模式下,该函数不管I/O操作有没有完成都会立即返回,若未完成一般会返回错误码WSAWOULDBLOCK,意味着必须重新进行尝试。

阻塞模式与非阻塞模式比较,从编程角度来说,前者更便于使用,但从程序运行的效率来说,由于阻塞调用后会使得所在的线程(如果是主线程那么就是整个程序)等待在该I/O操作上,因此后者效率更高。

默认情况下,这些I/O操作工作于阻塞模式。

在阻塞模式下使用Winsock2的API库函数进行数据报套接字编程的过程如图ChpNum-2所示。

在服务器端,先调用WSASartup函数进行初始化,初始化完成后调用Socket函数创建一个Sockets,再调用bind函数将该套接字绑定到某个特定端口,接下来调用Listen函数启动监听并调用Accept函数接收客户连接,若客户连接请求未及时到达,则Accept函数处于阻塞状态。

Accept函数为客户端的连接请求创建一个新的套接字S1,在以后的通信中,服务器利用套接字s1与客户端进行数据双向传输。

通信结束时,服务器可以采用Closesocket函数释放套接字,并可调用WSAClearup释放WinsockDLL。

客户机是连接的请求的发起者,在创建Socket之后直接通过调用Connect发起连接请求,成功后即可以利用该Socket进行双向通信了。

下面对Winsock2提供的主要接口函数逐一进行介绍。

 

图ChpNum-2基于TCP的网络应用程序

(1)WSAStartup()函数和WSACleanup()函数

由于Winsock2提供的API服务是以动态链接库ws2_32.dll实现的,所以必须先调用WSAStartup()函数对ws2_32.dll进行加载初始化,协商Winsock的版本支持,并分配必要的资源。

在应用程序关闭套接字后,还应调用WSACleanup()函数来终止和卸载动态链接库ws2_32.dll,释放资源。

(2)socket()函数

服务进程和客户进程在通信前必须创建各自的套接字,然后才能用相应的套接字进行发送、接收操作,实现数据的传输。

服务进程总是先于客户进程启动,服务进程和客户进程调用socket()函数创建套接字。

(3)bind()函数

当用socket()创建套接字后,它便存在于一个名字空间(地址族)中,但并未赋名。

bind()函数通过给一个未命名套接字分配一个本地名字(主机地址/端口号)来为套接字建立本地捆绑。

客户端一般隐式地向操作系统请求一个随机的未使用过的临时端口号,跟自己的IP地址一起,与所创建的套接字建立联系,由于该临时端口号客户端程序事先是不确定的,因此不显式地使用绑定函数。

(4)listen()函数

调用listen()函数对服务器上套接字启动监听,即允许客户连接请求开始排队。

(5)accept()函数

服务器设置监听工作方式后,通过调用accept()函数使套接字等待接受客户连接。

如果已有连接请求到来,该函数会返回一个新的套接字描述符,它对应于已经接受的那个客户端连接。

对于该客户机后续的所有操作,都应使用这个新套接字。

至于原来那个监听套接字,它仍然用于接受其他客户机连接,继续处于监听模式。

(6)connect()函数

客户端利用connect()函数和服务器建立一个端到端的连接。

(7)closesocket()函数

网络通信任务完成后,利用本函数释放套接字占用的所有资源。

4软件设计

本设计客户端及服务器端均采用单线程实现,命令和数据的传输在同一个Socket链接上进行。

客户端支持DIR(远端文件夹查询)、GET(文件下载)、PUT(文件上传)、PWD(远端当前路径查询)、CD(远端当前路径设置)、MD(远端文件夹创建)、DEL(远端文件删除)等7个常用FTP命令。

用户命令格式为“命令字路径名/文件名”,如下载当前目录下的test.txt文件,则用户在控制台界面输入的命令格式为“GETtest.txt”。

客户机和服务器的命令格式约定为“命令字$路径名/文件名”,即test.txt文件下载命令格式为“命令字$路径名/文件名”。

图ChpNum-3程序流程

图ChpNum-3(a)示出了客户机的主程序流程,初始化Winsock后,用socket函数新建一个socket,填写入服务器的及IP地址及监听端口后,利用connnect函数连接到服务器后即提示用户输入ftp命令,程序阻塞在scanf函数。

用户输入命令后,scanf函数返回,通过字符串比对函数strncmp识别命令,并调用相应的命令发送函数,若输入的是quit命令,客户端程序退出。

命令处理函数主要工作有两个,一是构建命令字节流发送到服务器,二是与服务器交互该命令的后续执行数据,例如,对于get命令,该函数在发出get命令请求字节流后,要接收服务器下发的文件数据。

各命令处理函数的实现请参见源代码。

图ChpNum-3(b)示出了服务器端主程序流程,先初始化Winsock,建立Socket并绑定到监听端口,启动监听,阻塞在Accept函数等待连接请求的到来,当连接请求到达,Accept函数为该请求创建新的Socket用于与对应的客户通信,而原来Socket继续处于监听状态。

此后,主程序从新的Socket中读取命令,通过字串比较识别命令,若发现是quit命令,则关闭当前连接,准备接收下一个连接;若不是quit命令,则转移到相应的命令处理函数,处理完毕后继续在该Socket上读取命令并进行处理。

各命令处理函数的设计请参看源代码。

5程序代码

5.1服务器端程序文件

/*********************************************************************

文件名:

server.c

说明:

简单的ftp服务器端程序文件,包含main函数及get、put等命令处理函数。

**********************************************************************/

#include

#include

#include

#pragmacomment(lib,"ws2_32.lib")

WSADATAwsd;

charSendBuffer[80],RecvBuffer[80];//发送缓冲区及接收缓冲区

#defineDEFAULT_LSTN_PORT2416//本地默认监听端口

intn,bytes;

SOCKETh_NewSocket;//accept函数产生的新socket

structsockaddr_inRemoteAddr;

charpath[80]="";

charstrObject[100]="";

intiSynError=1;

intsdirfun(SOCKETh_NewSocket);

intsgetfun(SOCKETh_NewSocket);

intsputfun(SOCKETh_NewSocket);

intspwdfun(SOCKETh_NewSocket);

intscdfun(SOCKETh_NewSocket);

intsmdfun(SOCKETh_NewSocket);

intsdelfun(SOCKETh_NewSocket);

/***********************************************************************

函数名:

main

说明:

主函数

输入参数:

intargc输入参数长度char*argv[]输入参数,用于传入监听端口号

***********************************************************************/

intmain(intargc,char*argv[])

{

structsockaddr_inSLocalAddr;

SOCKETh_Socket4Lstn;//欲用作监听的socket

intaddr_in_len;//地址长度

//初始化winsock

if(WSAStartup(MAKEWORD(2,2),&wsd)!

=0)

{

WSACleanup();

printf("WSAStartupfailed\n");

}

memset(&SLocalAddr,0,sizeof(SLocalAddr));

//创建socket

h_Socket4Lstn=socket(PF_INET,SOCK_STREAM,0);

if(h_Socket4Lstn<0)printf("creatingsocketfailed\n");

SLocalAddr.sin_family=AF_INET;

if(argc==2)SLocalAddr.sin_port=htons((u_short)atoi(argv[1]));

elseSLocalAddr.sin_port=htons(DEFAULT_LSTN_PORT);

SLocalAddr.sin_addr.s_addr=INADDR_ANY;

//绑定socket

if(bind(h_Socket4Lstn,(structsockaddr*)(&SLocalAddr),sizeof(SLocalAddr))

<0)printf("Bindfailed!

\n");

while

(1)

{//主循环

listen(h_Socket4Lstn,3);//启动监听

addr_in_len=sizeof(RemoteAddr);

//接受连接请求

h_NewSocket=accept(h_Socket4Lstn,

(structsockaddr*)(&RemoteAddr),&addr_in_len);

if(h_NewSocket==INVALID_SOCKET)break;//出错退出

printf("%sisconnectedatport%d\n",inet_ntoa(RemoteAddr.sin_addr),ntohs(SLocalAddr.sin_port));

sprintf(SendBuffer,"200Welcome\r\n");//向客户端发送欢迎消息

bytes=send(h_NewSocket,SendBuffer,strlen(SendBuffer),0);

sprintf(SendBuffer,"530Login\r\n");

bytes=send(h_NewSocket,SendBuffer,strlen(SendBuffer),0);

while

(1)

{//接收客户端的命令并调用命令处理函数

n=0;

iSynError=1;

while

(1)

{

bytes=recv(h_NewSocket,&RecvBuffer[n],1,0);

if((bytes<0)||(bytes==0))break;

if(RecvBuffer[n]=='$')

{

RecvBuffer[n]='\0';

break;

}

if(RecvBuffer[n]!

='\r')n++;

}

if((bytes<0)||(bytes==0))

break;

printf("TheServerreceived:

'%s'cmdfromclient\n",RecvBuffer);

//命令识别

//查看当前目录

if(strncmp(RecvBuffer,"dir",3)==0)sdirfun(h_NewSocket);

//查询当前目录路径

if(strncmp(RecvBuffer,"pwd",3)==0)spwdfun(h_NewSocket);

//改变当前目录

if(strncmp(RecvBuffer,"cd",2)==0)scdfun(h_NewSocket);

//文件下载

if(strncmp(RecvBuffer,"get",3)==0)sgetfun(h_NewSocket);

//文件上传

if(strncmp(RecvBuffer,"put",3)==0)sputfun(h_NewSocket);

//新建文件夹

if(strncmp(RecvBuffer,"md",2)==0)smdfun(h_NewSocket);

//删除文件

if(strncmp(RecvBuffer,"del",3)==0)sdelfun(h_NewSocket);

if(strncmp(RecvBuffer,"quit",4)==0)//退出命令

{

printf("quit\n");

sprintf(SendBuffer,"221Byebye...\r\n");

bytes=send(h_NewSocket,SendBuffer,

strlen(SendBuffer),0);

iSynError=0;

break;

}

if(iSynError==1)//Syntaxerror

{

printf("commandunrecognized,non-implemented!

\n");

sprintf(SendBuffer,"500Syntaxerror.\n");

bytes=send(h_NewSocket,SendBuffer,

strlen(SendBuffer),0);

}

}

closesocket(h_NewSocket);

printf("%sdisconnectedfromport%d,controlsocketisclosed.\n",inet_ntoa(RemoteAddr.sin_addr),ntohs(SLocalAddr.sin_port));

}

closesocket(h_Socket4Lstn);//释放监听的socket

return0;

}

/***********************************************************************

函数名:

sdirfun

说明:

用于处理来自客户端的目录查询命令

输入参数:

SOCKETh_NewSocket,命令通过此socket接收到,可通过它响应命令。

**********************************************************************/

intsdirfun(SOCKETh_NewSocket)

{

chartemp_buffer[80];

FILE*p_FiLeTemp;

//整理本地dir命令

strObject[0]='\0';

strcat(strObject,"dir");

strcat(strObject,path);

strcat(strObject,">tmp.txt");

system(strObject);//system函数执行shell命令

p_FiLeTemp=fopen("tmp.txt","r");//打开执行结果文件,准备发送到客户端

sprintf(SendBuffer,"125Transfering...\r\n");

bytes=send(h_NewSocket,SendBuffer,strlen(SendBuffer),0);

while(fgets(temp_buffer,80,p_FiLeTemp)!

=NULL)//每次读取80字节发送

{

sprintf(SendBuffer,"%s",temp_buffer);//

send(h_NewSocket,SendBuffer,strlen(SendBuffer),0);

}

fclose(p_FiLeTemp);//发送完毕,关闭结果临时文件

sprintf(SendBuffer,"226Transfercompleted...\r\n");

bytes=send(h_NewSocket,SendBuffer,strlen(SendBuffer),0);

system("deltmp.txt");//删除结果临时文件

sprintf(SendBuffer,"226Closethedatasocket...\r\n");

bytes=send(h_NewSocket,SendBuffer,strlen(SendBuffer),0);

printf("dircommandhasbeendone!

\n");

iSynError=0;

return0;

}

/***********************************************************************

函数名:

sgetfun

说明:

用于处理来自客户端的文件下载命令

输入参数:

SOCKETh_NewSocket,命令通过此socket接收到,可通过它响应命令。

***********************************************************************/

intsgetfun(SOCKETh_NewSocket)

{

inti=4,k=0;

charFileName[20],temp_buffer[80];

char*p_FileName=strObject;

FILE*fp;

printf("requiredfileis:

");//打印文件名到屏幕

while

(1)

{//提取文件名

bytes=recv(h_NewSocket,&RecvBuffer[i],1,0);

printf("%c",RecvBuffer[i]);

if((bytes<0)||(bytes==0))break;

FileName[k]=RecvBuffer[i];

if(RecvBuffer[i]=='\0'){FileName[k]='\0';bre

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2