`

基于java的远程登录和文件传输功能的实现

 
阅读更多

基于java的远程登录和文件传输功能的实现

摘要

Internet发展至今产生了两种比较重要的网络体系结构:ISO/OSI和TCP/IP参考模型,ISO/OSI模型有7层:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层。而TCP/IP模型只有4层:主机至网络层,互连网层,传输层,应用层。其中TCP/IP参考模型中的应用层包含所有的高层协议,最早引入的是虚拟终端协议,文件传输协议和电子邮件协议。本文对网络计算机相互通信的机理进行比较细致和深入的分析,并采用java编程工具实现了远程登录和文件传输的功能。

关键词:TCP/IP JAVA远程登录 文件传输

Abstract

Internet produces two kinds of more important networksystem structure so far that Internet is developed, ISO/OSI and TCP/IP consultmodels, ISO/OSI model is 7 layers: Physics layer, data link layer, network layer,transport layer, session layer, persentaltion layer, opplication layer. AndTCP/IP model is 4 layers: From host computer to Internet, network layer, transportlayer, opplication layer. TCP/IP among them consult application layer of modelinclude all on the senior level Protocol, introduce TELecommunications NETwork, File Transfer Protocol and Simple MailTransfer Protocol, Careful and deep analysis that this text compares mechanismthat the network computer communicates each other, and adopt java programmingtool to realize the functions of telecommunications network and file transfer.

KEY WORDS: TCP/IPJAVA TELNET FTP

目 录

前言--------------------------------------------------------------------------------------------1

第一章互联网概述 ---------------------------------------------------------------------- 3

1.1 互联网的发展状况--------------------------------------------------------------------3

1.2 互联网的应用-------------------------------------------------------------------------5

第二章互联网中两种重要的参考模型 -----------------------------------------------6

2.1 OSI参考模型-------------------------------------------------------------------------6

2.2 TCP/IP参考模型--------------------------------------------------------------------8

第3章远程登录的功能实现 ----------------------------------------------------------------9

3.1 虚拟终端-------------------------------------------------------------------------------9

3.2 远程登录协议 --------------------------------------------------------------------------11

3.3 有关远程登录的实现方法(线程的概念) ---------------------------------------13

3.4 远程登录的最终完成---------------------------------------------------------------18

第4章文件传输的执行行为和功能------------------------------------------------------27

4.1传输文件--------------------------------------------------------------------------------27

4.2 文件传输的传输模式----------------------------------------------------------------28

4.3 构造控制连接和数据连接------------------------------------------------------------32

4.4 文件传输程序的完成-----------------------------------------------------------------42

参考文献---------------------------------------------------------------------------------------54

历史的发展表明I n t e r n e t的产生要追溯到最开始的时间,穴壁上的画、烟信号、驿站—所有这些通信方式都使我们的祖先一直在考虑一个更好的通信方式。接着就发展出了电报、电话、无线电—现在这些东西随处可见。在这之后,计算机出现了,那时,最大最耗电,并产生大量热的计算设备也比不上现在最小的掌上计算器,这些大家伙当时用于赢得战争和

人口普查,但当时计算机的数量极少,也很少有人买得起,况且还需要很多房间装下它们。

今天,I n t e r n e t已经发展得更加商业化,更加面向消费者,尽管基本目的发生了改变,但其最初的所有质量标准(也就是开放式、抗毁性和可靠性)依然是必需的。这些特性包括可靠传输数据、自动检测和避免网络发生错误的能力。更重要的就是T C P / I P是一个开放式通信协议,开放性意味着在任何组合间,不管这些设备的物理特征有多大差异,都可以进行通信。

毫无疑问,TCP/IP (通常它是指传输控制协议/网际协议, TransmissionControlProtocol/Internet Protocol)是发展至今最成功的通信协议,它被用于当今所构筑的最大的开放式网络系统I n t e r n e t之上就是其成功的明证。I n t e r n e t最初的设计是为了满足美国国防的需要,具体来讲就是使美国政府即使在遭受核打击时也能保证通信不间断, T C P / I P就是用于这个目的的。

正是T C P / I P使得I n t e r n e t发展到今天这个状态,随后, I n t e r n e t正像它的“革命先驱者”,如打印机、电和计算机一样改变了我们生活和工作的方式。如果没有诸如H T T P、S M T P、TELNET和F T P这些流行的协议和服务, I n t e r n e t不会比一个由大量计算机所连成毫无价值的节点强多少。

本文将比较详细介绍流行的I n t e r n e t协议中的两种远程登录和文件传输功能实现方法。

第一章 互联网概述

1.1 互联网的发展状况

10年来,互联网在风中一路飘摇,舆论像是骑在墙头上的看客,用各种各样东倒西歪的姿势见证着一切,用各种各样慷慨激昂、不着边际的语言谈论着一切。从文化和社会学的某种角度看,现代化在中国一直是一个有意思的历史过程;而当第二次现代化与第一次现代化交织在一起,在中国这样一个半封闭社会当中含混其辞、跌跌闪闪地匍匐前进的时候,农民式的市民,在目光、内心和行为举止当中尤其充满了荒诞。互联网这么一个不是东西的东西,太容易成为第三世界公民们的谈资。过去有关互联网的报道、分析和评论,很多在开口的一刹那很可能就已经是错误的,现在也是如此。诸多无比经典的荒诞,无需太多讨论和佐证,因为无数的无知和短视行为最终已经被各种各样的事实证明:它们而不是互联网,是错误的。互联网一直以它自己的方式进行着,它一直在那儿。

历史的荒原经常风云突变,互联网一直是预言中的下一个重大事件,但是我们能够目睹到的似乎一直都是一个又一个或明或暗的瞬间。就像我们曾经感触到、不曾感觉到的很多关于互联网的变化一样,互联网的到来是一个若隐若现、动态变幻的历史过程。没有绝对的开启点,也没有绝对的终结点。互联网发展进程的切换也好,互联网与传统产业的结合也好,互联网自身的不断创新和扩展也好,都是这个动态的历史过程的不是片段的片段。

  我们不知道互联网从哪里来,因为互联网没有确定的、唯一的源头;我们也不知道互联网将会向哪里去,因为迄今为止我们并不了解互联网的全部,而且我们也还没有看到互联网的全部。互联网只是呈现出它在今天所初步呈现出来的这个样子。今天的互联网,不是互联网的全部。互联网,也不是那个“重大事件”的全部。那个重大事件正在把我们带向哪里?我们正在把互联网带向何方?或者更准确地讲,互联网将会把我们带向何方?

  如果说作为重大事件一个不是片段的片段的互联网的历史才刚刚开始,如果说在2004年昔日充满荒诞与喧嚣的狂飙突进的网络运动已经告一段落,网络部落的大队人马走过蛮荒之地,那么,眼前到来的,是否就是亚马逊平原?2004年,没有人知道互联网的全部,没有人知道关于互联网的所有答案。

  而且,我们其实仍然不是非常确切地知道互联网是不是一种技术,或者说并不是一种技术,或者说是技术的商业,抑或是商业的技术。或者说,是一种生产力,还是一种生产关系?是技术的制度,还是制度的技术?所以,互联网对于我们的社会,互联网关于技术、资本、财富、商业的启蒙已经结束了吗,或者是还没有结束?互联网是经常被我们借用的符号意义上的“他者”,还是正在到来的下一个重大事件所驱动的变革的主体?或者仅仅只是阶段性的主题?这些问题,都是需要回答的未解之题。

  所以,2004年,关于互联网,我们不要急于做出这样那样的判断,迫切宣告这样那样的结论。互联网没有结束,所以互联网没有结论。2004年,在中国互联网全功能接入国际互联网10周年庆典仪式刚刚隆重举行的这个时候,我们不要四处遗撒关于互联网的种种大词汇。所谓10年,是我们自己给互联网定义的一个只属于我们自己的仪式。互联网一直以它自己的方式进行着,它一直在那儿。它并没有因为这个仪式而有任何改变。

  互联网的发展是开放的,过去是开放式的,现在是开放式的,未来更是开放式的。迄今为止,关于互联网的所有定义都是暂时的。所以,说我们不知道,比说我们知道要好得多。因为,极有可能在说知道的那一瞬间,我们对于互联网的认知已经步入了歧途。

所以,真正想要说的是:在第二次启蒙、互联网的第二次发展热潮到来之前,让我们对自己的思想来一次非常理性的清零。2004,我们重新出发。而且,我们必须承认,到现在为止,我们并不是特别了解互联网。也只有这样彻底的清零,彻底抛弃关于互联网的种种成见、偏见和意见,在2004年,我们才可能重新出发,正确地出发,正确面对我们自己为自己定义的互联网的下一个10年。

1.2 互联网的应用

从90年代开始,计算机网络开始为居家的个人用户提供服务。下面简要的介绍方兴未艾的最激动人心的3种服务:访问远程信息,个人间通信和交互式信息。

访问远程信息有多种形式。一个常见的例子是访问财务部门。许多人现在用电子方式支付帐单,管理银行户头和进行投资。居家购物也开始流行,人们可以浏览成千上万的联机货物清单。某些清单还能很快提供相应产品的即时影响,只需轻轻一点该产品的名字就可以了。

第二类应用是访问信息系统。比如当前世界范围内使用的万维网,它包含了有关艺术,商业,餐饮,政府,健康,历史,爱好,娱乐,科学,体育,旅游等方面的信息。

以上所有应用都涉及到任何远程数据库的交互。网络的第二类广泛应用将使人际交互,基本上它是21世纪替代19时机发明的电话的手段。电子邮件或者说email,以在上百万人当中使用,并且将很快包含声音,图像,与文本一起传送。但这要花相当长的一段时间才能做到尽善尽美。

再一群经选择的人之间使用世界范围的新闻组已是常事,他们在一起讨论各种可能的话题,并且有越来越多的公众参加进来。在讨论中,某人发表一篇文章,而其他订阅该新闻组的人就可阅读它,通常这种讨论很有趣也很热烈。

第三类应用是娱乐,这是一个巨大并且还在继续增长的工业。这里最吸引人的应用是视频点播,它可能会把其他所有应用都赶出去。新电影可能会是交互式的,观众可以在某一时刻选择故事的发展方向而在拍摄时已经为各种可能的情节发展提供了场景。直播电视也肯恩硅会变成交互式的,观众将参与问答节目,选择竞赛者等。

在另一方面,视频点播并不一定会成为主宰。游戏可能会使黑马。我们已经有了多人实时模拟游戏,如在虚拟地牢中玩捉迷藏,或者飞行模拟,一组玩家与另一组娃家对玩。如果能做到用头盔来玩,并由实施三维动作及图片质量的移动图像,我们就拥有了世界范围的共享虚拟现实。

简单的说,综合信息,通信和娱乐的能力将造成一个新兴的,基于计算机网络的巨大工业。

第二章 互联网中两种重要的参考模型

我们将重点介绍两个层次模型,正是这两个模型使的开放式数据通信成为可能。这两个模型是开放式系统互联(Open Systems Interconnect,OSI)参考模型和TCP/IP参考模型。这些模型通过将网络分为各种功能模块,从而大大提高了网络的可理解程度。这些模块被分为层。这些层次的命令以及开放通信的概念,为TCP/IP各种组件和使用的更广泛探索提供了场景和依据。

2.1 OSI参考模型

国际标准化组织( I S O )开发了开放式系统互联( O S I)参考模型,以促进计算机系统的开放互联。开放式互联就是可在多个厂家的环境中支持互联。该模型为计算机间开放式通信所需要定义的功能层次建立了全球标准。

该模型很成功地达到了它最初的目的:将它自己付诸讨论通过。至此,早先的专利极端

集成方式已经消失了。今天,开放式通信是必需的,令人惊奇的是,很少有产品是完全的O S I模式;相反,其基本层次框架常常满足新标准。然而, O S I参考模型为示范网络的功能结构提供了可行的机制。

O S I模型将通信会话需要的各种进程划分成7个相对独立的功能层次,这些层次的组织是以在一个通信会话中事件发生的自然顺序为基础的。

1. 物理层

最底层称为物理层(Physical Layer),这一层负责传送比特流,它从第二层数据链路层( D D L )接收数据帧,并将帧的结构和内容串行发送即每次发送一个比特,然后这些数据流被传输给D L L重新组合成数据帧。

2. 数据链路层( D L L )

OSI 参考模型的第二层称为数据链路层( D L L )。与所有其他层一样,它肩负两个责任:发送和接收。它还要提供数据有效传输的端端(端到端)连接。

在发送方, D L L需负责将指令、数据等包装到帧中,帧( f ra m e )是D L L层生成的结构,它包含足够的信息,确保数据可以安全地通过本地局域网到达目的地。

3. 网络层

网络层负责在源机器和目标机器之间建立它们所使用的路由。这一层本身没有任何错误检测和修正机制,因此,网络层必须依赖于端端之间的由D L L提供的可靠传输服务。

4. 传输层

传输层提供类似于D L L所提供的服务,传输层的职责也是保证数据在端端之间完整传输,不过与D L L不同,传输层的功能是在本地L A N网段之上提供这种功能,它可以检测到路由器丢弃的包,然后自动产生一个重新传输请求。

传输层的另一项重要功能就是将乱序收到的数据包重新排序,数据包乱序有很多原因。例如,这些包可能通过网络的路径不同,或者有些在传输过程中被破坏。不管是什么情况,传输层应该可以识别出最初的包顺序,并且在将这些包的内容传递给会话层之前要将它们恢复成发送时的顺序。

5. 会话层

O S I会话层的功能主要是用于管理两个计算机系统连接间的通信流。通信流称为会话,它决定了通信是单工还是双工。它也保证了接受一个新请求一定在另一请求完成之后。

6. 表示层

表示层负责管理数据编码方式,不是所有计算机系统都使用相同的数据编码方式,表示

层的职责就是在可能不兼容的数据编码方式,例如在A S CI I和E B C D I C之间,提供翻译。

7. 应用层

OSI 参考模型的最顶层是应用层,尽管它称为应用层,但它并不包含任何用户应用。相反,它只在那些应用和网络服务间提供接口。

2.2 TCP/IP参考模型

与OSI参考模型不同,TCP/IP模型更侧重于互联设备间的数据传送,而不是严格的功能层次划分。它通过解释功能层次分布的重要性来做到这一点,但它仍为设计者具体实现协议留下很大的余地。因此,OSI参考模型在解释互联网络通信机制上比较适合,但TCP/IP成为了互联网络协议的市场标准。

TCP/IP是处理上述所有操作并和远程主机通信的一个环境。TCP/IP由四层组成,这与OSI由七层组成不相同。这四层包括:

1.链路层

链路层包括ARP和RARP,负责报文传输。

2.网络层

网络层由以下协议组成:ICMP、IP、IGMP、RIP、OSPF和用于路由的EGP,用户不必操心这些,因为它们是相当底层的东西。

3.传输层

传输层包括UDP和TCP。UDP几乎不进行检查,而TCP提供传输保证。

4.应用层

应用层包括SMTP、FTP、NFS、NIS、LPD、Telnet和Remote Login。对于大多数Internet用户来说这些都是很熟悉的。

以下章节我将详细介绍远程登录和文件传输功能的实现。

第3章远程登录的功能实现

3.1 虚拟终端

简单的说,远程登录让一台电脑连线载入另外一部电脑。在网络上的应用程序多半是采用Client/Server模式,也就是一定有一端是请求端,请求端执行远程登录请求程序。在主机这一端则有装置服务程序来接受连线请求,不过在多半的情况下,主机端则是Client与Server两者都有。

远程登录的使用程序与您平常在本地通过电话线或任何其他方式载入一部主机并没有很大区别,您在对方主机一定要有一个私人使用帐号,以及您的通行密码,这样您才有办法连线进入该主机系统。另外,在Internet上,有相当多的各式各样服务系统也是通过这种方式来提供服务,其中绝大部分是免费的服务,像Hytelnet、BBS、Gopher 及Archie等等就是,这类系统通常开放有公用帐号,且无须使用密码。

远程登录协议是通过网络来调用服务器,并使自己的机器作为服务器的终端的协议。而作为远程登录协议安装的程序是远程登录客户端程序(参见图3.1)。


           虚拟终端通过网络

连接到服务器 

远程登录客户端             远程登录服务器


可以利用网络功能自由选择服务器

                         远程登录服务器

图3.1 虚拟终端

远程登录客户端按图3.2所示连接到远程登录服务器,开始时,远程登录客户端对远程登录的标准端口23提出TCP连接请求。服务器建立其与客户端的TCP连接。此后,客户端与服务器之间交换连接时所必需的信息。

为了能成为虚拟终端,服务器和客户端双方必须确认客户端要采用的界面控制功能和服务器端所采用的终端控制功能,这种操作过程成为协商(negotiation)。

协商的具体步骤由远程登录协议决定。协商结束之后,用户即可使用远程登录的终端功能了。此外,不仅是在会话开始,即使是在使用终端功能的过程中,如果有必要也要进行协商。

从网络的观点来看,远程登录不过是一个只建立起远程登录的客户端与服务器之间的连接的简单协议,但要真正完成通信,远程登录客户端则需要一个结构相当复杂的程序。作为虚拟终端程序,远程登录必须提供终端功能。因此,除了完成协商以外,还要实现如画面控制和语言处理等终端仿真功能。此外,不同的服务器所提供的终端功能也会有差异,因此,对于每个希望连接的目的服务器的多样性,使的远程登录程序变得复杂起来。


   远程登录客户端             远程登录服务器

              23号

   连接请求              

同意请求

协商(negotiation)           协商(negotiation)

执行登录(login)           

接受登录(login)

作为终端通信              提供终端通信  

协商(必要时)             协商(必要时)

      图3.2 远程登录的连接过程

3.2 远程登录协议

现在,让我们来浏览一下远程登录协议,也包括实际安装远程登录程序要重点注意的一些地方。

根据RFC854,远程登录协议,依据由表3.1所示的3个中心思想构成。

表3.1构成远程登录协议所遵循的3个中心思想

NVT(Network Virtual Terminal)

具备最低限度的终端功能的网络虚拟终端。提供虚拟打印机和键盘。NVT提供基本的行方式的输入输出

协商的选项

使用NVT以上的功能进行通信时,确定通过协商执行的选项

服务器端和客户端的对称性

利用协商扩大通信功能,是服务器端和客户端的人以一方都可以对称的工作

NVT(Network Virtual Terminal)定义了构成终端所应具备的最低限度的功能。使用远程登录协议尝试连接的初期,服务器端和客户端都要使用的NVT功能进行通信。

远程登录协议中的协商可以从服务器端和客户端的任何一方执行,此即表4.1中所说的对称性。

表3.2协商中使用的命令

名称

代码

含义

IAC

255

此后的数据作为命令解释(Interpret As Command)

WLII(选项命令)

251

同意执行指定选项

WONT(选项命令)

252

不同意执行指定选项

DO(选项命令)

253

同意执行对方的指定选项的要求

DONT(选项命令)

254

不同意执行对方的指定选项的要求

SB

250

开始选项协商

SE

240

结束选项协商

协商通过以IAC为首的连续3个命令组合后进行。例如,使用RFC857中规定的回声选项(ECHO,代码为1)时,要向对方发送如下信息:

IAC WILL ECHO(代码为255 255 2)

开始的IAC表明接在它之后的数据都表示命令,第二和第三个字符串WILL和ECHO用于向对方说希望使用回声选项。

对于WILL的回答可以是DO或DONT。例如,对于上例,如果同意使用回声选项,就应该使用DO返回如下形式的应答:

IAC DO ECHO(代码为 255 253 1)

于是,双方之间达成了同意执行回声选项的协议。相反的,如果不同意执行回声选项,要使用DONT命令来应答:

IAC DON’T ECHO(代码为255 254 1)

上述讨论说明,对于利用WILL进行执行选项请求的应答总是DO或DON’T,这与使用WONT进行请求时的应答一样。想使对方执行选项时使用命令DO,而对应于DO的回答既可以是WILL,也可以是WONT。

根据对称性,以上的协商可以从客户端和服务器端的任何一方执行,但通常是以从服务器端提出请求而客户端进行应答的方式进行协商。

3.3 有关远程登录的实现方法(线程的概念)

仅通过简单地TCP连接设置是不能实现远程登录的。实际安装远程登录时,必须要考虑到如何进行协商和终端仿真等多种细致而繁复的工作。不过,如果不先建立连接,自然也就没有了后话也是事实。因此,我们先来设计能够实现从双向建立TCP连接的客户端程序。

如果单说远程登录客户端程序的原型,或者说是工作在这一层次上的程序,他只要能够实现如图3.3所示的处理也就够了。

在图3.3中,原始远程登录程序从TCP连接开始,TCP连接作为远程登录的输入并构成终端输出。之后,网络段应与来自标准输入端所形成的输入以及其他不同的输出端构成连接,及网络端应构成键盘输入和到其它网络输出的中介。



远程登录客户端

远程登录服务器

图3.3 远程登录原始程序的行为

利用指定服务器的地址和端口号开始TCP连接要经过一系列信息交换,如下所示:

serversocket=newSocket(host,port);

随后,可以对此socket进行读写操作,并可将结果送至标准输入输出。为此,必须构造负责输入输出的类中的OutputStream类以及BufferedInputStream类的对象,即进行如下的准备工作:

ServerOutput=ServerSocket.getOutputStream();

ServerInput=newBufferedInputStream(serversocket.getInputStream());

利用上述对象进行读出和写入时,可以使用其read和write方法。先检查一下输入流的状态,只有在输入时进行处理。将上述方法稍微完善就得到了如图3.4所示的程序。

while(true){

byte[]buff=new byte[1024];

if(serverInput.available()>0){

intn=serverInput.read(buff);

system.out.write(buff,0,n);}

if(System.in.available()>0){

intn=System.in.read(buff)l

serverOutput.write(buff,0,n);}}

图3.4 以网络和标准输入输出对应的两种读写操作

图3.4中先使用available方法检查是否可以从输入流中读取数据,如果可以,则调用read方法将数据读入进来,再用标准数据将数据发送到服务器端。

现在我们考虑使用线程(thread)的方法。线程是一种使程序可以并行执行的结构。

如果是对象作为线程来执行,则不同的对象可以实现并行操作。那么,现将来自网络的输入和向网络的输出各自作为线程来处理,就可能实现输入和输出的并行操作了。

作为使用现成的简单实例,此处给出了一个用于输出清单3.1中所示数据的程序 Threadtest.java。

Threadtest程序中利用2个线程来输出从2至10之间的数。由于线程执行的并行性,什么时候输出以及哪一个线程输出会因实际执行时的环境变化而改变。

图3.5示出1和2两次实际运行的结果。尽管图中所示为相同环境,相同的操作系统以及同一个程序的实际运行结果,但线程的执行顺序也有差异。

清单3.1线程的使用示例――Threadtest程序

程序名

Threadtest.Java

处理内容概要

执行2个输出从0到10递增的数值的线程

结束条件

2个线程结束时整个程序结束

输入设备

输出设备

显示器

//线程测试程序Threadtest.Java

//此程序输出从0到10的数,2个线程并行执行

//使用方法: Java Threadtest

//导入库

import java.net.*;

import java.io.*;

// Threadtest 类

// Threadtest 类用于生成和管理线程

public class Threadtest {

// main方法

public static void main(String[]arg){

try {

// 生成线程是用的CountTen类的对象

CountTen no1 =

new CountTen("No1");

CountTen no2 =

newCountTen("No2");

//生成线程

Thread no1_thread = newThread(no1);

Thread no2_thread = newThread(no2);

// 启动线程

no1_thread.start();

no2_thread.start();}

catch(Exception e){

System.err.print(e);

System.exit(1); }}}

// CountTen类

// 从0至10递增的数

class CountTen implements Runnable {

String myname ;

// 构造器 接受线程名

public CountTen(String name){

myname = name;}

// 处理部分的函数体

public void run(){

int i ;

for(i=0;i<=10;++i){

System.out.println(myname +":" + i) ; }}}

在java语言中很同意将一个对象用作线程。作为对Runnable接口的实现,Threadtest程序中通过定义类来构造线程。就是说,应该按照如下形式来书写类名,进行类的定义,则类CountTen就可以作为线程来执行:

class CountTen implements Runnable {

… …

CountTen类的对象作为实际的线程启动时,类中的run方法就会被调用并执行相应地处理工作。CountTen的run方法的工作很简单,只是将0到10的数值一个一个地发送到标准输出上.

图3.5 Threadtest程序的运行实例

在网络处理中,有很多是利用依赖线程进行并发处理的方法来描述的过程,简单易懂。如果也用如图3.6所示的2个线程来描述远程登录,则可以实现远程登录。


远程登录客户端服务器(SMTP,HTTP)


服务器

(SMTP,HTTP)

图3.6 利用线程实现远程登录

3.4 远程登录的最终完成

远程登录是一个具有类似连接到HTTP服务器以读取数据,或是连接到SMTP服务器以发送邮件等功能的程序,并且考虑到了协商问题,因此可以真正成为远程登录客户端。

通常,客户端要通知服务器端是否提供了VT-100等适当的终端仿真功能。为此,远程登录客户端自然要用软件方法实现终端仿真功能。因此,所讨论的远程登录客户端程序也以支持最基本的协商和终端仿真功能为准。

最基本的功能莫过于NVT。这里,我将实现以NVT为终端功能而进行的协商。

图3.7给出了远程登录程序的启动方法和执行行为示例。程序清单由清单3.2给出。在图3.7中,利用远程登录程序连接到 bbs.zsu.edu.cn(中山大学“逸仙时空”BBS)。

图3.7远程登录程序的运行示例

图3.7是一次执行远程登录的示例。根据连接方服务器的不同,上述过程和输出信息也有差异。

此外,Ycdl.java程序在按下<Ctrl>+C键时运行结束。即使与服务器的连接结束了,如果不按下<Ctrl>+C键,Ycdl.java程序也不会自己终止。

清单3.2 远程登录程序

程序名

Ycdl.java

处理内容概要

建立连接,实现与指定地址的端口的标准输入输出操作。没有指定端口号是假定为23号端口

若被连接方是23号端口时进行远程登录协商。

结束条件

按<Ctrl>+C键结束输入

输入设备

键盘,网络

输出设备

显示器,网络


//远程登录程序Ycdl.Java

//此程序用于建立以实现与指定地址的端口进行标准输入输出操作的连接

//若被连接方是远程登录端口(23号)时进行远程登录协商

//协商时,拒绝服务器端的所有请求

//使用方法(1):java Ycdl 服务器地址 端口号

//使用方法(2):java Ycdl 服务器地址(2)的场合, 假定端口号为23号(远程登录端口)

//运行示例: java Ycdl bbs.zsu.edu.cn

//要结束请按<Ctrl>+C键

//导入库

import java.net.*;

import java.io.*;

// Ycdl类

// Ycdl类用于实现网络连接管理

// 利用StreamConnector类进行线程处理

//有2种构造器,分别对应使用方法(1)和(2)

public class Ycdl {

SocketserverSocket;//连接用Socket

publicOutputStream serverOutput;//用于网络输出的流

publicBufferedInputStream serverInput;// 用于网络输入的流

String host;// 连接服务器地址

int port; //连接服务器的端口号

static finalint DEFAULT_YCDL_PORT = 23;// 远程登录的端口号(23号)

//构造器(1):指定地址和端口号时用

publicYcdl(String host, int port){

this.host =host;

this.port =port;}

//构造器(2)只指定地址时用

public Ycdl(Stringhost){

this(host,DEFAULT_YCDL_PORT);// 假定为远程登录端口

}

//openConnection方法

//有地址和端口号构成Socket而形成流

public voidopenConnection()

throwsIOException,UnknownHostException

{

serverSocket= new Socket(host, port);

serverOutput= serverSocket.getOutputStream();

serverInput= new

BufferedInputStream(serverSocket.getInputStream());

//若连接的是远程登录端口则进行协商

if (port ==DEFAULT_YCDL_PORT){

negotiation(serverInput,serverOutput);}}

// main_proc方法

//启动进行网络处理的线程

public voidmain_proc()

throws IOException

{

try {

// 生成线程用StreamConnector类的对象

StreamConnectorstdin_to_socket =

newStreamConnector(System.in, serverOutput);

StreamConnectorsocket_to_stdout =

newStreamConnector(serverInput, System.out);

//生成线程

Threadinput_thread = new Thread(stdin_to_socket);

Threadoutput_thread = new Thread(socket_to_stdout);

//启动线程

input_thread.start();

output_thread.start();}

catch(Exceptione){

System.err.print(e);

System.exit(1);}}

// 定义用于协商的命令

static finalbyte IAC = (byte) 255;

static finalbyte DONT = (byte) 254;

static finalbyte DO = (byte) 253;

static finalbyte WONT = (byte) 252;

static finalbyte WILL = (byte) 251;

// negotiation方法

//利用 NVT 进行协商通信

static voidnegotiation(

BufferedInputStreamin,OutputStream out)

throwsIOException

{

byte[] buff= new byte[3];//接受命令的数组

while(true){

in.mark(buff.length);

if(in.available() >= buff.length) {

in.read(buff);

if(buff[0] != IAC){// 协商结束

in.reset();

return;

}else if (buff[1] == DO) {//对于DO命令……

buff[1]= WONT;//用 WON'T作为应答

out.write(buff);}}}}

// main方法

// 建立TCP 连接,开始处理

public staticvoid main(String[] arg){

try {

Ycdl t= null;

// 有参数个数决定调用哪个服务器

switch(arg.length){

case1://只指定服务器地址

t =new Ycdl(arg[0]);

break;

case2://指定地址和端口

t =new Ycdl(arg[0], Integer.parseInt(arg[1]));

break;

default://使用方法不正确时

System.out.println(

"usage:java Ycdl <host name> {<port number>}");

return;}

t.openConnection();

t.main_proc();

}catch(Exceptione){

e.printStackTrace();

System.exit(1);}}}

// StreamConnector类

// 接受流参数以二者结合实现数据传递

// StreamConnector类是用于构造线程的类

class StreamConnector implements Runnable {

InputStream src= null;

OutputStreamdist = null;

// 构造器 接受输入输出流

publicStreamConnector(InputStream in, OutputStream out){

src = in;

dist = out;

}

//执行处理的函数体

// 无限循环进行流的读写

public voidrun(){

byte[] buff= new byte[1024];

while(true) {

try {

intn = src.read(buff);

if(n > 0)

dist.write(buff,0, n);}

catch(Exceptione){

e.printStackTrace();

System.err.print(e);

System.exit(1);}}}}

下面,我们来解释一下远程登录程序中的重点内容。

远程登录程序中定义了两个类,其一是作为线程模板的StreamConnector类,其二是用于实现网络连接管理的远程登录类。

关于StreamConnector类的工作处理,2个流用以实现二者之间的数据交互。构造器StreamConnector的接受2个流对象,分别高背给自己内部的InputStream类对象src成员和OutputStream类对象成员dist。

实现处理的函数体很简单,将从对象src用read方法读出的数据存储在byte类型的数组buff中,按数据的个数使用dist的write方法将数据全部读入此对象,此过程无限循环。

此外远程登录类中包含4个方法和两个构造器。根据执行命令是否携带端口号而有2种参数处理方法,分别对应着不同的构造函数。如果没有指定端口号,则使用远程登录协议的缺省端口号23与服务器建立连接。4个方法中除了mian方法外,还有用于建立TCP连接并进行处理的方法openConnection,用于生成并启动线程的main_proc方法,和用于协商的negotiation方法。

Main方法负责生成远程登录类的一个对象t,使用t实现后续操作。

Ycdl t =null;

首先由参数个数来判断调用哪个构造器

switch(arg.length){

case 1://只指定服务器地址

t = new Ycdl(arg[0]);

break;

case 2://指定地址和端口

t = new Ycdl(arg[0],Integer.parseInt(arg[1]));

break;

default:// 使用方法不正确时

System.out.println(

"usage: javaYcdl <host name> {<port number>}");

return;}

然后再调用openConnection方法建立连接。OpenConnection方法要求使用被连接的目标计算机名和端口号作为参数。

t.openConnection();

若连接成功,接着将构造线程。为此,须调用main_proc方法。

t.main_proc();

执行完上述代码后,main方法的处理工作也就结束了。程序中的catch以后的剩余部分用于异常处理。

由main第一个调用方法openConnection根据传递来的参数,即地址和端口号构成socket(套接字),从而形成输入输出流。由地址和端口构成socket并藉此进行输入输出的所谓网络存取的结构都与前述的操作方法相同。

Main_proc方法按与前述的含有线程的Threadtest示例程序同样的方法构造线程。生成线程后,利用标准输入输出和用于网络交互的流进行组合,形成清单3.2所示的程序。

下面我来详细介绍一下negotiation方法,它主要负责协商的工作。这里,由于只使用NVT功能进行通信,故须拒绝服务器端的所有有关执行选项的请求。就是说,对于WILL的请求权不用DON’T作为应答。

Negotiation方法的中心内容如图所示3.8所示。协商处理过程进行到从服务器端不再接收到IAC命令时结束。处理开始时,使用mark方法将协商开始时从网路上读取的位置记录下来。协商过程结束时,利用reset方法重新开始自mark的位置起始的服务器-客户端之间的通信。

图3.8中的else if部分是返回给服务器的应答。尽管远程登录协议中使用有3个码组合成的命令进行协商处理,但此处的第二个码是DO时,都将其换成WONT作为应答返回给服务器。

while(true) {

in.mark(buff.length);

if (in.available() >= buff.length){

in.read(buff);

if (buff[0] != IAC){// 协商结束 in.reset(); return;

协商处

理的往 受理要求

返 和处理

else if (buff[1] == DO) {//对于DO命令… buff[1] = WONT;//用 WON'T作为应答 out.write(buff);

}

图3.8 协商处理的中心内容

这样一来,无论服务器端提出什么样的请求,都将被拒绝。由此可见,远程登录程序可以利用最低限度的功能实现与服务器连接。远程登录程序的流程图由图3.9给出。

Main方法

由参数个数决定调

用哪个构造器 生成Ycdl类的对象t


openconnection方法

if(端口号=23)

no yes


进行协商


main_proc方法

生成线程,启动线程,出错处理,

调用StreamConnector构造器


run方法

读出输入流数据存入buff中,

再发送到服务器

图3.9 远程登录流程图

第4章文件传输的执行行为和功能

4.1传输文件

文件传输是与远程登录一样在因特网上广为应用的协议之一。文件传输是Internet上最早出现的服务功能之一,但是到目前为止,它仍然是Internet上最常用也是最重要的服务之一。文件传输的主要作用,就是让用户连接上一个远程计算机(这些计算机上运行着文件传输服务器程序,并且储存有成千上万个非常有用的文件,包括计算机软件、声音文件、图像文件、重要资料、电影……),查看远程计算机有哪些文件,然后把这些文件从远程计算机上复制到本地计算机,或把本地计算机的文件送到远程计算机上。

在2台装有相同操作系统的计算机之间,可以很容易地实现文件的共享。但是,不同的系统如UNIX和Windows之间,要实现文件的共享就要费一番周折。此时,或者在2台计算机之间增设文件传输结构,或者利用网络媒介作为交换文件的手段,总之,都需要使用文件传输协议。

应该说,文件传输解决了在不同计算机上工作的操作系统之间的差异问题,可以实现通用的文件传输。

Windows中附带的文件传输客户端程序中所支持的命令示于表4.1中。

表4.1 Windows附带的文件传输客户端程序中支持的命令示例

命令名

说明

临时转换外壳

显示帮助

ascii

选择文本传输模式

binary

选择二进制传输模式

Bye

关闭连接并结束文件传输命令

Cd

目录切换

close

关闭连接

Dir

显示服务器端的目录清单

Get

读取服务器端的文件

Lcd

客户端目录切换

Ls

显示服务器端文件清单(简单形式)

Mget

读取服务器的多个文件

Mput

从客户端向文件服务器发送多个文件

Open

服务器连接设置

Put

从客户端向文件服务器发送文件

Quit

关闭连接并结束文件传输命令(与bye相同)

User

服务器端的用户设置

4.2 文件传输的传输模式

利用文件传输进行文件传输时,客户端和服务器端与远程登录的结构非常相似,但实际上,文件传输的传输模式与远程登录还是有一些差别的.

应该说,使用远程登录也可以连接到服务器的文件传输端口,但不能正确地实现文件传输,原因是文件传输与远程登录及HTTP等协议不同,采用2个独立的通道完成会话。

图4.1所示为文件传输中数据传输模式示意图。正如图中所示的那样,文件传输协议的一个会话共有2个单独的会话组成,其一是用于控制的TCP连接,其二则是传输文件等数据的TCP连接。

控制用 端口号

连接 21

传输数据连接 20

文件传输客户端 文件传输服务器

图4.1 文件传输的传输模式

要进行文件传输会话,首先要使用负责传输控制的TCP连接客户端和服务器之间的会话。此时,文件传输服务器端的端口号是21(参见图4.1)。此后,当有文件传输等请求时,就会在客户端准备好用于建立从服务器到此客户端的数据传输连接用的socket。

可见,这种情况下,服务器socket是由文件传输客户端来构造的。原因是此时服务器端所准备的端口号是任意的,如果不通知服务器端就不能实现从服务器到客户端的连接。

这里,客户端准备好端口号,然后再利用控制会话将此端口号通知给服务器。

在文件传输会话过程中,利用控制会话将客户端的命令传送给服务器,再将服务器的处理结果返回给客户端,这种循环称为cycle。从客户端发出的命令于表4.1所示的特定的客户的命令不同,是由文件传输协议确定的命令。

对于表4.1中所示的命令,服务器端要先对命令进行解释,然后再进行适当的处理。此时,服务器端要利用控制会话对客户端进行应答,应答由3位代码后接信息组成,表4.3给出了应答代码的示例。

表4.3 文件传输的应答代码示例

第一位的值

代码

含义

1

(准备阶段的肯定应答)

125

传输数据连接已经就绪

150

文件状态正常

2

(完成时的肯定应答)

200

命令正确

202

命令没有实际执行

220

端口用尽,不能连接新用户

226

传输数据连接结束

230

用户正在登录

250

请求的文件处理结束

3(中间的肯定应答)

331

用户名正确,需要口令

4

(暂时的否定应答)

421

控制连接关闭

425

传输数据连接未建立

426

连接关闭,传输过程被中断

450

没执行请求的文件处理

5

(永久的否定应答)

500

语法错误

501

参数的语法错误

502

命令未执行

530

未登录

在图4.2中,首先看到是向文件传输服务器登陆的过程。对此,文件传输服务器利用以应答码230开始的信息报告登录成功。随之,文件传输客户端软件使用ls命令显示目录清单。

图4.2 文件传输会话开始及使用LIST命令取得目录信息

文件传输有称为A和I的2种模式.处理文本数据时应使用A(ascii)模式,而处理二进制数据时应使用binary命令切换到I(image)模式。图4.3指定选用的模式为A传输文件。

4.3图 传输示例

4.3 构造控制连接和数据连接

要实现最简单的文件传输也就是要在客户端计算机上准备好控制连接和传输数据连接即可。

选择序号1的作用是如图4.4所示的向服务器注册。再次运行示例中,来自服务器的信息可能因连接方服务器的种类不同而有差异。

Wjcs2.java的源代码由清单4.1给出。

清单 4.1 Wjcs2程序

程序名

Wjcs2.java

处理内容概要

与文件传输服务器建立控制连接,在建立数据连接并执行LIST命令

结束条件

输入quit命令结束

输入设备

键盘,网络

输出设备

显示器,网络

// 文件传输程序Wjcs2.java

// 此程序的功能是建立文件传输服务器的连接

// 准备数据连接,执行LIST命令

// 没有文件传输功能

// 使用方法:java Wjcs2 服务器地址

// 启动示例java Wjcs2 202.102.2.218


// 导入库

importjava.net.*;

importjava.io.*;

// Wjcs2类

public classWjcs2 {

// socket准备

Socket ctrlSocket;//控制用socket

public PrintWriter ctrlOutput;//控制输出用的流

public BufferedReader ctrlInput;// 控制输入用的流

final int CTRLPORT = 21 ;// 文件传输控制用的端口

// openConnection方法

//由地址和端口号构成socket形成控制用的流

public void openConnection(String host)

throws IOException,UnknownHostException

{ ctrlSocket= new Socket(host, CTRLPORT);

ctrlOutput = newPrintWriter(ctrlSocket.getOutputStream());

ctrlInput

= new BufferedReader(newInputStreamReader

(ctrlSocket.getInputStream()));}

// closeConnection方法

//关闭控制用的socket

public void closeConnection()

throws IOException

{ ctrlSocket.close();}

// showMenu方法

// 输出文件传输的命令菜单

public void showMenu()

{ System.out.println(">Command?") ;

System.out.print("1 login") ;

System.out.print(" 2 ls") ;

System.out.print(" 3 cd") ;

System.out.println(" 9 quit");}

// getCommand方法

// 读取用户指定的命令序号

public String getCommand()

{ Stringbuf = "" ;

BufferedReader lineread

= new BufferedReader(newInputStreamReader(System.in)) ;

while(buf.length() != 1){// 循环接收一个字符的输入

try{ buf = lineread.readLine() ;}

catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

return (buf) ;}

// doLogin方法

// 登录到文件传输服务器

public void doLogin()

{

String loginName = "" ;

String password = "" ;

BufferedReader lineread

= new BufferedReader(new InputStreamReader(System.in));

try{

System.out.println("请输入用户名") ;

loginName = lineread.readLine() ;

// 利用USER命令登录

ctrlOutput.println("USER" + loginName) ;

ctrlOutput.flush() ;

// 利用PASS命令输入口令

System.out.println("请输入口令") ;

password = lineread.readLine() ;

ctrlOutput.println("PASS" + password) ;

ctrlOutput.flush() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// doQuit方法

// 从文件传输服务器注销

public void doQuit()

{

try{

ctrlOutput.println("QUIT ");// 发送QUIT命令

ctrlOutput.flush() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// doCd方法

// 切换目录

public void doCd()

{

String dirName = "" ;

BufferedReader lineread

= new BufferedReader(newInputStreamReader(System.in)) ;

try{

System.out.println("请输入目录名") ;

dirName = lineread.readLine() ;

ctrlOutput.println("CWD" + dirName) ;// CWD命令

ctrlOutput.flush() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// doLs方法

// 取得目录信息

public void doLs()

{

try{

int n ;

byte[] buff = new byte[1024] ;

// 建立数据连接

Socket dataSocket =dataConnection("LIST") ;

// 准备读取数据用的流

BufferedInputStream dataInput

= newBufferedInputStream(dataSocket.getInputStream()) ;

// 读取目录信息

while((n= dataInput.read(buff)) > 0){

System.out.write(buff,0,n);}

dataSocket.close() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// dataConnection方法

// 构造与服务器交换数据用的socket

// 再用port命令将端口通知服务器

public Socket dataConnection(String ctrlcmd)

{

String cmd = "PORT " ; //PORT存放用PORT命令传递数据的变量

int i ;

Socket dataSocket = null ;// 传送数据用socket

try{

// 得到自己的地址

byte[] address =InetAddress.getLocalHost().getAddress() ;

// 用适当的端口号构造服务器

ServerSocket serverDataSocket =new ServerSocket(0,1) ;

// 准备传送PORT命令用的数据

for(i = 0; i < 4; ++i)

cmd = cmd + (address[i]& 0xff) + "," ;

cmd = cmd +(((serverDataSocket.getLocalPort()) / 256) & 0xff)

+ ","

+ (serverDataSocket.getLocalPort() &0xff) ;

//利用控制用的流传送PORT命令

ctrlOutput.println(cmd) ;

ctrlOutput.flush() ;

// 向服务器发送处理对象命令(LIST,RETR,及STOR)

ctrlOutput.println(ctrlcmd) ;

ctrlOutput.flush() ;

// 接受与服务器的连接

dataSocket =serverDataSocket.accept() ;

serverDataSocket.close() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}

return dataSocket ;}

// execCommand方法

// 对应各种命令分别调用其处理方法

public boolean execCommand(String command)

{

boolean cont = true ;

switch(Integer.parseInt(command)){

case 1 : // login 处理

doLogin() ;

break ;

case 2 : // 显示服务器的目录信息

doLs() ;

break ;

case 3 : // 切换服务器的工作目录

doCd() ;

break ;

case 9 : // 处理结束

doQuit() ;

cont = false ;

break ;

default : //其他输入

System.out.println("请输入一个序号") ;}

return(cont) ;}

// main_proc方法

// 输出文件传输命令菜单,调用各种处理方法

public void main_proc()

throws IOException

{

boolean cont = true ;

try {

while(cont){

// 输出菜单

showMenu() ;

// 接受命令并执行

cont =execCommand(getCommand()) ;}}

catch(Exception e){

System.err.print(e);

System.exit(1);}}

// getMsgs方法

// 启动从控制流收信的线程

public void getMsgs(){

try {

CtrlListen listener = newCtrlListen(ctrlInput) ;

Thread listenerthread = newThread(listener) ;

listenerthread.start() ;

}catch(Exception e){

e.printStackTrace() ;

System.exit(1) ;}}

// main方法

// 建立TCP连接,开始处理

public static void main(String[] arg){

try {

Wjcs2 f = null;

f = new Wjcs2();

f.openConnection(arg[0]); // 建立控制连接

f.getMsgs() ; // 启动收信线程

f.main_proc(); // 文件传输处理

f.closeConnection() ; // 关闭连接

System.exit(0) ; // 程序结束

}catch(Exception e){

e.printStackTrace();

System.exit(1);}}}

// CtrlListen 类

classCtrlListen implements Runnable{

BufferedReader ctrlInput = null ;

// 构造器指定读取地址

public CtrlListen(BufferedReader in){

ctrlInput = in ;}

public void run(){

while(true){

try{ // 按行读入并输出到标准输出上

System.out.println(ctrlInput.readLine());

} catch (Exception e){

System.exit(1) ;}}}}

Wjcs2程序中利用Wjcs2类的main方法执行如下的处理:

f.openConnection(arg[0]); // 控制连接的设置

f.getMsgs() ; // 启动接收线程

f.main_proc(); // 文件传输 处理

f.closeConnection() ; // 关闭连接

openConnection方法负责建立与文件传输服务器的控制连接,服务器的地址由参数指定。

getMsgs()方法利用CtrlListen类接受来自控制连接的信号,创建收信线程。

Main_proc方法负责从控制连接收信以外的事情,即负责显示客户端的命令菜单和接收用户输入的命令,进而调用执行与命令相应的处理方法,继续执行文件传输处理。Main方法中最后调用的closeconnection方法用于关闭控制连接。

作为处理中心的main_proc方法中的主要代码如下:

while(cont){

//输出菜单

showMenu();

//接受命令并执行

cont=execCommand(getConmmand());}

showMenu方法的功能是将菜单在标准输出设备上显示出来。GetConmmand方法接收用户输入的一个字符,并以此作为返回值并指向对应的处理功能。各种情况的处理有下述switch语句实现:

switch(Integer.parseInt(command)){

case 1 : // login 处理

doLogin() ;

break ;

case 9 : // 处理结束

doQuit() ;

cont = false ;

break ;

default : //除此以外的输入

System.out.println("请选择一个序号") ;}

DoLogin方法执行向文件传输服务器的登录。具体的说,就是要求用户输入用户名和口令,并使用控制连接发送给文件传输服务器。

DoQuit方法极为简单,只负责向文件传输服务器发送QUIT命令,这里,再发送完命令之后仍要调用flush方法清空缓冲区。

ctrlOutput.println("QUIT") ;// 发送QUIT命令

ctrlOutput.flush();

图4.4为能够取得目录信息之类的目的Wjcs2程序的运行示例。由图可见,Wjcs2程序可以执行数据连接所需的LIST命令。

图4.4 Wjcs2程序的运行示例

其中,在doLs方法中要使用dataConnection方法建立与文件传输服务器之间的数据连接。初始时,要构造建立数据连接用的socket,由此构成对应的流:

// 建立数据连接

Socket dataSocket =dataConnection("LIST") ;

// 准备读取数据用的流

BufferedInputStreamdataInput

= newBufferedInputStream(dataSocket.getInputStream()) ;

随后,利用已构造好的流从数据连接读取数据,再写到标准输出上。当从连接上读取数据完毕后,就将连接关闭。

// 读取目录信息

while((n =dataInput.read(buff)) > 0){

System.out.write(buff,0,n) ;}

dataSocket.close();

4.4 文件传输程序的完成

应该说,Wjcs2程序已经可以实现文件传输客户端的最基本功能了,此处在给出经过最后扩充的文件传输完成程序。

对于文件传输程序,在文件传输对话开始时必须要进行注册,并成为能够指定传输模式那样。

图4.2是文件传输程序运行示例,其程序源代码由清单4.3给出。

清单4.3 Wjcs程序

程序名

Wjcs.java

处理内容概要

与文件传输服务器建立控制连接,在执行文件传输

结束条件

输入quit命令结束

输入设备

键盘,网络以及文件

输出设备

显示器,网络以及文件

// 文件传输程序Wjcs.java

// 此程序的功能是建立与文件传输服务器的连接并实现文件传输

// 使用方法:java Wjcs 服务器地址

// 运行示例:java Wjcs 202.102.2.218

// 导入库

importjava.net.*;

importjava.io.*;

// Wjcs类

public classWjcs {

// 准备socket

Socket ctrlSocket;//控制用socket

public PrintWriter ctrlOutput;//控制输出用的流

public BufferedReader ctrlInput;// 控制输入用的流

final int CTRLPORT = 21 ;// 文件传输的控制用端口

// openConnection方法

//由地址和端口,构造socket形成控制用的流

public void openConnection(String host)

throws IOException,UnknownHostException

{ ctrlSocket= new Socket(host, CTRLPORT);

ctrlOutput = new PrintWriter(ctrlSocket.getOutputStream());

ctrlInput

= new BufferedReader(newInputStreamReader

(ctrlSocket.getInputStream()));}

// closeConnection方法

//关闭控制用的socket

public void closeConnection()

throws IOException

{ ctrlSocket.close();}

// showMenu方法

// 输出文件传输的命令菜单

public void showMenu()

{ System.out.println(">Command?");

System.out.print("2 ls") ;

System.out.print(" 3 cd") ;

System.out.print(" 4 get") ;

System.out.print(" 5 put") ;

System.out.print(" 6 ascii") ;

System.out.print(" 7 binary") ;

System.out.println(" 9 quit");}

// getCommand方法

// 读取用户指定的命令符号

public String getCommand()

{

String buf = "" ;

BufferedReader lineread

= new BufferedReader(newInputStreamReader(System.in)) ;

while(buf.length() != 1){// 循环接收一个字符的输入

try{

buf = lineread.readLine();

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

return (buf) ;}

// doLogin方法

// 登录文件传输服务器

public void doLogin()

{

String loginName = "" ;

String password = "" ;

BufferedReader lineread

= new BufferedReader(newInputStreamReader(System.in)) ;

try{

System.out.println("请输入用户名") ;

loginName = lineread.readLine() ;

// 用USER命令登录

ctrlOutput.println("USER" + loginName) ;

ctrlOutput.flush() ;

// 用PASS命令输入口令

System.out.println("请输入口令") ;

password = lineread.readLine() ;

ctrlOutput.println("PASS" + password) ;

ctrlOutput.flush() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// doQuit方法

// 向文件传输服务器注销

public void doQuit()

{

try{

ctrlOutput.println("QUIT") ;// QUIT发送QUIT命令

ctrlOutput.flush() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// doCd方法

// 切换目录

public void doCd()

{

String dirName = "" ;

BufferedReader lineread

= new BufferedReader(newInputStreamReader(System.in)) ;

try{

System.out.println("请输入目录名") ;

dirName = lineread.readLine() ;

ctrlOutput.println("CWD" + dirName) ;// CWD命令

ctrlOutput.flush() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// doLs方法

// 取得目录信息

public void doLs()

{

try{

int n ;

byte[] buff = new byte[1024] ;

// 建立数据连接

Socket dataSocket =dataConnection("LIST") ;

// 准备读取数据用的流

BufferedInputStream dataInput

= newBufferedInputStream(dataSocket.getInputStream()) ;

// 读取目录信息

while((n = dataInput.read(buff))> 0){

System.out.write(buff,0,n);}

dataSocket.close() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// dataConnection方法

// 构造与服务器交换数据用的socket

// 再用PORT命令将端口号通知给服务器

public Socket dataConnection(String ctrlcmd)

{

String cmd = "PORT " ; //PORT存储用PORT命令传输的数据用的变量

int i ;

Socket dataSocket = null ;// 传送数据用socket

try{

// 取得自己的地址

byte[] address =InetAddress.getLocalHost().getAddress() ;

// 用适当的端口号构造服务器socket

ServerSocket serverDataSocket =new ServerSocket(0,1) ;

// 准备传送PORT命令用的数据

for(i = 0; i < 4; ++i)

cmd = cmd + (address[i]& 0xff) + "," ;

cmd = cmd +(((serverDataSocket.getLocalPort()) / 256) & 0xff)

+ ","

+ (serverDataSocket.getLocalPort() &0xff) ;

// 利用控制用的流发送PORT命令

ctrlOutput.println(cmd) ;

ctrlOutput.flush() ;

// 向服务器发送处理对象命令(LIST,RETR及STOR)

ctrlOutput.println(ctrlcmd) ;

ctrlOutput.flush() ;

// 受理服务器的连接

dataSocket = serverDataSocket.accept();

serverDataSocket.close() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}

return dataSocket ;}

// doAscii方法

// 设置文本传输模式

public void doAscii()

{

try{

ctrlOutput.println("TYPEA") ;// A模式

ctrlOutput.flush() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// doBinary方法

// 设置二进制传输模式

public void doBinary()

{

try{

ctrlOutput.println("TYPEI") ;// I模式

ctrlOutput.flush() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// doGet方法

// 取得服务器上的文件

public void doGet()

{

String fileName = "" ;

BufferedReader lineread

= new BufferedReader(newInputStreamReader(System.in)) ;

try{

int n ;

byte[] buff = new byte[1024] ;

// 指定服务器上的文件的名称

System.out.println("请输入文件名") ;

fileName = lineread.readLine() ;

// 在客户端上准备存储器内容的文件

FileOutputStream outfile = newFileOutputStream(fileName) ;

// 构造传输文件用的数据流

Socket dataSocket =dataConnection("RETR " + fileName) ;

BufferedInputStream dataInput

= newBufferedInputStream(dataSocket.getInputStream()) ;

// 从服务器上读取数据并存储到文件中

while((n = dataInput.read(buff))> 0){

outfile.write(buff,0,n) ;}

dataSocket.close() ;

outfile.close() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// doPut方法

// 向服务器发送文件

public void doPut()

{

String fileName = "" ;

BufferedReader lineread

= new BufferedReader(newInputStreamReader(System.in)) ;

try{

int n ;

byte[] buff = new byte[1024] ;

FileInputStream sendfile = null ;

// 指定文件名

System.out.println("请输入文件名") ;

fileName = lineread.readLine() ;

// 准备读出客户端的文件

try{

sendfile = newFileInputStream(fileName) ;

}catch(Exception e){

System.out.println("文件不存在") ;

return ;}

// 准备发送数据的流

Socket dataSocket =dataConnection("STOR " + fileName) ;

OutputStream outstr =dataSocket.getOutputStream() ;

// 读出文件,经由网络发送给服务器

while((n = sendfile.read(buff))> 0){

outstr.write(buff,0,n) ;}

dataSocket.close() ;

sendfile.close() ;

}catch(Exception e)

{

e.printStackTrace();

System.exit(1);}}

// execCommand方法

// 调用与命令对应的各种处理方法

public boolean execCommand(String command)

{

boolean cont = true ;

switch(Integer.parseInt(command)){

case 2 : // 显示服务器上的目录信息

doLs() ;

break ;

case 3 : // 切换服务器的工作目录

doCd() ;

break ;

case 4 : // 从服务器取得文件

doGet() ;

break ;

case 5 : // 向服务器发送文件

doPut() ;

break ;

case 6 : // 文件传输模式

doAscii() ;

break ;

case 7 : // 二进制传输模式

doBinary() ;

break ;

case 9 : // 处理结束

doQuit() ;

cont = false ;

break ;

default : //其他的输入

System.out.println("请选择一个序号") ;}

return(cont) ;}

// main_proc方法

// 输出文件传输的命令菜单并进行各种处理

public void main_proc()

throws IOException

{

boolean cont = true ;

try {

// 进行登录

doLogin() ;

while(cont){

// 输出菜单

showMenu() ;

// 接受并执行命令

cont = execCommand(getCommand());}}

catch(Exception e){

System.err.print(e);

System.exit(1);}}

// getMsgs方法

// 启动接收控制流的线程

public void getMsgs(){

try {

CtrlListen listener = newCtrlListen(ctrlInput) ;

Thread listenerthread = newThread(listener) ;

listenerthread.start() ;

}catch(Exception e){

e.printStackTrace() ;

System.exit(1) ;}}

// main方法

// 建立TCP连接,开始处理

public static void main(String[] arg){

try {

Wjcs f = null;

if(arg.length < 1){

System.out.println("usage:java Wjcs <host name>") ;

return ;}

f = new Wjcs();

f.openConnection(arg[0]); // 建立控制连接

f.getMsgs() ; // 启动接收线程

f.main_proc(); // 文件传输处理

f.closeConnection() ; // 关闭连接

System.exit(0) ; // 结束程序

}catch(Exception e){

e.printStackTrace();

System.exit(1);}}}

// CtrlListen 类

classCtrlListen implements Runnable{

BufferedReader ctrlInput = null ;

// 构造器 指定读取地点

public CtrlListen(BufferedReader in){

ctrlInput = in ;}

public void run(){

while(true){

try{ // 按行读入并写到标准输出上

System.out.println(ctrlInput.readLine());

} catch (Exception e){

System.exit(1) ;}}}}

Wjcs程序与Wjcs2程序有如下一些差异:

l 对showMenu菜单有所更改,在其中删除了序号1(login),增加了序号4(get)和5(put)

l 对exeCommand命令有所更改,在swith结构中删除了1(login),增加了对4(get)和5(put)的处理

l 增加了doput方法,并禁止指定不存在的文件

l 增加了doGet方法

l 增加了dataConnection方法

l 增加了doAscii方法

l 增加了doBinary方法

在Wjcs程序中,login不包含在菜单中,使用户在开始会话时必须进行登录。此外,增加了doAscii方法和doBinary方法,使程序可以进行传输模式切换。Wjcs程序流程图4.7给出。

Main方法

进入main主函数,为实现调用

其它函数作准备

openConnection方法

以host为参数构造ctrlOutput

和ctrlIutput流


getmsgs方法

调用ctrllisten类,创建收信线程

run()函数

调用此函数,接受服务器的信息

main_proc方法

先进行登录,再调用showmenu

()方法,输出菜单

showmenu()方法

输出菜单

execcommand方法

Ls Cd Get PutdoAscii doBinary doQuit 其他输入


调用doLs方法

取得目录信息 调用doput方法

向服务器发

送文件 调用dobinary方法

设置二进制

dataconnection方法 传输模式

构造socket,实现 调用doget方法

与服务器交换数据 取得服务器上的

文件 调用doquit方法

向文件传输服务器

调用doCd方法 调用doascii方法 注销

切换目录 设置文本传输模式

调用closeconnection方法

关闭控制用的socket


图4.7 WJCS程序流程图 结束程序

参考文献

[1] 熊桂喜,王小虎. 计算机网络[M]. 清华大学出版社 2002

[2] 宋波,董小梅. java应用设计[M]. 人民邮电出版社 2001

[3] 飞思科技产品研发中心. java TCP/IP.应用开发详解[M]

电子工业出版社2002

[4] 金勇华,曲俊生. java网络高级编程. 人民邮电出版社 2001

[5] 张曜,张青,郭立山. Java程序设计教程. 冶金工业出版社 2002

[6] TimParker Mark Sportack. TCP/IP技术大全. 机械工业出版社 2001

[7] 殷兆麟,付慧生,赵纪平..Java网络编程基础. 清华大学出版社2004


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics