文件传输协议的C语言实现
定到监听端口,启动监听,阻塞在 Accept 函数等待连接请求的到来,当连接请求到达, Accept 函数为该请求创建新的 Socket 用于与对应的客户通信, 而原来 Socket 继续处于监 听状态。此后,主程序从新的 Socket 中读取命令,通过字串比较识别命令,若发现是 quit 命令,则关闭当前连接,准备接收下一个连接;若不是 quit 命令,则转移到相应的命令 处理函数,处理完毕后继续在该 Socket 上读取命令并进行处理。各命令处理函数的设计 请参看源代码。
图 ChpNum-2 基于 TCP 的网络应用程序
(1)WSAStartup()函数和 WSACleanup()函数 由于 Winsock 2 提供的 API 服务是以动态链接库 ws2_32.dll 实现的,所以必须先调 用 WSAStartup() 函数对 ws2_32.dll 进行加载初始化,协商 Winsock 的版本支持,并分配 必要的资源。 在应用程序关闭套接字后, 还应调用 WSACleanup( )函数来终止和卸载动态
WSADATA wsd; char SendBuffer[80],RecvBuffer[80];//发送缓冲区及接收缓冲区 #define DEFAULT_LSTN_PORT 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); 2416 //本地默认监听端口
令字$路径名/文件名”,即 test.txt 文件下载命令格式为“命令字$路径名/文件名”。
图 ChpNum-3 程序流程
图 ChpNum-3(a)示出了客户机的主程序流程,初始化 Winsock 后,用 socket 函数新 建一个 socket,填写入服务器的及 IP 地址及监听端口后,利用 connnect 函数连接到服务 器后即提示用户输入 ftp 命令, 程序阻塞在 scanf 函数。 用户输入命令后, scanf 函数返回, 通过字符串比对函数 strncmp 识别命令,并调用相应的命令发送函数,若输入的是 quit 命令,客户端程序退出。命令处理函数主要工作有两个,一是构建命令字节流发送到服 务器,二是与服务器交互该命令的后续执行数据,例如,对于 get 命令,该函数在发出 get 命令请求字节流后,要接收服务器下发的文件数据。各命令处理函数的实现请参见源 代码。 图 ChpNum-3(b)示出了服务器端主程序流程,先初始化 Winsock,建立 Socket 并绑
第 ChpNumБайду номын сангаас章 文件传输协议的 C 语言实现
1 设计目的
本设计旨在利用 Winsock 2.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 control Protocol,传输控制协议)之上的一个应用层协议, 应用程序之间的通信需要用到传输层提供的字节流透明无误传输服务。Windows 操作系 统具有 TCP/IP 协议栈,应用程序可通过 Winsock API 函数的调用实现端到端透明数据链 接的建立。 3.2 Winsock API 介绍 因特网 (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
5 程序代码 5.1 服务器端程序文件
/********************************************************************* 文件名: server.c 说明: 简单的 ftp 服务器端程序文件,包含 main 函数及 get、put 等命令处理函数。 **********************************************************************/ #include <stdio.h> #include <winsock2.h> #include <stdlib.h> #pragma comment(lib,"ws2_32.lib")
图 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 操作有没有完成都会立
4 软件设计
本设计客户端及服务器端均采用单线程实现,命令和数据的传输在同一个 Socket 链 接上进行。 客户端支持 DIR(远端文件夹查询)、 GET(文件下载)、 PUT(文件上传)、 PWD(远 端当前路径查询)、CD(远端当前路径设置)、MD(远端文件夹创建)、DEL(远端文件删除) 等 7 个常用 FTP 命令。 用户命令格式为“命令字 路径名/文件名”,如下载当前目录下的 test.txt 文件,则用 户在控制台界面输入的命令格式为“GET test.txt”。客户机和服务器的命令格式约定为“命
即返回,若未完成一般会返回错误码 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 提供的主要接口函数逐 一进行介绍。
链接库 ws2_32.dll,释放资源。 (2)socket()函数 服务进程和客户进程在通信前必须创建各自的套接字,然后才能用相应的套接字进 行发送、接收操作,实现数据的传输。服务进程总是先于客户进程启动,服务进程和客 户进程调用 socket() 函数创建套接字。 (3)bind( ) 函数 当用 socket( )创建套接字后,它便存在于一个名字空间(地址族)中,但并未赋名。 bind ( )函数通过给一个未命名套接字分配一个本地名字(主机地址/端口号)来为套接字 建立本地捆绑。客户端一般隐式地向操作系统请求一个随机的未使用过的临时端口号, 跟自己的 IP 地址一起,与所创建的套接字建立联系,由于该临时端口号客户端程序事先 是不确定的,因此不显式地使用绑定函数。 (4)listen( )函数 调用 listen( )函数对服务器上套接字启动监听,即允许客户连接请求开始排队。 (5)accept( )函数 服务器设置监听工作方式后,通过调用 accept( ) 函数使套接字等待接受客户连接。 如果已有连接请求到来,该函数会返回一个新的套接字描述符,它对应于已经接受的那 个客户端连接。对于该客户机后续的所有操作,都应使用这个新套接字。至于原来那个 监听套接字,它仍然用于接受其他客户机连接,继续处于监听模式。 (6)connect( )函数 客户端利用 connect( ) 函数和服务器建立一个端到端的连接。 (7)closesocket( )函数 网络通信任务完成后,利用本函数释放套接字占用的所有资源。
协议栈, (包括传输层协议 TCP 及 UDP; 网络层协议 IP、 ICMP 及 IGMP;链路层协议 ARP 和 RAR),该模块的相关功能以动态链接库的形式被应用程序调用。操作系统接受网卡驱 动程序的注册,网卡驱动程序本质上是一套控制网卡硬件收发报文的函数,也是以动态 链接库的形式被调用。物理通信介质是指网卡驱动芯片及其外围电路,完成链路层数据 帧的封装/解封、发送/接收等功能。