文件传输协议的C语言实现文档格式.docx
《文件传输协议的C语言实现文档格式.docx》由会员分享,可在线阅读,更多相关《文件传输协议的C语言实现文档格式.docx(31页珍藏版)》请在冰点文库上搜索。
![文件传输协议的C语言实现文档格式.docx](https://file1.bingdoc.com/fileroot1/2023-4/30/8b8f63ef-44f0-41e1-8d66-87cedc4d975a/8b8f63ef-44f0-41e1-8d66-87cedc4d975a1.gif)
网络中两台主机各自在自己机器上建立通信的端点--套接字,然后使用套接字进行数据通信。
一个套接字包含五个基本元素:
协议类型、本地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<
stdio.h>
winsock2.h>
stdlib.h>
#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;
//出错退出
%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);
530Login\r\n"
while
(1)
{//接收客户端的命令并调用命令处理函数
n=0;
iSynError=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++;
0)||(bytes==0))
TheServerreceived:
'
%s'
cmdfromclient\n"
RecvBuffer);
//命令识别
//查看当前目录
if(strncmp(RecvBuffer,"
dir"
3)==0)sdirfun(h_NewSocket);
//查询当前目录路径
pwd"
3)==0)spwdfun(h_NewSocket);
//改变当前目录
if(strncmp(RecvBuffer,"
cd"
2)==0)scdfun(h_NewSocket);
//文件下载
get"
3)==0)sgetfun(h_NewSocket);
//文件上传
put"
3)==0)sputfun(h_NewSocket);
//新建文件夹
md"
2)==0)smdfun(h_NewSocket);
//删除文件
del"
3)==0)sdelfun(h_NewSocket);
quit"
4)==0)//退出命令
quit\n"
sprintf(SendBuffer,"
221Byebye...\r\n"
bytes=send(h_NewSocket,SendBuffer,
strlen(SendBuffer),0);
iSynError=0;
if(iSynError==1)//Syntaxerror
commandunrecognized,non-implemented!
500Syntaxerror.\n"
closesocket(h_NewSocket);
%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]='
strcat(strObject,"
dir"
strcat(strObject,path);
>
tmp.txt"
system(strObject);
//system函数执行shell命令
p_FiLeTemp=fopen("
"
r"
//打开执行结果文件,准备发送到客户端
125Transfering...\r\n"
while(fgets(temp_buffer,80,p_FiLeTemp)!
=NULL)//每次读取80字节发送
%s"
temp_buffer);
//
send(h_NewSocket,SendBuffer,strlen(SendBuffer),0);
fclose(p_FiLeTemp);
//发送完毕,关闭结果临时文件
226Transfercompleted...\r\n"
system("
deltmp.txt"
//删除结果临时文件
226Closethedatasocket...\r\n"
dircommandhasbeendone!
\n"
sgetfun
用于处理来自客户端的文件下载命令
intsgetfun(SOCKETh_NewSocket)
inti=4,k=0;
charFileName[20],temp_buffer[80];
char*p_FileName=strObject;
FILE*fp;
requiredfileis:
"
//打印文件名到屏幕
{//提取文件名
RecvBuffer[i],1,0);
%c"
RecvBuffer[i]);
FileName[k]=RecvBuffer[i];
if(RecvBuffer[i]=='
){FileName[k]='
if(RecvBuffer[i]