第ChpNum章 文件传输协议的C语言实现
1设计目的
本设计旨在利用Winsock2.0简单实现FTP(File Transfer Protocol,文件传输协议)的客户端和服务器端程序。通过完成此设计,了解Winsock API函数调用方法和一般网络应用程序的编程方法,理解FTP协议,掌握C语言设计FTP协议软件的基本技术,为将来开发其他通信协议软件打下坚实基础。
2 设计准备
(1)连入同一局域网的PC,每人一台。
(2)PC装有Windows操作系统、Visual C++ 6.0编译器及开发手册MSDN 6.0。
3关键技术
3.1 文件传输协议介绍
FTP是File Transfer Protocol(文件传输协议)的英文简称,用于Internet上的控制文件的双向传输。在实现的层面上,FTP又可理解为一个可用于文件传输的客户机/服务器系统,该系统包括客户机端程序和服务器端程序,客户端和服务器端通信规则为FTP协议。用户通过客户机程序向服务器程序发出命令请求,服务器程序执行用户所发出的命令,并将执行的结果返回到客户机。比如说,用户发出一条命令,要求服务器向用户传送某一个文件的一份拷贝,服务器会响应这条命令,将指定文件送至用户的机器上。客户机程序接收到这个文件,将其存放在用户目录中。在通信协议的分层模型中,文件传输协议是在TCP(Transmission controlProtocol,传输控制协议)之上的一个应用层协议,应用程序之间的通信需要用到传输层提供的字节流透明无误传输服务。Windows操作系统具有TCP/IP协议栈,应用程序可通过Winsock API函数的调用实现端到端透明数据链接的建立。
3.2 WinsockAPI介绍
因特网(Internet)最初是基于Unix的,而Sockets(套接字)是Unix第一个支持TCP/IP协议栈的网络API,最早于1982年8月随4.2 BSD版Unix推出,常被称为Berkeley sockets(伯克利套接字)。Winsock(Windows Sockets API)是从Sockets移植过来的TCP/IP编程的低级Windows API。Winsock分1.1版和2.x版,从Windows 98开始使用2.x版。
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服务是以动态链接库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文件,则用户在控制台界面输入的命令格式为“GET test.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
#pragma comment(lib,"ws2_32.lib")
WSADATA wsd;
char SendBuffer[80],RecvBuffer[80];//发送缓冲区及接收缓冲区
#define DEFAULT_LSTN_PORT 2416 //本地默认监听端口
int n,bytes;
SOCKET h_NewSocket; //accept函数产生的新socket
struct sockaddr_in RemoteAddr;
char path[80]="";
char strObject[100]="";
int iSynError=1;
int sdirfun(SOCKET h_NewSocket);
int sgetfun(SOCKET h_NewSocket);
int sputfun(SOCKET h_NewSocket);
int spwdfun(SOCKET h_NewSocket);
int scdfun(SOCKET h_NewSocket);
int smdfun(SOCKET h_NewSocket);
int sdelfun(SOCKET h_NewSocket);
/***********************************************************************
函数名:main
说明: 主函数
输入参数: int argc 输入参数长度 char *argv[]输入参数,用于传入监听端口号
***********************************************************************/
int main(int argc, char *argv[])
{
structsockaddr_in SLocalAddr;
SOCKETh_Socket4Lstn; //欲用作监听的socket
intaddr_in_len;//地址长度
//初始化winsock
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
{
WSACleanup();
printf("WSAStartup failed\n");
}
memset(&SLocalAddr,0,sizeof(SLocalAddr));
//创建socket
h_Socket4Lstn = socket(PF_INET, SOCK_STREAM, 0);
if(h_Socket4Lstn <0) printf("creating socket failed\n");
SLocalAddr.sin_family = AF_INET;
if(argc ==2) SLocalAddr.sin_port =htons((u_short)atoi(argv[1]));
else SLocalAddr.sin_port =htons(DEFAULT_LSTN_PORT);
SLocalAddr.sin_addr.s_addr = INADDR_ANY;
//绑定socket
if(bind(h_Socket4Lstn,(struct sockaddr *)(&SLocalAddr),sizeof(SLocalAddr))
<0) printf("Bind failed!\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("%s isconnected at port %d \n",inet_ntoa(RemoteAddr.sin_addr), ntohs(SLocalAddr.sin_port));
sprintf(SendBuffer,"200 Welcome \r\n"); //向客户端发送欢迎消息
bytes = send(h_NewSocket, SendBuffer, strlen(SendBuffer), 0);
sprintf(SendBuffer,"530 Log in \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("TheServer received: '%s' cmd from client \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, "221 Bye bye ... \r\n");
bytes = send(h_NewSocket, SendBuffer,
strlen(SendBuffer),0);
iSynError=0;
break;
}
if (iSynError==1) //Syntax error
{
printf("command unrecognized, non-implemented!\n");
sprintf(SendBuffer, "500Syntax error. \n");
bytes = send(h_NewSocket, SendBuffer,
strlen(SendBuffer),0);
}
}
closesocket(h_NewSocket);
printf("%sdisconnected from port %d, control socket is closed.\n", inet_ntoa(RemoteAddr.sin_addr),ntohs(SLocalAddr.sin_port));
}
closesocket(h_Socket4Lstn); //释放监听的socket
return 0;
}
/***********************************************************************
函数名:sdirfun
说明: 用于处理来自客户端的目录查询命令
输入参数: SOCKET h_NewSocket,命令通过此socket接收到,可通过它响应命令。
**********************************************************************/
int sdirfun(SOCKET h_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, "125 Transfering... \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, "226 Transfer completed... \r\n");
bytes =send(h_NewSocket, SendBuffer, strlen(SendBuffer), 0);
system("del tmp.txt"); //删除结果临时文件
sprintf(SendBuffer,"226Close the data socket... \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;
charFileName[20],temp_buffer[80];
char*p_FileName=strObject;
FILE *fp;
printf("requiredfile is: "); //打印文件名到屏幕
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'; break; }
if(RecvBuffer[i] != '\r') { i++; k++; }
}
printf("\n"); //文件名打印结束
strObject[0]='\0';
strcat(strObject,path);
if(strlen(path)>0)
trcat(strObject,"\\");
strcat(strObject,FileName);
//打开客户端欲下载的文件
if((fp=fopen(p_FileName,"r")) == NULL )
{ //未成功打开文件
sprintf(SendBuffer,"Sorry, cannot open %s. 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);
return1;
}
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)
{ //循环读取文件并通过h_NewSocket发送到客户端
sprintf(SendBuffer,"%s",temp_buffer);
send(h_NewSocket, SendBuffer, 80, 0);
printf("."); //文件发送中,每发80个字节在屏幕打一个点号
}
fclose(fp);
sprintf(SendBuffer, "226 Transfer completed... \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;
return0;
}
/***********************************************************************
函数名: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;
}
5.2 客户端程序文件
/*********************************************************************
文件名: client.c
说明: 简单的ftp客户端程序文件,包含main函数及get、put等命令发送函数。
**********************************************************************/
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
#define DEFAULT_SERV_PORT 2416 //服务器的监听端口
#define DEFAULT_BUFFER_SIZE 2048 //缓冲区长度
char sz_ServIp[128]; // 服务器的IP地址
char sz_Msg2Snd[1024]; // 发给服务器端的字符串
int iPort =DEFAULT_SERV_PORT; // 服务器的监听端口
BOOL b_IsSendOnly = FALSE; // 只发消息,不收消息
int dirfun(SOCKET ); //"dir"命令处理函数
int getfun(SOCKET h_Socket4Cmd,charFileName[40]); //"get"命令处理函数
int putfun(SOCKET h_Socket4Cmd,charFileName[40]); //"put"命令处理函数
int pwdfun(SOCKET); //"pwd"命令处理函数
int cdfun(SOCKET h_Socket4Cmd,char pathname[40]); //"cd"命令处理函数
int mdfun(SOCKET h_Socket4Cmd,char DocName[20]); //"md"命令处理函数
int delfun(SOCKET h_Socket4Cmd,char name[20]); //"del"命令处理函数
int helpfun(); //"help"命令处理函数
/***********************************************************************
函数名:main
说明: 主函数
输入参数: int argc 输入参数长度 char *argv[]输入参数字符型数组
***********************************************************************/
int main(int argc, char **argv)
{
WSADATA wsd;
SOCKET h_Socket4Cmd;
char szBuffer[DEFAULT_BUFFER_SIZE];
int ret;
struct sockaddr_inserver;
structhostent *host = NULL;
charCmdWords[5],CmdWords2[40];
argv[1]="-s:127.0.0.1";
strcpy(sz_ServIp, &argv[1][3]);
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) //winsock初始化
{
printf("Failed to load Winsock library!\n");
return1;
}
h_Socket4Cmd= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(h_Socket4Cmd == INVALID_SOCKET)
{
printf("creating socket failed, error_code : %d\n",WSAGetLastError());
return1;
}
server.sin_family= AF_INET;
server.sin_port = htons(iPort);
server.sin_addr.s_addr = inet_addr(sz_ServIp);
if(server.sin_addr.s_addr == INADDR_NONE)
{
host =gethostbyname(sz_ServIp);
if (host== NULL)
{
printf("Unableto resolve server: %s\n", sz_ServIp);
return 1;
}
CopyMemory(&server.sin_addr, host->h_addr_list[0],
host->h_length);
}
if(connect(h_Socket4Cmd, (struct sockaddr *)&server,
sizeof(server)) == SOCKET_ERROR) //链接到服务器端
{
printf("connecting to server failed,error_num: %d\n",WSAGetLastError());
return1;
}
//接收服务器欢迎消息并打印到屏幕
ret = recv(h_Socket4Cmd, szBuffer,DEFAULT_BUFFER_SIZE, 0);
if (ret == 0) return 0;
else if (ret == SOCKET_ERROR)
{
printf("recv function failed,error_num:%d\n", WSAGetLastError());
return 0;
}
szBuffer[ret] = '\0';
printf("%s\n",szBuffer);
if(ret<15)
{
ret = recv(h_Socket4Cmd, szBuffer, DEFAULT_BUFFER_SIZE,0);
if (ret== 0) return 0;
else if(ret == SOCKET_ERROR) return 0;
szBuffer[ret] = '\0';
printf("%s\n",szBuffer);
}
helpfun(); //打印命令列表
while(1)
{
puts("------------------------------------------");
printf("ftp>");
scanf("%s", CmdWords); //输入命令扫描
//输入命令识别
if(strncmp(CmdWords,"dir",3)==0||strncmp(CmdWords,"DIR",3)==0)
{
dirfun(h_Socket4Cmd); continue;
}
else if(strncmp(CmdWords,"pwd",3)==0||strncmp(CmdWords,"PWD",3)==0)
{
pwdfun(h_Socket4Cmd); continue;
}
else if(strncmp(CmdWords,"?",1)==0)
{
helpfun(); continue;
}
elseif(strncmp(CmdWords,"quit",4)==0||strncmp(CmdWords,"QUIT",2)==0)
break;
scanf("%s", CmdWords2);
if(strncmp(CmdWords,"get",3)==0||strncmp(CmdWords,"GET",3)==0)
{
getfun(h_Socket4Cmd,CmdWords2); continue;
}
elseif(strncmp(CmdWords,"put",3)==0||strncmp(CmdWords,"PUT",3)==0)
{
putfun(h_Socket4Cmd,CmdWords2); continue;
}
elseif(strncmp(CmdWords,"cd",2)==0||strncmp(CmdWords,"CD",2)==0)
{
cdfun(h_Socket4Cmd,CmdWords2); continue;
}
else if(strncmp(CmdWords,"md",2)==0||strncmp(CmdWords,"MD",2)==0)
{
mdfun(h_Socket4Cmd,CmdWords2); continue;
}
else if(strncmp(CmdWords,"del",3)==0||strncmp(CmdWords,"DEL",3)==0)
{
delfun(h_Socket4Cmd,CmdWords2); continue;
}
else
{
puts("输入错误,按?号获取帮助,或重新输入!");
fflush(stdin); printf("\n");
}
}
closesocket(h_Socket4Cmd);
WSACleanup( );
return 0;
}
/***********************************************************************
函数名:dirfun
说明: 按协议规则构建目录查询命令并发送到服务器
输入参数: SOCKET h_Socket4Cmd,通过此socket发送命令到服务器。
***********************************************************************/
int dirfun(SOCKET h_Socket4Cmd)
{
int ret;
char*MSG="dir$";
char szBuffer[80];
strcpy(sz_Msg2Snd, MSG);
ret =send(h_Socket4Cmd, sz_Msg2Snd, strlen(sz_Msg2Snd), 0);
if (ret ==0) return 1;
else if (ret== SOCKET_ERROR)
{
printf("send funtion failed,error_num: %d\n",WSAGetLastError());
return 1;
}
while(!b_IsSendOnly)
{ //读取流并显示
ret =recv(h_Socket4Cmd, szBuffer, 80, 0);
if (ret== 0) return 1;
else if(ret == SOCKET_ERROR)
{
printf("recv function failed, error_num: %d\n",WSAGetLastError());
return 1;
}
szBuffer[ret] = '\0';
if(strncmp(szBuffer,"226 Close",strlen("226Close"))==0) break;
printf("%s",szBuffer);
if(strncmp(szBuffer,"500 Syntax error",strlen("500 Syntaxerror"))==0) break;
}
return 0;
}
/***********************************************************************
函数名:getfun
说明: 按协议规则构建文件下载命令并发送到服务器
输入参数: SOCKET h_Socket4Cmd,通过此socket发送命令到服务器。
charFileName[40], 欲下载文件的文件名
***********************************************************************/
int getfun(SOCKET h_Socket4Cmd,char FileName[40])
{
int ret;
FILE *fpre;
charszBuffer[80];
sz_Msg2Snd[0]='\0';
strcat(sz_Msg2Snd, "get$");
strcat(sz_Msg2Snd,FileName);
//向服务器发送get命令
ret =send(h_Socket4Cmd, sz_Msg2Snd, strlen(sz_Msg2Snd)+1, 0);
if (ret ==0) return 1;
else if (ret== SOCKET_ERROR)
{
printf("send function failed,error_num: %d\n",WSAGetLastError());
return1;
}
printf("Send %d bytes successfully!\n", ret);
ret =recv(h_Socket4Cmd, szBuffer, 80, 0);
szBuffer[ret] = '\0';
printf("%s\n",szBuffer);
//判断服务器是否在发送文件,若在发送则读取并保存到本地文件。
if(strncmp(szBuffer,"125 Transfering...",strlen("125 Transfering..."))==0)
{
if((fpre=fopen(FileName,"w")) == NULL )//打开文件准备写入
{ printf("error of opening file!");
return 1;
}
while(!b_IsSendOnly)
{ //读取流,每次80个字节
ret= recv(h_Socket4Cmd, szBuffer, 80, 0);
if(ret == 0) return 1;
elseif (ret == SOCKET_ERROR)
{ printf("receivefunction failed,error_num: %d\n", WSAGetLastError());
return 1;
}
//读取流中是有传输结束标志,停止接收
if(strncmp(szBuffer,"226Transfer",strlen("226 Transfer"))==0) break;
if(strncmp(szBuffer,"500 Syntax error",strlen("500 Syntaxerror"))==0)
{ //判断读取流中是否有通信错误提示
break;
}
if(-1 == fprintf(fpre,"%s",szBuffer)) //将读取的数据写入到文件
printf("error of writing into the file!");
}
fclose(fpre);
printf("transfer is completed!\n");
}
return 0;
}
/***********************************************************************
函数名:putfun
说明: 按协议规则构建文件上传命令并发送到服务器
输入参数: SOCKET h_Socket4Cmd,通过此socket发送命令到服务器。
charFileName[40], 欲上传文件的文件名
***********************************************************************/
int putfun(SOCKET h_Socket4Cmd,char FileName[40])
{
Printf(“篇幅所限,请读者完成。\n”);
iSynError=0;
return 0;
}
/***********************************************************************
函数名:pwdfun
说明: 按协议规则构建文件当前路径查询命令并发送到服务器
**********************************************************************/
int pwdfun(SOCKET h_Socket4Cmd)
{
Printf(“篇幅所限,请读者完成。\n”);
iSynError=0;
return 0;
}
/***********************************************************************
函数名:cdfun
说明: 按协议规则构建文件当前路径设置命令并发送到服务器
输入参数: SOCKET h_Socket4Cmd,通过此socket发送命令到服务器。
char pathname[40],欲设置的路径名
***********************************************************************/
int cdfun(SOCKET h_Socket4Cmd,char pathname[40])
{
Printf(“篇幅所限,请读者完成。\n”);
iSynError=0;
return 0;
}
/***********************************************************************
函数名:mdfun
说明: 按协议规则构建文件夹新建命令并发送到服务器
输入参数: SOCKET h_Socket4Cmd,通过此socket发送命令到服务器。
char DocName[40], 欲设新建的文件夹名
***********************************************************************/
int mdfun(SOCKET h_Socket4Cmd,char DocName[20])
{
Printf(“篇幅所限,请读者完成。\n”);
iSynError=0;
return 0;
}
/***********************************************************************
函数名:delfun
说明: 按协议规则构建文件删除命令并发送到服务器
输入参数: SOCKET h_Socket4Cmd,通过此socket发送命令到服务器。
char FileName[40], 欲设新建的文件夹名
**********************************************************************/
int delfun(SOCKET h_Socket4Cmd,char FileName[20])
{
Printf(“篇幅所限,请读者完成。\n”);
iSynError=0;
return 0;
}
/***********************************************************************
函数名:delfun
说明: 帮助函数,按?号回车,则列出命令列表
**********************************************************************/
int helpfun( )
{
puts("------------------------------------------");
puts("get:取远方的一个文件");
puts("put:传给远方一个文件");
puts("pwd:显示远方当前路径");
puts("dir:列出远方当前目录");
puts("md :在远方新建文件夹");
puts("cd :改变远方当前目录");
puts("del:删远方的一个文件");
puts("? : 显示你提供的命令");
puts("quit :退出返回");
return 0;
}
6 参考文献
[1] 特南鲍姆 (Tanenbaum A.S.), 潘爱民 (译)。计算机网络[M],北京:清华大学出版社,2005。
[2] 赖特 (Wright), 史蒂文斯 (Stevens), 陆雪莹 (译)。TCP/IP详解•卷2:实现[M],北京:机械工业出版社,2004。