当前位置:文档之家› 文件传输协议的C语言实现

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

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

1设计目的

本设计旨在利用Winsock 简单实现FTP(File Transfer Protocol,文件传输协议)的客户端和服务器端程序。通过完成此设计,了解Winsock API函数调用方法和一般网络应用程序的编程方法,理解FTP协议,掌握C语言设计FTP协议软件的基本技术,为将来开发其他通信协议软件打下坚实基础。

2 设计准备

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

(2)PC装有Windows操作系统、Visual C++ 编译器及开发手册MSDN 。

3关键技术

文件传输协议介绍

FTP 是File Transfer Protocol(文件传输协议)的英文简称,用于Internet上的控制文件的双向传输。在实现的层面上,FTP又可理解为一个可用于文件传输的客户机/服务器系统,该系统包括客户机端程序和服务器端程序,客户端和服务器端通信规则为FTP协议。用户通过客户机程序向服务器程序发出命令请求,服务器程序执行用户所发出的命令,并将执行的结果返回到客户机。比如说,用户发出一条命令,要求服务器向用户传送某一个文件的一份拷贝,服务器会响应这条命令,将指定文件送至用户的机器上。客户机程序接收到这个文件,将其存放在用户目录中。在通信协议的分层模型中,文件传输协议是在TCP(Transmission control Protocol,传输控制协议)之上的一个应用层协议,应用程序之间的通信需要用到传输层提供的字节流透明无误传输服务。Windows操作系统具有TCP/IP协议栈,应用程序可通过Winsock API函数的调用实现端到端透明数据链接的建立。

Winsock API介绍

因特网(Internet)最初是基于Unix的,而Sockets(套接字)是Unix第一个支持TCP/IP 协议栈的网络API,最早于1982年8月随BSD版Unix推出,常被称为Berkeley sockets (伯克利套接字)。Winsock(Windows Sockets API)是从Sockets移植过来的TCP/IP编程

的低级Windows API。Winsock分版和版,从Windows 98开始使用版。

Winsock与windows操作系统的关系如图ChpNum-1所示。操作系统实现了TCP/IP协议栈,(包括传输层协议TCP及UDP;网络层协议IP、ICMP及IGMP;链路层协议ARP和RAR),该模块的相关功能以动态链接库的形式被应用程序调用。操作系统接受网卡驱动程序的注册,网卡驱动程序本质上是一套控制网卡硬件收发报文的函数,也是以动态链接库的形式被调用。物理通信介质是指网卡驱动芯片及其外围电路,完成链路层数据帧的封装/解封、发送/接收等功能。

图ChpNum1 Winsock与操作系统的关系

套接字可看作是不同主机间的进程进行双向通信的虚拟管道端点:网络中两台主机各自在自己机器上建立通信的端点--套接字,然后使用套接字进行数据通信。一个套接字包含五个基本元素:协议类型、本地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操作工作于阻塞模式。

在阻塞模式下使用Winsock 2的API库函数进行数据报套接字编程的过程如图ChpNum-2所示。在服务器端,先调用WSASartup函数进行初始化,初始化完成后调用Socket函数创建一个Socket s,再调用bind函数将该套接字绑定到某个特定端口,接下来调用Listen函数启动监听并调用Accept函数接收客户连接,若客户连接请求未及时到达,则Accept函数处于阻塞状态。Accept函数为客户端的连接请求创建一个新的套接字S1,在以后的通信中,服务器利用套接字s1与客户端进行数据双向传输。通信结束时,服务器可以采用Closesocket函数释放套接字,并可调用WSAClearup释放Winsock DLL。客户机是连接的请求的发起者,在创建Socket之后直接通过调用Connect发起连接请求,成功后即可以利用该Socket进行双向通信了。下面对Winsock 2提供的主要接口函数逐一进行介绍。

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

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

由于Winsock 2提供的API服务是以动态链接库实现的,所以必须先调用WSAStartup() 函数对进行加载初始化,协商Winsock的版本支持,并分配必要的资源。在应用程序关闭套接字后,还应调用WSACleanup( )函数来终止和卸载动态链接库,释放资源。

(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命令。

用户命令格式为“命令字路径名/文件名”,如下载当前目录下的文件,则用户在控制台界面输入的命令格式为“GET ”。客户机和服务器的命令格式约定为“命令字$路径名/文件名”,即文件下载命令格式为“命令字$路径名/文件名”。

(a) 客户端主程序流程(b) 服务器端主程序流程

图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程序代码

服务器端程序文件

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

文件名:

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

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

#include <>

#include <>

#include <>

#pragma comment(lib,"")

WSADATA wsd;

char SendBuffer[80],RecvBuffer[80];del. \r\n");

bytes = send(h_NewSocket, SendBuffer,

strlen(SendBuffer), 0);

iSynError=0;

break;

}

if (iSynError==1) \n");

bytes = send(h_NewSocket, SendBuffer,

strlen(SendBuffer), 0);

}

}

closesocket(h_NewSocket);

printf("%s disconnected from port %d, control socket is closed.\n", inet_ntoa,ntohs);

}

closesocket(h_Socket4Lstn); . \r\n");

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

while (fgets(temp_buffer,80,p_FiLeTemp)!=NULL) . \r\n");

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

system("del "); . \r\n");

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

printf("dir command has been done! \n");

iSynError=0;

return 0;

}

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

函数名:sgetfun

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

输入参数: SOCKET h_NewSocket,命令通过此socket接收到,可通过它响应命令。***********************************************************************/ int sgetfun(SOCKET h_NewSocket)

{

int i=4,k=0;

char FileName[20],temp_buffer[80];

char *p_FileName=strObject;

FILE *fp;

printf("required file is: "); Please try again.\r\n", FileName);

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

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

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

return 1;

}

else

{

printf("The file %s is found,ready to transfer.\n",FileName);

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

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

while (fgets(temp_buffer,80,fp)!=NULL)

{ ; . \r\n");

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

}

iSynError=0;

printf("get command has been done! \n");

return 0;

}

/***********************************************************************函数名:sputfun

说明: 用于处理来自客户端的文件上传命令

***********************************************************************/ int sputfun(SOCKET h_NewSocket)

{

Printf(“篇幅所限,请读者完成。\n”);

iSynError=0;

return 0;

}

/***********************************************************************函数名:spwdfun

说明: 用于处理来自客户端的当前路径查询命令

***********************************************************************/ int spwdfun(SOCKET h_NewSocket)

{

Printf(“篇幅所限,请读者完成。\n”);

iSynError=0;

return 0;

}

/***********************************************************************函数名:scdfun

说明: 用于处理来自客户端的当前路径设置命令

***********************************************************************/ int scdfun(SOCKET h_NewSocket)

{

Printf(“篇幅所限,请读者完成。\n”);

iSynError=0;

return 0;

}

/***********************************************************************函数名:smdfun

说明: 用于处理来自客户端的当前文件夹新建命令

***********************************************************************/ int smdfun(SOCKET h_NewSocket)

{

Printf(“篇幅所限,请读者完成。\n”);

iSynError=0;

return 0;

}

/***********************************************************************函数名:sdelfun

说明: 用于处理来自客户端的文件删除命令

***********************************************************************/ int sdelfun(SOCKET h_NewSocket)

{

Printf(“篇幅所限,请读者完成。\n”);

iSynError=0;

return 0;

}

客户端程序文件

/*********************************************************************文件名:

说明: 简单的ftp客户端程序文件,包含main函数及get、put等命令发送函数。

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

#include <>

#include <>

#include <>

#pragma comment(lib,"")

#define DEFAULT_SERV_PORT 2416 delDEL.",strlen("125 Transfering...") )==0) {

if( (fpre=fopen(FileName,"w")) == NULL ), 潘爱民(译)。计算机网络[M],北京:清华大学出版社,2005。

[2]赖特(Wright), 史蒂文斯(Stevens), 陆雪莹(译)。TCP/IP详解?卷2:实现[M],北京:机械工业出版社,2004。

相关主题