| |||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
---|---|---|---|---|---|---|---|---|---|
29 | 30 | 1 | 2 | 3 | 4 | 5 | |||
6 | 7 | 8 | 9 | 10 | 11 | 12 | |||
13 | 14 | 15 | 16 | 17 | 18 | 19 | |||
20 | 21 | 22 | 23 | 24 | 25 | 26 | |||
27 | 28 | 29 | 30 | 31 | 1 | 2 | |||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
Published April 2013
對(duì)于許多基于客戶端-服務(wù)器程序來說,老的HTTP 請(qǐng)求-響應(yīng)模型已經(jīng)有它的局限性. 信息必須通過多次請(qǐng)求才能將其從服務(wù)端傳送到客戶端.
過去許多的黑客使用某些技術(shù)來繞過這個(gè)問題,例如:長(zhǎng)輪詢(long polling)、基于 HTTP 長(zhǎng)連接的服務(wù)器推技術(shù)(Comet).
在2011年, IETF發(fā)布了標(biāo)準(zhǔn)WebSocket協(xié)議-RFC 6455. 從那時(shí)起,大多數(shù)Web瀏覽器都實(shí)現(xiàn)了支持WebSocket協(xié)議的客戶端APIs.同時(shí),許多Java 包也開始實(shí)現(xiàn)了WebSocket協(xié)議.
WebSocket協(xié)議利用HTTP升級(jí)技術(shù)來將HTTP連接升級(jí)到WebSocket. 一旦升級(jí)后,連接就有了在兩個(gè)方向上相互獨(dú)立(全雙式)發(fā)送消息(數(shù)據(jù)楨)的能力.
不需要headers 或cookies,這大大降低了所需的帶寬. 通常,WebSockets來周期性地發(fā)送小消息 (例如,幾個(gè)字節(jié)).
JSR 356, WebSocket的Java API, 明確規(guī)定了API,當(dāng)Java開發(fā)者需要在應(yīng)用程序中集成WebSocket時(shí),就可以使用此API—服務(wù)端和客戶端均可. 每個(gè)聲明兼容JSR 356的WebSocket協(xié)議,都必須實(shí)現(xiàn)這個(gè)API.
因此,開發(fā)人員可以自己編寫?yīng)毩⒂诘讓覹ebSocket實(shí)現(xiàn)的WebSocket應(yīng)用。這是一個(gè)巨大的好處,因?yàn)樗梢苑乐构?yīng)商鎖定,并允許更多的選擇、自由的庫、應(yīng)用程序服務(wù)器。
JSR 356是即將到來的java EE 7標(biāo)準(zhǔn)的一部分,因此,所有與Java EE 7兼容的應(yīng)用服務(wù)器都有JSR 365標(biāo)準(zhǔn)WebSocket的實(shí)現(xiàn).一旦建立,WebSocket客戶端和服務(wù)器節(jié)點(diǎn)已經(jīng)是對(duì)稱的了。客戶端API與服務(wù)器端API的區(qū)別是很小的,JSR 356定義的Java client API只是Java EE7完整API的子集.
客戶段-服務(wù)器端程序使用WebSockets,通常會(huì)包含一個(gè)服務(wù)器組件和多個(gè)客戶端組件, 如圖1所示:
圖1
在這個(gè)例子中,server application 是通過Java編寫的,WebSocket 協(xié)議細(xì)節(jié)是由包含在Java EE 7容器中JSR 356 實(shí)現(xiàn)來處理的.
JavaFX 客戶端可依賴任何與JSR 356兼容的客戶端實(shí)現(xiàn)來處理WebSocket協(xié)議問題.
其它客戶端(如,iOS 客戶端和HTML5客戶端)可使用其它 (非Java)與RFC6455兼容的實(shí)現(xiàn)來與server application通信.
JSR 356定義的專家小組,希望支持Java EE開發(fā)人員常用的模式和技術(shù)。因此,JSR 356使用了注釋和注入。
一般來說,支持兩種編程模型:
Endpoint接口和與生命周期交互的方法.
典型的WebSocket 交互生命周期如下:
大部分WebSocket生命周期事件都與Java方法對(duì)應(yīng),不管是 annotation-driven 還是interface-driven.
接受WebSocket請(qǐng)求的端點(diǎn)可以是以 @ServerEndpoint
注解的POJO.
此注解告知容器,此類應(yīng)該被認(rèn)為是WebSocket端點(diǎn).
必須的value
元素指定了WebSocket端點(diǎn)的路徑.
考慮下面的代碼片斷:
@ServerEndpoint("/hello") public class MyEndpoint { }
此代碼將會(huì)以相對(duì)路徑hello來發(fā)布一個(gè)端點(diǎn).在后續(xù)方法調(diào)用中,此路徑可攜帶路徑參數(shù),如: /hello/{userid}是一個(gè)有效路徑,在這里
{userid}
的值,可在生命周期方法使用@PathParam
注解獲取.
在GlassFish中,如果你的應(yīng)用程序是用上下文mycontextroot
部署的,且在localhost的8080端口上監(jiān)聽
, WebSocket可通過使用ws://localhost:8080/mycontextroot/hello來訪問
.
初始化WebSocket連接的端點(diǎn)可以是以 @ClientEndpoint
注解的POJO.@ClientEndpoint
和 @ServerEndpoint的主要區(qū)別是
ClientEndpoint
不接受路徑路值元素,因?yàn)樗O(jiān)聽進(jìn)來的請(qǐng)求。
@ClientEndpoint public class MyClientEndpoint {}
Java中使用注解驅(qū)動(dòng)POJO方式來初始化WebSocket連接,可通過如下代碼來完成:
javax.websocket.WebSocketContainer container = javax.websocket.ContainerProvider.getWebSocketContainer(); container.conntectToServer(MyClientEndpoint.class, new URI("ws://localhost:8080/tictactoeserver/endpoint"));
此后,以 @ServerEndpoint
或@ClientEndpoint
注解的類都稱為注解端點(diǎn).
一旦建立了WebSocket連接 ,就會(huì)創(chuàng)建 Session,并且會(huì)調(diào)用注解端點(diǎn)中以
@OnOpen注解的方法.
此方法包含了幾個(gè)參數(shù):
javax.websocket.Session
參數(shù), 代表創(chuàng)建的Session
EndpointConfig
實(shí)例包含了關(guān)于端點(diǎn)配置的信息@PathParam注解的
字符串參數(shù),指的是端點(diǎn)路徑的path參數(shù)下面的方法實(shí)現(xiàn)了當(dāng)打開WebSocket時(shí),將會(huì)打印session的標(biāo)識(shí)符:
@OnOpen public void myOnOpen (Session session) { System.out.println ("WebSocket opened: "+session.getId()); }
Session實(shí)例只要WebSocket未關(guān)閉就會(huì)一直有效
. Session類中包含了許多有意思的方法,以允許開發(fā)者獲取更多關(guān)于的信息
。
同時(shí),Session
也包含了應(yīng)用程序特有的數(shù)據(jù)鉤子,即通過getUserProperties()
方法來返回 Map<String, Object>
.
這允許開發(fā)者可以使用session-和需要在多個(gè)方法調(diào)用間共享的應(yīng)用程序特定信息來填充Session
實(shí)例.
i當(dāng)WebSocket端收到消息時(shí),將會(huì)調(diào)用以@OnMessage
注解的方法.以@OnMessage
注解的方法可包含下面的參數(shù):
javax.websocket.Session
參數(shù).@PathParam注解的
字符串參數(shù),指的是端點(diǎn)路徑的path參數(shù)當(dāng)其它端發(fā)送了文本消息時(shí),下面的代碼片斷會(huì)打印消息內(nèi)容:
@OnMessage public void myOnMessage (String txt) { System.out.println ("WebSocket received message: "+txt); }
如果以@OnMessage
i注解的方法返回值不是void
, WebSocket實(shí)現(xiàn)會(huì)將返回值發(fā)送給其它端點(diǎn).下面的代碼片斷會(huì)將收到的文本消息以首字母大寫的形式發(fā)回給發(fā)送者:
@OnMessage public String myOnMessage (String txt) { return txt.toUpperCase(); }
另一種通過WebSocket連接來發(fā)送消息的代碼如下:
RemoteEndpoint.Basic other = session.getBasicRemote(); other.sendText ("Hello, world");
在這種方式中,我們從Session
對(duì)象開始,它可以從生命周期回調(diào)方法中獲取(例如,以 @OnOpen注解的方法
).session實(shí)例上getBasicRemote()
方法返回的是WebSocket其它部分的代表RemoteEndpoint
. RemoteEndpoint
實(shí)例可用于發(fā)送文本或其它類型的消息,后面有描述.
當(dāng)關(guān)閉WebSocket連接時(shí),將會(huì)調(diào)用@OnClose
注解的方法。此方法接受下面的參數(shù):
javax.websocket.Session
參數(shù). 注意,一旦WebSocket真正關(guān)閉了,此參數(shù)就不能被使用了,這通常發(fā)生在@OnClose
注解方法返回之后.javax.websocket.CloseReason
參數(shù),用于描述關(guān)閉WebSocket的原因,如:正常關(guān)閉,協(xié)議錯(cuò)誤,服務(wù)過載等等.@PathParam注解的
字符串參數(shù),指的是端點(diǎn)路徑的path參數(shù)下面的代碼片段打印了WebSocket關(guān)閉的原因:
@OnClose public void myOnClose (CloseReason reason) { System.out.prinlnt ("Closing a WebSocket due to "+reason.getReasonPhrase()); }
完整情況下,這里還有一個(gè)生命周期注解:如果收到了錯(cuò)誤,將會(huì)調(diào)用 @OnError
注解的方法。
annotation-driven 方式允許我們注解一個(gè)Java類,以及使用生命周期注解來注解方法.
使用interface-driven方式,開發(fā)者可繼承javax.websocket.Endpoint
并覆蓋其中的onOpen
, onClose
, 以及onError
方法:
public class myOwnEndpoint extends javax.websocket.Endpoint { public void onOpen(Session session, EndpointConfig config) {...} public void onClose(Session session, CloseReason closeReason) {...} public void onError (Session session, Throwable throwable) {...} }
為了攔截消息,需要在onOpen實(shí)現(xiàn)中注冊(cè)一個(gè)javax.websocket.MessageHandler
:
public void onOpen (Session session, EndpointConfig config) { session.addMessageHandler (new MessageHandler() {...}); }
MessageHandler
接口有兩個(gè)子接口: MessageHandler.Partial和
MessageHandler.Whole
.
MessageHandler.Partial
接口應(yīng)該用于當(dāng)開發(fā)者想要收到部分消息通知的時(shí)候,MessageHandler.Whole的實(shí)現(xiàn)應(yīng)該用于整個(gè)消息到達(dá)通知
。
下面的代碼片斷會(huì)監(jiān)聽進(jìn)來的文件消息,并將文本信息轉(zhuǎn)換為大小版本后發(fā)回給其它端點(diǎn):
public void onOpen (Session session, EndpointConfig config) { final RemoteEndpoint.Basic remote = session.getBasicRemote(); session.addMessageHandler (new MessageHandler.Whole<String>() { public void onMessage(String text) { try { remote.sendString(text.toUpperCase()); } catch (IOException ioe) { // handle send failure here } } }); }
WebSocket的JavaAPI非常強(qiáng)大,因?yàn)樗试S發(fā)送任或接收任何對(duì)象作為WebSocket消息.
基本上,有三種不同類型的消息:
當(dāng)使用interface-driven模式,每個(gè)session最多只能為這三個(gè)不同類型的消息注冊(cè)一個(gè)MessageHandler
.
當(dāng)使用annotation-driven模式,針對(duì)不同類型的消息,只允許出現(xiàn)一個(gè)@onMessage
注解方法. 在注解方法中,消息內(nèi)容中允許的參數(shù)依賴于消息類型。
Javadoc for the @OnMessage
annotation 明確指定了消息類型上允許出現(xiàn)的消息參數(shù):
String
用于接收整個(gè)消息String
和 boolean 對(duì)用于部分接收消息Reader
用于以阻塞流的方式接收整個(gè)消息Decoder.Text
或 Decoder.TextStream
).byte[]
或 ByteBuffer
用于接收整個(gè)消息byte[]
和 boolean 對(duì), 或者 ByteBuffer
和boolean對(duì)用于部分接收消息InputStream
用于按阻塞流的方式接收整個(gè)消息Decoder.Binary
or Decoder.BinaryStream
).PongMessage
用于處理pong消息"任何Java對(duì)象使用編碼器都可以編碼為基于文本或二進(jìn)制的消息.這種基于文本或二進(jìn)制的消息將轉(zhuǎn)輸?shù)狡渌它c(diǎn),在其它端點(diǎn),它可以解碼成Java對(duì)象-或者被另外的WebSocket 包解釋.
通常情況下,XML或JSON用于來傳送WebSocket消息, 編碼/解碼然后會(huì)將Java對(duì)象編組成XML或JSON并在另一端解碼為Java對(duì)象.
encoder是以javax.websocket.Encoder
接口的實(shí)現(xiàn)來定義,decoder是以javax.websocket.Decoder
接口的實(shí)現(xiàn)來定義的.
有時(shí),端點(diǎn)實(shí)例必須知道encoders和decoders是什么.使用annotation-driven方式, 可向@ClientEndpoint
和 @ServerEndpoint
l注解中的encode和decoder元素傳遞 encoders和decoders的列表。
Listing 1 中的代碼展示了如何注冊(cè)一個(gè) MessageEncoder
類(它定義了MyJavaObject實(shí)例到文本消息的轉(zhuǎn)換). MessageDecoder
是以相反的轉(zhuǎn)換來注冊(cè)的.
@ServerEndpoint(value="/endpoint", encoders = MessageEncoder.class, decoders= MessageDecoder.class) public class MyEndpoint { ... } class MessageEncoder implements Encoder.Text<MyJavaObject> { @override public String encode(MyJavaObject obj) throws EncodingException { ... } } class MessageDecoder implements Decoder.Text<MyJavaObject> { @override public MyJavaObject decode (String src) throws DecodeException { ... } @override public boolean willDecode (String src) { // return true if we want to decode this String into a MyJavaObject instance } }
Listing 1
Encoder
接口有多個(gè)子接口:
Encoder.Text
用于將Java對(duì)象轉(zhuǎn)成文本消息Encoder.TextStream
用于將Java對(duì)象添加到字符流中Encoder.Binary
用于將Java對(duì)象轉(zhuǎn)換成二進(jìn)制消息Encoder.BinaryStream
用于將Java對(duì)象添加到二進(jìn)制流中類似地,Decoder
接口有四個(gè)子接口:
Decoder.Text
用于將文本消息轉(zhuǎn)換成Java對(duì)象Decoder.TextStream
用于從字符流中讀取Java對(duì)象Decoder.Binary
用于將二進(jìn)制消息轉(zhuǎn)換成Java對(duì)象Decoder.BinaryStream
用于從二進(jìn)制流中讀取Java對(duì)象WebSocket Java API為Java開發(fā)者提供了標(biāo)準(zhǔn)API來集成IETF WebSocket標(biāo)準(zhǔn).通過這樣做,Web 客戶端或本地客戶端可使用任何WebSocket實(shí)現(xiàn)來輕易地與Java后端通信。
rabbitmq-service.bat — 管理RabbitMQ AMQP service
rabbitmq-service.bat [command]
RabbitMQ是AMQP的實(shí)現(xiàn), 后者是高性能企業(yè)消息通信的新興標(biāo)準(zhǔn). RabbitMQ server是AMQP 中間件的健壯,可擴(kuò)展實(shí)現(xiàn).
運(yùn)行rabbitmq-service,可允許RabbitMQ broker在NT/2000/2003/XP/Vista®環(huán)境上以服務(wù)來運(yùn)行,這樣就可以通過Windows® services applet來啟動(dòng)和停止服務(wù).
默認(rèn)情況下,服務(wù)會(huì)以本地系統(tǒng)帳戶中認(rèn)證上下文來運(yùn)行。因此,有必要將Erlang cookies 和本地系統(tǒng)帳戶進(jìn)行同步(典型地,C:\WINDOWS\.erlang.cookie和帳戶將用來運(yùn)行 rabbitmqctl).
顯示使用信息.
安裝service,安裝后,它不會(huì)啟動(dòng)。如果環(huán)境變量修改了的話,隨后的調(diào)用將更新服務(wù)參數(shù).
刪除service.如果刪除時(shí),service正在運(yùn)行,則將會(huì)自動(dòng)停止。 它不會(huì)刪除任何文件,后續(xù)可通過rabbitmq-server 繼續(xù)操作。
啟動(dòng)service. 在此之前,service必須被正確安裝
停止service.
禁用service. 這等價(jià)于在服務(wù)控制面板中,將啟動(dòng)類型設(shè)置為禁用.
啟用service. 這等價(jià)于在服務(wù)控制面板中,將啟動(dòng)類型設(shè)置為自動(dòng).
默認(rèn)為RabbitMQ.
默認(rèn)是當(dāng)前用戶的應(yīng)用程序數(shù)據(jù)目錄. 這是日志和數(shù)據(jù)目錄的位置(C:\Users\Administrator\AppData\Roaming\RabbitMQ).
默認(rèn)是rabbit. 當(dāng)你想在一臺(tái)機(jī)器上運(yùn)行多個(gè)節(jié)點(diǎn)時(shí),此配置是相當(dāng)有用的, RABBITMQ_NODENAME在每個(gè)erlang-node和機(jī)器的組合中應(yīng)該唯一。
參考clustering on a single machine guide 來更多細(xì)節(jié).
默認(rèn)情況下,RabbitMQ會(huì)綁定到所有網(wǎng)絡(luò)接口上,如果只想綁定某個(gè)網(wǎng)絡(luò)接口,可修改此設(shè)置。
默認(rèn)為5672.
默認(rèn)為C:\Program Files\erl5.5.5\erts-5.5.5\bin (或64位環(huán)境 中為C:\Program Files (x86)\erl5.5.5\erts-5.5.5\bin). 這是Erlang service manager的安裝位置.
將此變量設(shè)置為new或reuse,以將服務(wù)器控制臺(tái)的輸出重定向到名為SERVICENAME.debug文件中(位于安裝服務(wù)的用戶應(yīng)用程序數(shù)據(jù)目錄).在Vista下,其位置在C:\Users\AppData\username\SERVICENAME. 在Windows的前期版本中,位置在C:\Documents and Settings\username\Application Data\SERVICENAME.
如果RABBITMQ_CONSOLE_LOG設(shè)置為new,那么每次服務(wù)啟動(dòng)時(shí)都會(huì)創(chuàng)建一個(gè)新文件。
如果RABBITMQ_CONSOLE_LOG設(shè)置為reuse,那么每次服務(wù)啟動(dòng)時(shí),文件都會(huì)被覆蓋.
當(dāng)RABBITMQ_CONSOLE_LOG 沒有設(shè)置或設(shè)置的值不是new或reuse時(shí),默認(rèn)的行為是丟棄服務(wù)器輸出。
要運(yùn)行ftp4j library,你需要Java 運(yùn)行時(shí)環(huán)境v. 1.4+.
將ftp4j JAR文件添加到你應(yīng)用程序的classpath中, 然后你就可以自動(dòng)啟用ftp4j類的使用了.
可參考ftp4j javadocs.
包中的主類是FTPClient (it.sauronsoftware.ftp4j.FTPClient).
創(chuàng)建一個(gè)FTPClient 實(shí)例:
FTPClient client = new FTPClient();
連接遠(yuǎn)程FTP服務(wù):
client.connect("ftp.host.com");
如果服務(wù)端口不是標(biāo)準(zhǔn)的21端口 (或 FTPS的990端口),需要使用port參數(shù)進(jìn)行指定:
client.connect("ftp.host.com", port);
如:
client.connect("ftp.host.com", 8021);
然后進(jìn)行登錄流程:
client.login("carlo", "mypassword");
如果沒有拋出任何異常的話,那么你就通過遠(yuǎn)程服務(wù)器的認(rèn)證了.否則,如果驗(yàn)證失敗,你將會(huì)收到it.sauronsoftware.ftp4j.FTPException異常.
匿名認(rèn)證,如果被連接服務(wù)認(rèn)可的話, 可通過發(fā)送用戶名"anonymous" 和任意密碼來完成(注意,有些服務(wù)器需要e-mail地址來代替密碼):
client.login("anonymous", "ftp4j");
使用遠(yuǎn)程FTP服務(wù)來做任何事情,然后再斷開連接:
client.disconnect(true);
這會(huì)向遠(yuǎn)程器發(fā)送FTP QUIT命令, 以進(jìn)行一個(gè)合法斷開流程.如果你只是想中斷連接而不想向服務(wù)器發(fā)送任何通知,那么可以使用:
client.disconnect(false);
客戶端通過連接器(一個(gè)繼承自it.sauronsoftware.ftp4j.FTPConnector的對(duì)象)來連接服務(wù)器, 它將返回一個(gè)已經(jīng)打開的連接(一個(gè)實(shí)現(xiàn)了it.sauronsoftware.ftp4j.FTPConnection 接口的對(duì)象).這也是為什么ftp4j 可以支持大量代理的原因.
在連接遠(yuǎn)程服務(wù)器前,客戶端實(shí)例可以使用setConnector() 方法來設(shè)置連接器:
client.setConnector(anyConnectorYouWant);
如果沒有設(shè)置連接器的話,會(huì)使用默認(rèn)的連接器DirectConnector (it.sauronsoftware.ftp4j.connectors.DirectConnector), 它實(shí)現(xiàn)了對(duì)遠(yuǎn)程服務(wù)器的直接連接,且不會(huì)使用代理。
如果你只能通過代理來連接遠(yuǎn)程服務(wù)器, ftp4j包可以讓你在下面的連接器中進(jìn)行選擇:
因?yàn)閒tp4j的連接器架構(gòu)設(shè)計(jì)為可插拔的,因此你可以繼承FTPConnector 抽象類來構(gòu)建自己的連接器.
ftp4j包支持FTPS (隱式基于 TLS/SSL的FTP) 和FTPES (顯示基于TLS/SSL的FTP).
setSecurity() 方法可用來打開這種特性:
client.setSecurity(FTPClient.SECURITY_FTPS); // 啟用 FTPS
client.setSecurity(FTPClient.SECURITY_FTPES); // 啟用 FTPES
兩個(gè)方法都需要在連接遠(yuǎn)程服務(wù)器前調(diào)用.
如果安全協(xié)議設(shè)置成了SECURITY_FTPS, 則connect() 方法默認(rèn)使用的端口為990.
默認(rèn)情況下,客戶端對(duì)象商討SSL連接會(huì)使用javax.net.ssl.SSLSocketFactory.getDefault()作為其套接字工廠.可通過調(diào)用client.setSSLSocketFactory()方法來改變默認(rèn)套接字工廠. 另外一種SSLSocketFactory, 可用來信任遠(yuǎn)程服務(wù)器頒發(fā)的證書(謹(jǐn)慎使用):
import it.sauronsoftware.ftp4j.FTPClient;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
// ... TrustManager[] trustManager = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) { }
public void checkServerTrusted(X509Certificate[] certs, String authType) { } } };
SSLContext sslContext = null;
try { sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustManager, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
FTPClient client = new FTPClient();
client.setSSLSocketFactory(sslSocketFactory);
client.setSecurity(FTPClient.SECURITY_FTPS);
// or client.setSecurity(FTPClient.SECURITY_FTPES); // ...
獲取當(dāng)前目錄的的絕對(duì)路徑(此目錄是FTP服務(wù)器的home目錄):
String dir = client.currentDirectory();
改變目錄:
client.changeDirectory(newPath);
你可以使用絕對(duì)路徑和相對(duì)路徑:
client.changeDirectory("/an/absolute/one");
client.changeDirectory("relative");
回到父目錄:
client.changeDirectoryUp();
要重命名遠(yuǎn)程文件或目錄:
client.rename("oldname", "newname");
rename() 方法也可以用來從當(dāng)前位置移動(dòng)文件或目錄到其它位置.
在這個(gè)例子子,假設(shè)在當(dāng)前工作目錄中,你有一個(gè)名為"myfile.txt"的文件,然后你想將其移動(dòng)到子目錄"myfolder"中:
client.rename("myfile.txt", "myfolder/myfile.txt");
要?jiǎng)h除遠(yuǎn)程文件,需要調(diào)用:
client.deleteFile(relativeOrAbsolutePath);
在這個(gè)例子中:
client.deleteFile("useless.txt");
如果遠(yuǎn)程服務(wù)給你機(jī)會(huì)的話,你可以在遠(yuǎn)程站點(diǎn)上創(chuàng)建新目錄:
client.createDirectory("newfolder");
你也可以已存在的目錄:
client.deleteDirectory(absoluteOrRelativePath);
在這個(gè)例子中:
client.deleteDirectory("oldfolder");
請(qǐng)注意,通常情況下,F(xiàn)TP 服務(wù)器只允許刪除空目錄.
FTP 協(xié)議并不會(huì)提供大量支持方法來獲取工作目錄的完整信息.通常LIST命令會(huì)給你想知道的東西,但不辛的是,每個(gè)服務(wù)器會(huì)使用不同樣式的響應(yīng). 這意味著某些服務(wù)器會(huì)返回UNIX樣式的目錄,有些服務(wù)器會(huì)返回DOS樣式的目錄,其它的服務(wù)器又會(huì)使用別的樣式.
ftp4j 包可以處理許多的LIST響應(yīng)格式, 并將它們構(gòu)建成統(tǒng)一目錄內(nèi)容的結(jié)構(gòu)對(duì)象表示.當(dāng)前ftp4j可以處理:
這可以通過使用可插拔的parsers來完成.包it.sauronsoftware.ftp4j.listparsers包含了用于處理上述樣式的對(duì)象.大多數(shù)時(shí)間,這些已經(jīng)夠用了。
要列出當(dāng)前工作目錄下的文件或文件夾,可調(diào)用:
FTPFile[] list = client.list();
如果你收到了FTPListParseException (it.sauronsoftware.ftp4j.FTPListParseException) 異常,這就意味著服務(wù)器對(duì)LIST命令返回了不可理解的樣式,即它不是上述列出的樣式.因此,你可以嘗試使用listNames() 方法, 但它并不如list()方法有優(yōu)勢(shì).。為了彌補(bǔ)這種缺陷,你可以構(gòu)建你自己的LIST響應(yīng)解析器,以支持你遇到的樣式.你可以實(shí)現(xiàn)FTPListParser (it.sauronsoftware.ftp4j.FTPListParser) 接口,然后你可以在client的addListParser()方法使用此實(shí)現(xiàn).
FTPFile (it.sauronsoftware.ftp4j.FTPFile) 對(duì)象提供了目錄內(nèi)容的表示,包括文件,子目錄和連接. 根據(jù)服務(wù)器的響應(yīng),F(xiàn)TPFile對(duì)象的某些字段可以是null 的或者是無意義的.請(qǐng)檢查javadocs來了解細(xì)節(jié).
在list() 方法中你也可以使用文件過濾參數(shù),如:
FTPFile[] list = client.list("*.jpg");
如果連接服務(wù)器明確支持MLSD命令, ftp4j會(huì)用其來代替基本的LIST命令。MLSD的響應(yīng)事實(shí)更為標(biāo)準(zhǔn),準(zhǔn)確,更易解析.不幸的是,不是所有服務(wù)器都支持這個(gè)命令,并且有些服務(wù)器支持得非常糟糕.基于這些理由,開發(fā)者可以控制ftp4j是否應(yīng)該使用MLSD命令,即通過調(diào)用FTPClient對(duì)象的setMLSDPolicy()方法. 合法的值:
FTPClient.MLSD_IF_SUPPORTED
client只在服務(wù)器明確支持MLSD命令時(shí),才使用MLSD命令. 這是ftp4j默認(rèn)的行為.
FTPClient.MLSD_ALWAYS
client總是會(huì)使用MLSD命令, 即便服務(wù)器沒有明確表明支持MLSD命令.
FTPClient.MLSD_NEVER
client絕不使用MLSD命令,即便服務(wù)器明確表明支持MLSD命令.
例如:
client.setMLSDPolicy(FTPClient.MLSD_NEVER);
通常情況下FTPFile對(duì)象會(huì)告訴你條目的最后修改時(shí)間, 但正如上面描述的,這依賴于服務(wù)器發(fā)回的響應(yīng).如果你需要最后的修改時(shí)間,但你又不能通過list()方法得到,那么可以嘗試這樣做:
java.util.Date md = client.modifiedDate("filename.ext");
下載遠(yuǎn)程文件最簡(jiǎn)單的方式是調(diào)用download(String, File) 方法:
client.download("remoteFile.ext", new java.io.File("localFile.ext"));
要上傳:
client.upload(new java.io.File("localFile.ext"));
要在已有文件中上傳追加內(nèi)容:
client.append(new java.io.File("localFile.ext"));
這些是阻塞式調(diào)用:它們會(huì)在傳輸完成后(或failed, 或 aborted時(shí))才返回. 此外同步鎖是否由客戶端來實(shí)施的,因?yàn)樵诿總€(gè)時(shí)間段內(nèi)只允許有一個(gè)常規(guī)的FTP通信.在每個(gè)時(shí)間段內(nèi),你可以處理多個(gè)傳輸器,即使用多個(gè)FTPClient 對(duì)象,每個(gè)都與服務(wù)器建立一個(gè)私有連接.
你可以使用FTPDataTransferListener (it.sauronsoftware.ftp4j.FTPDataTransferListener)對(duì)象來監(jiān)控傳輸.你可以自己實(shí)現(xiàn)一個(gè):
import it.sauronsoftware.ftp4j.FTPDataTransferListener;
public class MyTransferListener implements FTPDataTransferListener {
public void started() {
// Transfer started
}
public void transferred(int length) {
// Yet other length bytes has been transferred since the last time this
// method was called
}
public void completed() {
// Transfer completed
}
public void aborted() {
// Transfer aborted
}
public void failed() {
// Transfer failed
}
}
現(xiàn)在像下面這樣來下載或上傳:
client.download("remoteFile.ext", new java.io.File("localFile.ext"), new MyTransferListener());
client.upload(new java.io.File("localFile.ext"), new MyTransferListener());
client.append(new java.io.File("localFile.ext"), new MyTransferListener());
當(dāng)client處理下載或上傳時(shí),傳輸器可以被同一個(gè)FTPClient對(duì)象的不同線程通過調(diào)用 abortCurrentDataTransfer() 方法aborted. 此方法還需要一個(gè)boolean參數(shù):true表示執(zhí)行合法的abort過程(即向服務(wù)器發(fā)送ABOR命令), false表示實(shí)然關(guān)閉傳輸器,而不向服務(wù)器發(fā)送通知:
client.abortCurrentDataTransfer(true); // Sends ABOR
client.abortCurrentDataTransfer(false); // Breaks abruptly
需要注意的是,list()和listNames() 方法暗中包含了數(shù)據(jù)傳輸器,因abortCurrentDataTransfer() 方法也可以用來中斷其list過程.
當(dāng)數(shù)據(jù)傳輸器在download(), upload(), append(), list() and listNames() 方法中中斷時(shí),將會(huì)拋出FTPAbortedException (it.sauronsoftware.ftp4j.FTPAbortedException).
下載和上傳操作可通過restartAt 參數(shù)來重新恢復(fù):
client.download("remoteFile.ext", new java.io.File("localFile.ext"), 1056);
此操作會(huì)文件的第1056個(gè)字節(jié)處繼續(xù)執(zhí)行下載操作。第一個(gè)傳輸?shù)淖止?jié)將是第1057個(gè).
其它 download(), upload() 和append()方法的變種可以讓你使用流來替代java.io.File對(duì)象.因此你可以在數(shù)據(jù)庫,網(wǎng)絡(luò)連接或其它流上來傳輸數(shù)據(jù)。
客戶端和服務(wù)器之間的數(shù)據(jù)傳輸通道是通過單獨(dú)的網(wǎng)絡(luò)連接來建立的. 在傳輸通道建立期間,服務(wù)器可以是active或passive的. 服務(wù)器激活數(shù)據(jù)傳輸時(shí),工作如下:
active模式需要你的client能夠收到來自服務(wù)器的連接.如果你的client處于防火墻, 代理或這兩者混合之后,那么大部分時(shí)間都會(huì)出現(xiàn)問題,因?yàn)樗荒苁盏絹硗饨绲倪B接. 下面是passive數(shù)據(jù)傳輸模式:
在passive模式中,客戶端連接不要求能收到服務(wù)器的連接請(qǐng)求.
在ftp4j中,你可以使用下面的調(diào)用來切換active、passive模式:
client.setPassive(false); // Active mode
client.setPassive(true); // Passive mode
ftp4j client passive 標(biāo)志的默認(rèn)值為true: 如果你沒有調(diào)用setPassive(false) ,你的客戶端在每次傳輸前,都會(huì)向服務(wù)器請(qǐng)求passive模式.
當(dāng)使用 passive文件傳輸時(shí),服務(wù)器會(huì)提供一個(gè) IP地址和一個(gè)端口號(hào).作為FTP規(guī)范的client,需要使用給定的主機(jī)號(hào)和端口進(jìn)行連接.在商業(yè)環(huán)境中,這種行為可能會(huì)經(jīng)常帶來問題,因?yàn)镹AT配置可能會(huì)阻止對(duì)IP地址的連接.這就是為什么FTP clients通常會(huì)忽略服務(wù)器返回的任何IP地址,進(jìn)而在通信線路中使用同樣的主機(jī)來連接服務(wù)器.ftp4j的行為依賴于服務(wù)器因素:
在active傳輸模式中,可以設(shè)置下面的系統(tǒng)屬性:
ftp4j.activeDataTransfer.hostAddress
主機(jī)地址.當(dāng)服務(wù)器請(qǐng)求執(zhí)行與客戶端連接時(shí),client會(huì)跳轉(zhuǎn)到給定地址的服務(wù)器. 此值應(yīng)該是一個(gè)有效的IPv4地址,如:178.12.34.167. 如果沒有提供此值,客戶端會(huì)自動(dòng)解析系統(tǒng)地址.但如果client運(yùn)行于LAN中,為了激活數(shù)據(jù)傳輸,將會(huì)使用帶端口轉(zhuǎn)發(fā)的路由器來連接外部服務(wù)器,那么自動(dòng)探測(cè)到的地址可能不是正確的. 當(dāng)系統(tǒng)有多個(gè)網(wǎng)絡(luò)接口時(shí),也可能發(fā)生這種情況.通常使用系統(tǒng)屬性,可以解決這種問題
ftp4j.activeDataTransfer.portRange
連接端口范圍. client會(huì)在其中挑選一個(gè)來進(jìn)行數(shù)據(jù)傳輸.此值 必須是start-stop 形式 ,如6000-7000 表示client只會(huì)在給定范圍內(nèi)挑選一個(gè)端口來連接服務(wù)器.默認(rèn)情況下沒有指定端口范圍:這表示client會(huì)挑選任何一個(gè)可用的端口.
ftp4j.activeDataTransfer.acceptTimeout
以毫秒為單位的連接超時(shí)時(shí)間. 如果服務(wù)器不能在給定超時(shí)時(shí)間內(nèi)連接client,傳輸會(huì)因FTPDataTransferException異常而中斷.0值表示永不超時(shí)。默認(rèn)值30000 (即30秒).
要設(shè)置系統(tǒng)屬性,你可以:
用一個(gè)或多個(gè) -Dproperty=value參數(shù)來啟動(dòng)JVM.如:
java -Dftp4j.activeDataTransfer.hostAddress=178.12.34.167 -Dftp4j.activeDataTransfer.portRange=6000-7000 -Dftp4j.activeDataTransfer.acceptTimeout=5000 MyClass
直接在代碼中設(shè)置系統(tǒng)屬性,如:
System.setProperty("ftp4j.activeDataTransfer.hostAddress", "178.12.34.167");
System.setProperty("ftp4j.activeDataTransfer.portRange", "6000-7000");
System.setProperty("ftp4j.activeDataTransfer.acceptTimeout", "5000");
你可以調(diào)用下面的方法選擇你傳輸?shù)念愋?
client.setType(FTPClient.TYPE_TEXTUAL);
client.setType(FTPClient.TYPE_BINARY);
client.setType(FTPClient.TYPE_AUTO);
默認(rèn)的TYPE_AUTO常量 ,會(huì)讓client自動(dòng)來挑選類型:如果文件的擴(kuò)展名是client能被識(shí)別的文本類型標(biāo)記,那么它會(huì)選擇文本傳輸器來執(zhí)行. 文件擴(kuò)展名是通過FTPTextualExtensionRecognizer (it.sauronsoftware.ftp4j.FTPTextualExtensionRecognizer) 實(shí)例來識(shí)別的. 默認(rèn)擴(kuò)展識(shí)別器是it.sauronsoftware.ftp4j.recognizers.DefaultTextualExtensionRecognizer, 會(huì)將下面的視為文本類型:
abc acgi aip asm asp c c cc cc com conf cpp
csh css cxx def el etx f f f77 f90 f90 flx
for for g h h hh hh hlb htc htm html htmls
htt htx idc jav jav java java js ksh list
log lsp lst lsx m m mar mcf p pas php pl pl
pm py rexx rt rt rtf rtx s scm scm sdml sgm
sgm sgml sgml sh shtml shtml spc ssi talk
tcl tcsh text tsv txt uil uni unis uri uris
uu uue vcs wml wmls wsc xml zsh
你可通過實(shí)現(xiàn)FTPTextualExtensionRecognizer 接口來實(shí)現(xiàn)你自己的識(shí)別器,但你可以更喜歡使用 class ParametricTextualExtensionRecognizer(it.sauronsoftware.ftp4j.recognizers.ParametricTextualExtensionRecognizer)便利類.
無論如何,都不要忘記將你的識(shí)別器設(shè)置在client中:
client.setTextualExtensionRecognizer(myRecognizer);
有些服務(wù)器支持?jǐn)?shù)據(jù)傳輸壓縮特性-MODE Z. 在傳輸大文件時(shí),此特性可以節(jié)省帶寬.一旦client連上服務(wù)器并通過認(rèn)證,就可通過調(diào)用下面的方法來檢查是否支持壓縮:
boolean compressionSupported = client.isCompressionSupported();
如果服務(wù)器端支持壓縮,就可通過下面的調(diào)用來啟用壓縮:
client.setCompressionEnabled(true);
在此調(diào)用之后,后續(xù)的數(shù)據(jù)傳輸(下載,上傳,列舉操作)都被將壓縮以節(jié)省帶寬.
數(shù)據(jù)傳輸壓縮可通過下面的調(diào)用來禁用:
client.setCompressionEnabled(false);
也可以檢查標(biāo)記值:
boolean compressionEnabled = client.isCompressionEnabled();
請(qǐng)注意:壓縮數(shù)據(jù)傳輸只當(dāng)壓縮支持且啟用了的情況下才會(huì)發(fā)生.
默認(rèn)情況下,壓縮是禁用的,即便是服務(wù)器支持壓縮. 如果有需要,可以顯示地打開.
假設(shè)你的client什么事情都不做,因?yàn)樵诘却脩糨斎? 通常情況下, FTP服務(wù)器會(huì)自動(dòng)斷開非活躍客戶端. 為了避免超時(shí),你可以發(fā)送NOOP命令.
此命令不會(huì)做任何事情,但它會(huì)向服務(wù)器說明:客戶端仍然還活著,請(qǐng)重圍超時(shí)計(jì)數(shù)器.調(diào)用如下:
client.noop();
當(dāng)非活躍超時(shí)發(fā)生時(shí),客戶端也可以自動(dòng)發(fā)送NOOPs. 默認(rèn)情況下,此特性是禁用的。它可以在 setAutoNoopTimeout() 方法中設(shè)置超時(shí)時(shí)間時(shí)啟用,并提供一個(gè)毫秒為單位的值.如:
client.setAutoNoopTimeout(30000);
使用此值,client會(huì)在30秒后發(fā)送一個(gè)NOOP命令.
NOOP超時(shí)可通過設(shè)置小于等于0的值來禁用:
client.setAutoNoopTimeout(0);
你可以像下面這樣來發(fā)送站點(diǎn)特殊命令:
FTPReply reply = client.sendSiteCommand("YOUR COMMAND");
你也可以發(fā)送自定義命令:
FTPReply reply = client.sendCustomCommand("YOUR COMMAND");
sendSiteCommand() 和 sendCustomCommand() 都會(huì)返回一個(gè)FTPReply (it.sauronsoftware.ftp4j.FTPReply)對(duì)象.使用此對(duì)象,你可以檢查服務(wù)器的響應(yīng)代碼和消息.
FTPCodes (it.sauronsoftware.ftp4j.FTPCodes) 接口報(bào)告了一些通用的FTP響應(yīng)代碼,因此你可以使用這些包中的某個(gè)來進(jìn)行匹配.
ftp4j 包定義了五種類型的異常: