關鍵字: http rest
1.資源(Resource)
什么是資源,首先我們看《架構風格與基于網絡的軟件架構設計》的作者:Roy Thomas Fielding對資源的一個說明
簡而言之,資源是一個抽象的東西,而在具體資源訪問時,會根據內容協商的結果表示成一個具體表述(Representations)。每個資源由統一的資源標識符(URI)來描述,類似于資源的ID,或者說資源的地址。
2.表述(Representations)
表述是資源的具體表現形式,譬如,今天深圳的天氣(資源),可以使用一副天氣jpg圖來描述,也可以XML數據來描述,也可以使用HTML的頁面來描述,一種資源可以有多種表述,也就是說,通過同一個URI地址可以獲取到多種表現形式,而具體怎么表現,則取決于Web客戶端與Web服務端內容協商的一個結果。
3.內容協商
客戶端和服務端通過內容協商來協商請求內容和響應內容的格式,主要協商的內容包括:
請求協商:
1)字符集(Accept-Charset):客戶端通過發送該協商建議服務端使用該字符集來發送響應結果,譬如
Accept-Charset=gb2312,utf-8;q=0.7,*;q=0.7,客戶端建議服務端優先使用gb2312或者utf-8來發送響應結果
2)請求編碼(Accept-Encoding):客戶端告訴服務端客戶端所支持的編碼格式,譬如Accept-Encoding =gzip,deflate表明客戶端支持gzip壓縮或者普通響應的結果
3)語言(Accept-Language):客戶端通過發送該協商告訴服務端客戶端所使用的語言,譬如Accept-Language=zh-cn,zh;q=0.5表明客戶端偏號的語言是中文,譬如對于”今天深圳的天氣“這個資源來說,服務端可以通過該選項決定使用什么語言來表述資源
4)表述偏好(Accept):客戶端通過該選項告訴服務端其表述的偏號,譬如一個請求”今天深圳的天氣“的Ajax程序,可以通過設置表述偏好為Accept=application/json來告訴服務端,希望得到Json描述的結果,而一個瀏覽器則可以通過傳輸表述偏好為Accept=text/html來告訴服務端,希望得到Html描述的結果
響應協商:
1)表述/表述字符集(Content-Type):服務端通過該協商告訴客戶端表述的格式和字符集的情況,譬如Content-Type=text/html; charset=utf-8表示響應內容格式為Html,字符集為utf-8
2)表述編碼(Accept-Encoding):服務端通過該協商告訴客戶端表述的編碼,譬如Content-Encoding=gzip服務端告訴客戶端內容使用gzip壓縮
4.方法
方法定義了對資源的操作,主要的方法包括GET、POST、UPDATE、DELETE等等,它們分別代表了對資源的讀、建、改、刪的操作
5.緩存
HTTP協議支持在Web的各個節點對資源的表述進行緩存,譬如在瀏覽器客戶端、代理服務器、反向代理服務器、目標服務器等上對表述進行緩存(注意,此處的緩存不僅僅是指在目標服務器上進行的業務級別的緩存)
1)客戶端請求
如果客戶端緩存了某些表述,則在進行讀請求(GET)時,攜帶請求條件(所謂的條件GET,使用Cache-Control指令),服務端接收到客戶端的請求條件,比較確認客戶端的表述是否過時,如果沒有,則返回304響應,否則則把最新的表述響應給客戶端
2)服務端響應
服務端對一些需要緩存的表述,則響應中攜帶緩存指令,告訴請求客戶端如何對表述進行緩存
3)方法對緩存的影響
當對一個資源進行UPDATE或DELETE時,請求途經的所有服務器(如代理服務器、反向代理服務器、目標服務器)會自動將該資源對應的所有表述 緩存失效。
6.狀態碼
狀態碼描述了資源請求的結果,主要狀態碼包括:
1)1XX:信息類
2)2XX:成功類
典型的成功響應包括:
200 OK,表示請求正常處理
201 Created,表示POST請求已經接受,資源已創建,對于此響應,一般響應會攜帶新建資源的Reference給請求客戶端
202 Accepted,表示POST/UDATE請求已經接受,但不一定處理,譬如對于POST/UPDATE請求為后臺新建線程處理,可以使用該響應碼
3)3XX: 重定向類
典型的重定向響應包括:
300 Multiple Choice:表明請求的資源有多種表述
301 Moved Permenently:表明所請求的資源已轉移到其他位置,建議到新的位置上去請求資源
304 Not Modify:主要是針對攜帶條件的GET請求,服務端向客戶端表明所請求的資源沒有發生變化,可以繼續使用客戶端已緩存的數據
4)4XX: 客戶端錯誤類
典型的客戶端錯誤響應包括:
400 Bad Request:表明客戶端的請求格式服務端無法識別
403 Forbiden:客戶端要訪問的資源權限受限,不允許訪問
404 Not found:客戶端要訪問的資源不存在
405 Method Not Allowed:客戶端請求的方法不允許,譬如有可能一個資源不允許刪除,則不允許進行DELETE請求
408 Request Timeout:請求超時
5)5XX: 服務端錯誤類
典型的服務端響應包括:
500 Internal Server Error:服務器內部錯誤,無法響應
503 Service Unavailable:服務器無法處理當前請求
504 Gateway Timeout:網關超時
生產者進程(進程由多個線程組成)生產信息,例如它可以是計算進程。消費
者進程使用信息,它可以是輸出打印進程。由于生產者和消費者彼此獨立,且運
行速度不確定,所以很可能出現生產者已產生了信息而消費者卻沒有來得及接受
信息這種情況。為此,需要引入由一個或者若干個存儲單元組成的臨時存儲區,
以便存放生產者所產生的信息,平滑進程間由于速度不確定所帶來的問題。這個
臨時存儲區叫做緩沖區,通常用一維數組來表示。
由一個或若干個存儲單元組成的緩沖區叫作“有窮緩沖區”。下面我們來分
析一下有窮緩沖的生產者和消費者的例子。
假設有多個生產者和多個消費者,它們共享一個具有n個存儲單元的有窮緩沖
區Buffer(0……n-1),這是一個環形隊列。其隊尾指針Rear指向當前信息應存放
的位置(Buffer[Rear]),隊首指針Front指向當前取出信息的位置(Buffer[front
])。生產者進程總是把信息存放在Buffer[Rear]中,消費者進程則總是從Buffer
[Rear]中取出信息。如果想使生產者進程和消費者進程協調合作,則必須使它們
遵循如下規則:
1) 只要緩沖區有存儲單元,生產者都可往其中存放信息;當緩沖區已滿時,
若任意生產者提出寫要求,則都必須等待;
2) 只要緩沖區中有消息可取,消費者都可從緩沖區中取出消息;當緩沖區為
空時,若任意消費者想取出信息,則必須等待;
3) 生產者們和消費者們不能同時讀、寫緩沖區。
用JAVA 實現“生產者-消費者”問題的代碼如下:
public class ProducerConsumer {
public static void main(String[] args) {
SyncStack ss = new SyncStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
new Thread(p).start();
new Thread(p).start();
new Thread(p).start();
new Thread(c).start();
}
}
class WoTou {
int id;
WoTou(int id) {
this.id = id;
}
public String toString() {
return "WoTou : " + id;
}
}
class SyncStack {
int index = 0;
WoTou[] arrWT = new WoTou[6];
public synchronized void push(WoTou wt) {
while(index == arrWT.length) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
arrWT[index] = wt;
index ++;
}
public synchronized WoTou pop() {
while(index == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
index--;
return arrWT[index];
}
}
class Producer implements Runnable {
SyncStack ss = null;
Producer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for(int i=0; i<20; i++) {
WoTou wt = new WoTou(i);
ss.push(wt);
System.out.println("生產了:" + wt);
try {
Thread.sleep((int)(Math.random() * 200));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
SyncStack ss = null;
Consumer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for(int i=0; i<20; i++) {
WoTou wt = ss.pop();
System.out.println("消費了: " + wt);
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生產者消費者問題是研究多線程程序時繞不開的問題,它的描述是有一塊生產者和消費者共享的有界緩沖區,生產者往緩沖區放入產品,消費者從緩沖區取走產品,這個過程可以無休止的執行,不能因緩沖區滿生產者放不進產品而終止,也不能因緩沖區空消費者無產品可取而終止。
解決生產者消費者問題的方法有兩種,一種是采用某種機制保持生產者和消費者之間的同步,一種是在生產者和消費者之間建立一個管道。前一種有較高的效率并且可控制性較好,比較常用,后一種由于管道緩沖區不易控制及被傳輸數據對象不易封裝等原因,比較少用。
同步問題的核心在于,CPU是按時間片輪詢的方式執行程序,我們無法知道某一個線程是否被執行、是否被搶占、是否結束等,因此生產者完全可能當緩沖區已滿的時候還在放入產品,消費者也完全可能當緩沖區為空時還在取出產品。
現在同步問題的解決方法一般是采用信號或者加鎖機制,即生產者線程當緩沖區已滿時放棄自己的執行權,進入等待狀態,并通知消費者線程執行。消費者線程當緩沖區已空時放棄自己的執行權,進入等待狀態,并通知生產者線程執行。這樣一來就保持了線程的同步,并避免了線程間互相等待而進入死鎖狀態。
JAVA語言提供了獨立于平臺的線程機制,保持了”write once, run anywhere”的特色。同時也提供了對同步機制的良好支持。
在JAVA中,一共有四種方法支持同步,其中三個是同步方法,一個是管道方法。
1. 方法wait()/notify()
2. 方法await()/signal()
3. 阻塞隊列方法BlockingQueue
4. 管道方法PipedInputStream/PipedOutputStream
下面我們看各個方法的實現:
1. 方法wait()/notify()
wait()和notify()是根類Object的兩個方法,也就意味著所有的JAVA類都會具有這個兩個方法,為什么會被這樣設計呢?我們可以認為所有的對象默認都具有一個鎖,雖然我們看不到,也沒有辦法直接操作,但它是存在的。
wait()方法表示:當緩沖區已滿或空時,生產者或消費者線程停止自己的執行,放棄鎖,使自己處于等待狀態,讓另一個線程開始執行;
notify()方法表示:當生產者或消費者對緩沖區放入或取出一個產品時,向另一個線程發出可執行通知,同時放棄鎖,使自己處于等待狀態。
下面是一個例子代碼:
import java.util.LinkedList;
public class Sycn1...{
private LinkedList<Object> myList =new LinkedList<Object>();
private int MAX = 10;
public Sycn1()...{
}
public void start()...{
new Producer().start();
new Consumer().start();
}
public static void main(String[] args) throws Exception...{
Sycn1 s1 = new Sycn1();
s1.start();
}
class Producer extends Thread...{
public void run()...{
while(true)...{
synchronized(myList)...{
try...{
while(myList.size() == MAX)...{
System.out.println("warning: it's full!");
myList.wait();
}
Object o = new Object();
if(myList.add(o))...{
System.out.println("Producer: " + o);
myList.notify();
}
}catch(InterruptedException ie)...{
System.out.println("producer is interrupted!");
}
}
}
}
}
class Consumer extends Thread...{
public void run()...{
while(true)...{
synchronized(myList)...{
try...{
while(myList.size() == 0)...{
System.out.println("warning: it's empty!");
myList.wait();
}
Object o = myList.removeLast();
System.out.println("Consumer: " + o);
myList.notify();
}catch(InterruptedException ie)...{
System.out.println("consumer is interrupted!");
}
}
}
}
}
}
2. 方法await()/signal()
在JDK5.0以后,JAVA提供了新的更加健壯的線程處理機制,包括了同步、鎖定、線程池等等,它們可以實現更小粒度上的控制。await()和signal()就是其中用來做同步的兩種方法,它們的功能基本上和wait()/notify()相同,完全可以取代它們,但是它們和新引入的鎖定機制Lock直接掛鉤,具有更大的靈活性。
下面是一個例子代碼:
import java.util.LinkedList;
import java.util.concurrent.locks.*;
public class Sycn2...{
private LinkedList<Object> myList = new LinkedList<Object>();
private int MAX = 10;
private final Lock lock = new ReentrantLock();
private final Condition full = lock.newCondition();
private final Condition empty = lock.newCondition();
public Sycn2()...{
}
public void start()...{
new Producer().start();
new Consumer().start();
}
public static void main(String[] args) throws Exception...{
Sycn2 s2 = new Sycn2();
s2.start();
}
class Producer extends Thread...{
public void run()...{
while(true)...{
lock.lock();
try...{
while(myList.size() == MAX)...{
System.out.println("warning: it's full!");
full.await();
}
Object o = new Object();
if(myList.add(o))...{
System.out.println("Producer: " + o);
empty.signal();
}
}catch(InterruptedException ie)...{
System.out.println("producer is interrupted!");
}finally...{
lock.unlock();
}
}
}
}
class Consumer extends Thread...{
public void run()...{
while(true)...{
lock.lock();
try...{
while(myList.size() == 0)...{
System.out.println("warning: it's empty!");
empty.await();
}
Object o = myList.removeLast();
System.out.println("Consumer: " + o);
full.signal();
}catch(InterruptedException ie)...{
System.out.println("consumer is interrupted!");
}finally...{
lock.unlock();
}
}
}
}
}
3. 阻塞隊列方法BlockingQueue
BlockingQueue也是JDK5.0的一部分,它是一個已經在內部實現了同步的隊列,實現方式采用的是我們的第2種await()/signal()方法。它可以在生成對象時指定容量大小。
它用于阻塞操作的是put()和take()方法。
put()方法類似于我們上面的生產者線程,容量最大時,自動阻塞。
take()方法類似于我們上面的消費者線程,容量為0時,自動阻塞。
下面是一個例子代碼:
import java.util.concurrent.*;
public class Sycn3...{
private LinkedBlockingQueue<Object> queue = new LinkedBlockingQueue<Object>(10);
private int MAX = 10;
public Sycn3()...{
}
public void start()...{
new Producer().start();
new Consumer().start();
}
public static void main(String[] args) throws Exception...{
Sycn3 s3 = new Sycn3();
s3.start();
}
class Producer extends Thread...{
public void run()...{
while(true)...{
//synchronized(this){
try...{
if(queue.size() == MAX)
System.out.println("warning: it's full!");
Object o = new Object();
queue.put(o);
System.out.println("Producer: " + o);
}catch(InterruptedException e)...{
System.out.println("producer is interrupted!");
}
//}
}
}
}
class Consumer extends Thread...{
public void run()...{
while(true)...{
//synchronized(this){
try...{
if(queue.size() == 0)
System.out.println("warning: it's empty!");
Object o = queue.take();
System.out.println("Consumer: " + o);
}catch(InterruptedException e)...{
System.out.println("producer is interrupted!");
}
//}
}
}
}
}
你發現這個例子中的問題了嗎?
如果沒有,我建議你運行一下這段代碼,仔細觀察它的輸出,是不是有下面這個樣子的?為什么會這樣呢?
…
warning: it's full!
Producer: java.lang.object@4526e2a
…
你可能會說這是因為put()和System.out.println()之間沒有同步造成的,我也這樣認為,我也這樣認為,但是你把run()中的synchronized前面的注釋去掉,重新編譯運行,有改觀嗎?沒有。為什么?
這是因為,當緩沖區已滿,生產者在put()操作時,put()內部調用了await()方法,放棄了線程的執行,然后消費者線程執行,調用take()方法,take()內部調用了signal()方法,通知生產者線程可以執行,致使在消費者的println()還沒運行的情況下生產者的println()先被執行,所以有了上面的輸出。run()中的synchronized其實并沒有起什么作用。
對于BlockingQueue大家可以放心使用,這可不是它的問題,只是在它和別的對象之間的同步有問題。
對于這種多重嵌套同步的問題,以后再談吧,歡迎大家討論??!
4. 管道方法PipedInputStream/PipedOutputStream
這個類位于java.io包中,是解決同步問題的最簡單的辦法,一個線程將數據寫入管道,另一個線程從管道讀取數據,這樣便構成了一種生產者/消費者的緩沖區編程模式。
下面是一個例子代碼,在這個代碼我沒有使用Object對象,而是簡單的讀寫字節值,這是因為PipedInputStream/PipedOutputStream不允許傳輸對象,這是JAVA本身的一個bug,具體的大家可以看sun的解釋:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4131126
import java.io.*;
public class Sycn4...{
private PipedOutputStream pos;
private PipedInputStream pis;
//private ObjectOutputStream oos;
//private ObjectInputStream ois;
public Sycn4()...{
try...{
pos = new PipedOutputStream();
pis = new PipedInputStream(pos);
//oos = new ObjectOutputStream(pos);
//ois = new ObjectInputStream(pis);
}catch(IOException e)...{
System.out.println(e);
}
}
public void start()...{
new Producer().start();
new Consumer().start();
}
public static void main(String[] args) throws Exception...{
Sycn4 s4 = new Sycn4();
s4.start();
}
class Producer extends Thread...{
public void run() ...{
try...{
while(true)...{
int b = (int) (Math.random() * 255);
System.out.println("Producer: a byte, the value is " + b);
pos.write(b);
pos.flush();
//Object o = new MyObject();
//oos.writeObject(o);
//oos.flush();
//System.out.println("Producer: " + o);
}
}catch(Exception e)...{
//System.out.println(e);
e.printStackTrace();
}finally...{
try...{
pos.close();
pis.close();
//oos.close();
//ois.close();
}catch(IOException e)...{
System.out.println(e);
}
}
}
}
class Consumer extends Thread...{
public void run()...{
try...{
while(true)...{
int b = pis.read();
System.out.println("Consumer: a byte, the value is " + String.valueOf(b));
//Object o = ois.readObject();
//if(o != null)
//System.out.println("Consumer: " + o);
}
}catch(Exception e)...{
//System.out.println(e);
e.printStackTrace();
}finally...{
try...{
pos.close();
pis.close();
//oos.close();
//ois.close();
}catch(IOException e)...{
System.out.println(e);
}
}
}
}
//class MyObject implements Serializable {
//}
}
出處:http://blog.csdn.net/JaunLee/archive/2008/02/01/2077291.aspx
原文地址:http://tech.e800.com.cn/articles/2009/79/1247104593971_1.html
JVM內存由 Perm 和 Heap 組成. 其中
Heap = {Old + NEW = { Eden , from, to } }
JVM內存模型中分兩大塊,一塊是 NEW Generation, 另一塊是Old Generation. 在New Generation中,有一個叫Eden的空間,主要是用來存放新生的對象,還有兩個Survivor Spaces(from,to), 它們用來存放每次垃圾回收后存活下來的對象。在Old Generation中,主要存放應用程序中生命周期長的內存對象,還有個Permanent Generation,主要用來放JVM自己的反射對象,比如類對象和方法對象等。
垃圾回收描述:
在New Generation塊中,垃圾回收一般用Copying的算法,速度快。每次GC的時候,存活下來的對象首先由Eden拷貝到某個Survivor Space, 當Survivor Space空間滿了后, 剩下的live對象就被直接拷貝到Old Generation中去。因此,每次GC后,Eden內存塊會被清空。在Old Generation塊中,垃圾回收一般用mark-compact的算法,速度慢些,但減少內存要求.
垃圾回收分多級,0級為全部(Full)的垃圾回收,會回收OLD段中的垃圾;1級或以上為部分垃圾回收,只會回收NEW中的垃圾,內存溢出通常發生于OLD段或Perm段垃圾回收后,仍然無內存空間容納新的Java對象的情況。
當一個URL被訪問時,內存申請過程如下:
A. JVM會試圖為相關Java對象在Eden中初始化一塊內存區域
B. 當Eden空間足夠時,內存申請結束。否則到下一步
C. JVM試圖釋放在Eden中所有不活躍的對象(這屬于1或更高級的垃圾回收), 釋放后若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區
D. Survivor區被用來作為Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,否則會被保留在Survivor區
E. 當OLD區空間不夠時,JVM會在OLD區進行完全的垃圾收集(0級)
F. 完全垃圾收集后,若Survivor及OLD區仍然無法存放從Eden復制過來的部分對象,導致JVM無法在Eden區為新對象創建內存區域,則出現”out of memory錯誤”
JVM調優建議:
ms/mx:定義YOUNG+OLD段的總尺寸,ms為JVM啟動時YOUNG+OLD的內存大??;mx為最大可占用的YOUNG+OLD內存大小。在用戶生產環境上一般將這兩個值設為相同,以減少運行期間系統在內存申請上所花的開銷。
NewSize/MaxNewSize:定義YOUNG段的尺寸,NewSize為JVM啟動時YOUNG的內存大??;MaxNewSize為最大可占用的YOUNG內存大小。在用戶生產環境上一般將這兩個值設為相同,以減少運行期間系統在內存申請上所花的開銷。
PermSize/MaxPermSize:定義Perm段的尺寸,PermSize為JVM啟動時Perm的內存大?。籑axPermSize為最大可占用的Perm內存大小。在用戶生產環境上一般將這兩個值設為相同,以減少運行期間系統在內存申請上所花的開銷。
SurvivorRatio:設置Survivor空間和Eden空間的比例
內存溢出的可能性
1. OLD段溢出
這種內存溢出是最常見的情況之一,產生的原因可能是:
1) 設置的內存參數過小(ms/mx, NewSize/MaxNewSize)
2) 程序問題
單個程序持續進行消耗內存的處理,如循環幾千次的字符串處理,對字符串處理應建議使用StringBuffer。此時不會報內存溢出錯,卻會使系統持續垃圾收集,無法處理其它請求,相關問題程序可通過Thread Dump獲?。ㄒ娤到y問題診斷一章)單個程序所申請內存過大,有的程序會申請幾十乃至幾百兆內存,此時JVM也會因無法申請到資源而出現內存溢出,對此首先要找到相關功能,然后交予程序員修改,要找到相關程序,必須在Apache日志中尋找。
當Java對象使用完畢后,其所引用的對象卻沒有銷毀,使得JVM認為他還是活躍的對象而不進行回收,這樣累計占用了大量內存而無法釋放。由于目前市面上還沒有對系統影響小的內存分析工具,故此時只能和程序員一起定位。
2. Perm段溢出
通常由于Perm段裝載了大量的Servlet類而導致溢出,目前的解決辦法:
1) 將PermSize擴大,一般256M能夠滿足要求
2) 若別無選擇,則只能將servlet的路徑加到CLASSPATH中,但一般不建議這么處理
3. C Heap溢出
系統對C Heap沒有限制,故C Heap發生問題時,Java進程所占內存會持續增長,直到占用所有可用系統內存
其他:
JVM有2個GC線程。第一個線程負責回收Heap的Young區。第二個線程在Heap不足時,遍歷Heap,將Young 區升級為Older區。Older區的大小等于-Xmx減去-Xmn,不能將-Xms的值設的過大,因為第二個線程被迫運行會降低JVM的性能。
為什么一些程序頻繁發生GC?有如下原因:
l 程序內調用了System.gc()或Runtime.gc()。
l 一些中間件軟件調用自己的GC方法,此時需要設置參數禁止這些GC。
l Java的Heap太小,一般默認的Heap值都很小。
l 頻繁實例化對象,Release對象。此時盡量保存并重用對象,例如使用StringBuffer()和String()。
如果你發現每次GC后,Heap的剩余空間會是總空間的50%,這表示你的Heap處于健康狀態。許多Server端的Java程序每次GC后最好能有65%的剩余空間。
經驗之談:
1.Server端JVM最好將-Xms和-Xmx設為相同值。為了優化GC,最好讓-Xmn值約等于-Xmx的1/3[2]。
2.一個GUI程序最好是每10到20秒間運行一次GC,每次在半秒之內完成[2]。
注意:
1.增加Heap的大小雖然會降低GC的頻率,但也增加了每次GC的時間。并且GC運行時,所有的用戶線程將暫停,也就是GC期間,Java應用程序不做任何工作。
2.Heap大小并不決定進程的內存使用量。進程的內存使用量要大于-Xmx定義的值,因為Java為其他任務分配內存,例如每個線程的Stack等。
2.Stack的設定
每個線程都有他自己的Stack。
-Xss
每個線程的Stack大小
Stack的大小限制著線程的數量。如果Stack過大就好導致內存溢漏。-Xss參數決定Stack大小,例如-Xss1024K。如果Stack太小,也會導致Stack溢漏。
3.硬件環境
硬件環境也影響GC的效率,例如機器的種類,內存,swap空間,和CPU的數量。
如果你的程序需要頻繁創建很多transient對象,會導致JVM頻繁GC。這種情況你可以增加機器的內存,來減少Swap空間的使用[2]。
4.4種GC
第一種為單線程GC,也是默認的GC。,該GC適用于單CPU機器。
第二種為Throughput GC,是多線程的GC,適用于多CPU,使用大量線程的程序。第二種GC與第一種GC相似,不同在于GC在收集Young區是多線程的,但在Old區和第一種一樣,仍然采用單線程。-XX:+UseParallelGC參數啟動該GC。
第三種為Concurrent Low Pause GC,類似于第一種,適用于多CPU,并要求縮短因GC造成程序停滯的時間。這種GC可以在Old區的回收同時,運行應用程序。-XX:+UseConcMarkSweepGC參數啟動該GC。
第四種為Incremental Low Pause GC,適用于要求縮短因GC造成程序停滯的時間。這種GC可以在Young區回收的同時,回收一部分Old區對象。-Xincgc參數啟動該GC。
Java Socket
套接字(socket)為兩臺計算機之間的通信提供了一種機制,在James Gosling注意到Java 語言之前,套接字就早已赫赫有名。該語言只是讓您不必了解底層操作系統的細節就能有效地使用套接字。 1 客戶機/服務器模型 在飯店里,菜單上各種具有異國情調的食品映入你的眼簾,于是你要了一份pizza。幾分鐘后,你用力咀嚼澆著融化的乳酪和其他你喜歡的配料的熱pizza。你不知道,也不想知道:侍者從那里弄來了pizza,在制作過程中加進了什么,以及配料是如何獲得的。 上例中包含的實體有:美味的pizza、接受你定餐的侍者、制作pizza的廚房,當然還有你。你是定pizza的顧客或客戶。制作pizza的過程對于你而言是被封裝的。你的請求在廚房中被處理,pizza制作完成后,由侍者端給你。 你所看到的就是一個客戶機/服務器模型??蛻魴C向服務器發送一個請求或命令。服務器處理客戶機的請求??蛻魴C和服務器之間的通訊是客戶機/服務器模型中的一個重要組成部分,通常通過網絡進行。 客戶機/服務器模型是一個應用程序開發框架,該框架是為了將數據的表示與其內部的處理和存儲分離開來而設計的。客戶機請求服務,服務器為這些請求服務。請求通過網絡從客戶機傳遞到服務器。服務器所進行的處理對客戶機而言是隱藏的。一個服務器可以為多臺客戶機服務。 多臺客戶機訪問服務器 服務器和客戶機不一定是硬件組件。它們可以是工作啊同一機器或不同機器上的程序。、 考慮一個航空定票系統中的數據輸入程序:數據----乘客名、航班號、飛行日期、目的地等可以被輸入到前端----客戶機的應用程序中。一旦數據輸入之后,客戶機將數據發送到后端----服務器端。服務器處理數據并在數據庫中保存數據??蛻魴C/服務器模型的重要性在于所有的數據都存放在同一地點。客戶機從不同的地方訪問同一數據源,服務器對所有的輸入數據應用同樣的檢驗規則。 萬維網為‘為什么要將數據的表示與其存儲、處理分離開來’提供了一個很好的例子。在Web上,你無需控制最終用戶用來訪問你數據的平臺和軟件。你可以考慮編寫出適用與每一種潛在的目標平臺的應用程序。 ‘客戶機/服務器應用程序的服務器部分’管理通過多個客戶機訪問服務器的、多個用戶共享的資源。表明‘客戶機/服務器程序的服務器部分’強大功能的最好例子應該是Web服務器,它通過Internet將HTML頁傳遞給不同的Web用戶。 Java編程語言中最基本的特點是在Java中創建的程序的代碼的可移植性。因為具有其他語言所不具備的代碼可移植性,Java允許用戶只要編寫一次應用程序,就可以在任何客戶機系統上發布它,并可以讓客戶機系統解釋該程序。這意味著:你只要寫一次代碼,就能使其在任何平臺上運行。 2 協議 當你同朋友交談時,你們遵循一些暗含的規則(或協議)。例如:你們倆不能同時開始說話,或連續不間斷地說話。如果你們這樣作的話,誰也不能理解對方所說的東西。當你說話時,你的朋友傾聽,反之亦然。你們以雙方都能理解的語言和速度進行對話。 當計算機之間進行通訊的時候,也需要遵循一定的規則。數據以包的形式從一臺機器發送到另一臺。這些規則管理數據打包、數據傳輸速度和重新 數據將其恢復成原始形式。這些規則被稱為網絡協議。網絡協議是通過網絡進行通訊的系統所遵循的一系列規則和慣例。連網軟件通常實現有高低層次之分的多層協議。網絡協議的例子有:TCP/IP、UDP、Apple Talk和NetBEUI。 Java提供了一個豐富的、支持網絡的類庫,這些類使得應用程序能方便地訪問網絡資源。Java提供了兩種通訊工具。它們是:使用用戶報文協議(UDP)的報文和使用傳輸控制協議/因特網協議(TCP/IP)的Sockets(套接字)。 數據報包是一個字節數組從一個程序(發送程序)傳送到另一個(接受程序)。由于數據報遵守UDP,不保證發出的數據包必須到達目的地。數據報并不是可信賴的。因此,僅當傳送少量數據時才使用,而且發送者和接受者之間的距離間隔不大,假如是網絡交通高峰,或接受程序正處理來自其他程序的多個請求,就有機會出現數據報包的丟失。 Sockets套接字用TCP來進行通訊。套接字模型同其他模型相比,優越性在于其不受客戶請求來自何處的影響。只要客戶機遵循TCP/IP協議,服務器就會對它的請求提供服務。這意味著客戶機可以是任何類型的計算機??蛻魴C不再局限為UNIX、Windows、DOS或Macintosh平臺,因此,網上所有遵循TCP/IP協議的計算機可以通過套接字互相通訊。 3 Sockets套接字 3.1 Sockets概況 在客戶機/服務器應用程序中,服務器提供象處理數據庫查詢或修改數據庫中的數據之類的服務。發生在客戶機和服務器之間的通訊必須是可靠的,同時數據在客戶機上的次序應該和服務器發送出來的次序相同。 什么是套接字? 既然我們已經知道套接字扮演的角色,那么剩下的問題是:什么是套接字?Bruce Eckel 在他的《Java 編程思想》一書中這樣描述套接字:套接字是一種軟件抽象,用于表達兩臺機器之間的連接“終端”。對于一個給定的連接,每臺機器上都有一個套接字,您也可以想象它們之間有一條虛擬的“電纜”,“電纜”的每一端都插入到套接字中。當然,機器之間的物理硬件和電纜連接都是完全未知的。抽象的全部目的是使我們無須知道不必知道的細節。 簡言之,一臺機器上的套接字與另一臺機器上的套接字交談就創建一條通信通道。程序員可以用該通道來在兩臺機器之間發送數據。當您發送數據時,TCP/IP 協議棧的每一層都會添加適當的報頭信息來包裝數據。這些報頭幫助協議棧把您的數據送到目的地。好消息是 Java 語言通過"流"為您的代碼提供數據,從而隱藏了所有這些細節,這也是為什么它們有時候被叫做流套接字(streaming socket)的原因。 把套接字想成兩端電話上的聽筒,我和您通過專用通道在我們的電話聽筒上講話和聆聽。直到我們決定掛斷電話,對話才會結束(除非我們在使用蜂窩電話)。而且我們各自的電話線路都占線,直到我們掛斷電話。 如果想在沒有更高級機制如 ORB(以及 CORBA、RMI、IIOP 等等)開銷的情況下進行兩臺計算機之間的通信,那么套接字就適合您。套接字的低級細節相當棘手。幸運的是,Java 平臺給了您一些雖然簡單但卻強大的更高級抽象,使您可以容易地創建和使用套接字。 傳輸控制協議(TCP)提供了一條可靠的、點對點的通訊通道,客戶機/服務器應用程序可以用該通道互相通訊。要通過TCP進行通訊,客戶機和服務器程序建立連接并綁定套接字。套接字用于處理通過網絡連接的應用程序之間的通訊。客戶機和服務器之間更深入的通訊通過套接字完成。 Java被設計成一種連網語言。它通過將連接功能封裝到套接字類里而使得網絡編程更加容易。套接字類即Socket類(它創建一個客戶套接字)和ServerSocket類(它創建一個服務器套接字)。套接字類大致介紹如下: l Socket是基類,它支持TCP協議。TCP是一個可靠的流網絡連接協議。Socket類提供了流輸入/輸出的方法,使得從套接字中讀出數據和往套接字中寫數據都很容易。該類對于編寫因特網上的通訊程序而言是必不可少的。 l ServerSocket是一個因特網服務程序用來監聽客戶請求的類。ServerSocket實際上并不執行服務;而是創建了一個Socket對象來代表客戶機。通訊由創建的對象來完成。 3.2 IP地址和端口 因特網服務器可以被認為是一組套接字類,它們提供了一般稱為服務的附加功能。服務的例子有:電子郵件、遠程登錄的Telnet、和通過網絡傳輸文件的文件傳輸協議(FTP)。每種服務都與一個端口相聯系。端口是一個數值地址,通過它來處理服務請求(就象請求Web頁一樣)。 TCP協議需要兩個數據項:IP地址和端口號。因此,當鍵入http://www.jinnuo.com時,你是如何進入金諾的主頁呢? 因特網協議(IP)提供每一項網絡設備。這些設備都帶有一個稱為IP地址的邏輯地址。由因特網協議提供的IP地址具有特定的形式。每個IP地址都是32位的數值,表示4個范圍在0到255之間的8位數值金諾已經注冊了它的名字,分配給http://www.jinnuo.com的IP地址為192.168.0.110。 注意:域名服務或DNS服務是將http://www.jinnuo.com翻譯成192.168.0.110的服務。這使你可以鍵入http://www.jinnuo.com而不必記住IP地址。想象一下,怎么可能記住所有需要訪問的站點的IP地址!有趣的是一個網絡名可以映射到許多IP地址。對于經常訪問的站點可能需要這一功能,因為這些站點容納大量的信息,并需要多個IP地址來提供業務服務。例如:192.168.0.110的實際的內部名稱為http://www.jinnuo.com。DNS可以將分配給jinnuo Ltd.的一系列IP地址翻譯成http://www.jinnuo.com。 如果沒有指明端口號,則使用服務文件中服務器的端口。每種協議有一個缺省的端口號,在端口號未指明時使用該缺省端口號。 端口號 應用 21 FTP.傳輸文件 23 Telnet.提供遠程登錄 25 SMTP.傳遞郵件信息 67 BOOTP.在啟動時提供配置情況 80 HTTP.傳輸Web頁 109 POP.使用戶能訪問遠程系統中的郵箱 讓我們再來看一下URL:http://www.jinnuo.com URL的第一部分(http)意味著你正在使用超文本傳輸協議(HTTP),該協議處理Web文檔。如果沒有指明文件,大多數的Web服務器會取一個叫index.html文件。因此,IP地址和端口既可以通過明確指出URL各部分來決定,也可以由缺省值決定。 4 創建Socket客戶 我們將在本部分討論的示例將闡明在 Java 代碼中如何使用 Socket 和 ServerSocket。客戶機用 Socket 連接到服務器。服務器用 ServerSocket 在端口 1001 偵聽??蛻魴C請求服務器 C: 驅動器上的文件內容。 創建 RemoteFileClient 類 import java.io.*; import java.net.*; public class RemoteFileClient { protected BufferedReader socketReader; protected PrintWriter socketWriter; protected String hostIp; protected int hostPort; //構造方法 public RemoteFileClient(String hostIp, int hostPort) { this.hostIp = hostIp; this.hostPort=hostPort; } //向服務器請求文件的內容 public String getFile(String fileNameToGet) { StringBuffer fileLines = new StringBuffer(); try { socketWriter.println(fileNameToGet); socketWriter.flush(); String line = null; while((line=socketReader.readLine())!=null) fileLines.append(line+"\n"); } catch(IOException e) { System.out.println("Error reading from file: "+fileNameToGet); } return fileLines.toString(); } //連接到遠程服務器 public void setUpConnection() { try { Socket client = new Socket(hostIp,hostPort); socketReader = new BufferedReader(new InputStreamReader(client.getInputStream())); socketWriter = new PrintWriter(client.getOutputStream()); } catch(UnknownHostException e) { System.out.println("Error1 setting up socket connection: unknown host at "+hostIp+":"+hostPort); } catch(IOException e) { System.out.println("Error2 setting up socket connection: "+e); } } //斷開遠程服務器 public void tearDownConnection() { try { socketWriter.close(); socketReader.close(); }catch(IOException e) { System.out.println("Error tearing down socket connection: "+e); } } public static void main(String args[]) { RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1",1001); remoteFileClient.setUpConnection(); StringBuffer fileContents = new StringBuffer(); fileContents.append(remoteFileClient.getFile("RemoteFileServer.java")); //remoteFileClient.tearDownConnection(); System.out.println(fileContents); } } 首先我們導入 java.net 和 java.io。java.net 包為您提供您需要的套接字工具。java.io 包為您提供對流進行讀寫的工具,這是您與 TCP 套接字通信的唯一途徑。 我們給我們的類實例變量以支持對套接字流的讀寫和存儲我們將連接到的遠程主機的詳細信息。 我們類的構造器有兩個參數:遠程主機的IP地址和端口號各一個,而且構造器將它們賦給實例變量。 我們的類有一個 main() 方法和三個其它方法。稍后我們將探究這些方法的細節?,F在您只需知道 setUpConnection() 將連接到遠程服務器,getFile() 將向遠程服務器請求 fileNameToGet 的內容以及 tearDownConnection() 將從遠程服務器上斷開。 實現 main() 這里我們實現 main() 方法,它將創建 RemoteFileClient 并用它來獲取遠程文件的內容,然后打印結果。main() 方法用主機的 IP 地址和端口號實例化一個新 RemoteFileClient(客戶機)。然后,我們告訴客戶機建立一個到主機的連接。接著,我們告訴客戶機獲取主機上一個指定文件的內容。最后,我們告訴客戶機斷開它到主機的連接。我們把文件內容打印到控制臺,只是為了證明一切都是按計劃進行的。 建立連接 這里我們實現 setUpConnection() 方法,它將創建我們的 Socket 并讓我們訪問該套接字的流: public void setUpConnection() { try { Socket client = new Socket(hostIp,hostPort); socketReader = new BufferedReader(new InputStreamReader(client.getInputStream())); socketWriter = new PrintWriter(client.getOutputStream()); } catch(UnknownHostException e) { System.out.println("Error1 setting up socket connection: unknown host at "+hostIp+":"+hostPort); } catch(IOException e) { System.out.println("Error2 setting up socket connection: "+e); } } setUpConnection() 方法用主機的 IP 地址和端口號創建一個 Socket: Socket client = new Socket(hostIp, hostPort); 我們把 Socket 的 InputStream 包裝進 BufferedReader 以使我們能夠讀取流的行。然后,我們把 Socket 的 OutputStream 包裝進 PrintWriter 以使我們能夠發送文件請求到服務器: socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));socketWriter = new PrintWriter(client.getOutputStream()); 請記住我們的客戶機和服務器只是來回傳送字節。客戶機和服務器都必須知道另一方即將發送的是什么以使它們能夠作出適當的響應。在這個案例中,服務器知道我們將發送一條有效的文件路徑。 當您實例化一個 Socket 時,將拋出 UnknownHostException。這里我們不特別處理它,但我們打印一些信息到控制臺以告訴我們發生了什么錯誤。同樣地,當我們試圖獲取 Socket 的 InputStream 或 OutputStream 時,如果拋出了一個一般 IOException,我們也打印一些信息到控制臺。 與主機交談 這里我們實現 getFile() 方法,它將告訴服務器我們想要什么文件并在服務器傳回其內容時接收該內容。 public String getFile(String fileNameToGet) { StringBuffer fileLines = new StringBuffer(); try { socketWriter.println(fileNameToGet); socketWriter.flush(); String line = null; while((line=socketReader.readLine())!=null) fileLines.append(line+"\n"); } catch(IOException e) { System.out.println("Error reading from file: "+fileNameToGet); } return fileLines.toString(); } 對getFile()方法的調用要求一個有效的文件路徑String。它首先創建名為fileLines的 StringBuffer,fileLines 用于存儲我們讀自服務器上的文件的每一行。 StringBuffer fileLines = new StringBuffer(); 在 try{}catch{} 塊中,我們用 PrintWriter 把請求發送到主機,PrintWriter 是我們在創建連接期間建立的。 socketWriter.println(fileNameToGet); socketWriter.flush(); 請注意這里我們是 flush() 該 PrintWriter,而不是關閉它。這迫使數據被發送到服務器而不關閉 Socket。 一旦我們已經寫到 Socket,我們就希望有一些響應。我們不得不在 Socket 的 InputStream 上等待它,我們通過在 while 循環中調用 BufferedReader 上的 readLine() 來達到這個目的。我們把每一個返回行附加到 fileLines StringBuffer(帶有一個換行符以保護行): String line = null; while((line=socketReader.readLine())!=null) fileLines.append(line+"\n"); 斷開連接 這里我們實現 tearDownConnection() 方法,它將在我們使用完畢連接后負責“清除”。tearDownConnection()方法只是分別關閉我們在Socket的InputStream和OutputStream上創建的 BufferedReader和PrintWriter。這樣做會關閉我們從Socket獲取的底層流,所以我們必須捕捉可能的 IOException。 總結一下客戶機 我們的類研究完了。在我們繼續往前討論服務器端的情況之前,讓我們回顧一下創建和使用 Socket 的步驟: 1. 用您想連接的機器的 IP 地址和端口實例化 Socket(如有問題則拋出 Exception)。 2. 獲取 Socket 上的流以進行讀寫。 3. 把流包裝進 BufferedReader/PrintWriter 的實例,如果這樣做能使事情更簡單的話。 4. 對 Socket 進行讀寫。 5. 關閉打開的流。 5 創建服務器Socket 創建 RemoteFileServer 類 import java.io.*; import java.net.*; public class RemoteFileServer { int listenPort; public RemoteFileServer(int listenPort) { this.listenPort=listenPort; } //允許客戶機連接到服務器,等待客戶機請求 public void acceptConnections() { try { ServerSocket server = new ServerSocket(listenPort); Socket incomingConnection = null; while(true) { incomingConnection = server.accept(); handleConnection(incomingConnection); } } catch(BindException e) { System.out.println("Unable to bind to port "+listenPort); } catch(IOException e) { System.out.println("Unable to instantiate a ServerSocket on port: "+listenPort); } } //與客戶機Socket交互以將客戶機所請求的文件的內容發送到客戶機 public void handleConnection(Socket incomingConnection) { try { OutputStream outputToSocket = incomingConnection.getOutputStream(); InputStream inputFromSocket = incomingConnection.getInputStream(); BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputFromSocket)); FileReader fileReader = new FileReader(new File(streamReader.readLine())); BufferedReader bufferedFileReader = new BufferedReader(fileReader); PrintWriter streamWriter = new PrintWriter(incomingConnection.getOutputStream()); String line = null; while((line=bufferedFileReader.readLine())!=null){ streamWriter.println(line); } fileReader.close(); streamWriter.close(); streamReader.close(); } catch(Exception e) { System.out.println("Error handling a client: "+e); e.printStackTrace(); } } public static void main(String args[]) { RemoteFileServer server = new RemoteFileServer(1001); server.acceptConnections(); } } 跟客戶機中一樣,我們首先導入java.net的java.io。接著,我們給我們的類一個實例變量以保存端口,我們從該端口偵聽進入的連接。缺省情況下,端口是1001。 我們的類有一個main()方法和兩個其它方法。稍后我們將探究這些方法的細節?,F在您只需知道acceptConnections()將允許客戶機連接到服務器以及handleConnection()與客戶機Socket交互以將您所請求的文件的內容發送到客戶機。 實現 main() 這里我們實現main()方法,它將創建RemoteFileServer并告訴它接受連接:服務器端的main()方法中,我們實例化一個新RemoteFileServer,它將在偵聽端口(1001)上偵聽進入的連接請求。然后我們調用acceptConnections()來告訴該server進行偵聽。 接受連接 這里我們實現 acceptConnections() 方法,它將創建一個 ServerSocket 并等待連接請求: public void acceptConnections() { try { ServerSocket server = new ServerSocket(listenPort); Socket incomingConnection = null; while(true) { incomingConnection = server.accept(); handleConnection(incomingConnection); } } catch(BindException e) { System.out.println("Unable to bind to port "+listenPort); } catch(IOException e) { System.out.println("Unable to instantiate a ServerSocket on port: "+listenPort); } } acceptConnections()用欲偵聽的端口號來創建ServerSocket。然后我們通過調用該ServerSocket的accept()來告訴它開始偵聽。accept()方法將造成阻塞直到來了一個連接請求。此時,accept()返回一個新的Socket,這個Socket綁定到服務器上一個隨機指定的端口,返回的Socket被傳遞給handleConnection()。請注意我們在一個無限循環中處理對連接的接受。這里不支持任何關機。 無論何時如果您創建了一個無法綁定到指定端口(可能是因為別的什么控制了該端口)的 ServerSocket,Java代碼都將拋出一個錯誤。所以這里我們必須捕捉可能的BindException。就跟在客戶機端上時一樣,我們必須捕捉IOException,當我們試圖在ServerSocket上接受連接時,它就會被拋出。請注意,您可以通過用毫秒數調用setSoTimeout()來為accept()調用設置超時,以避免實際長時間的等待。調用setSoTimeout()將使accept()經過指定占用時間后拋出IOException。 處理連接 這里我們實現handleConnection()方法,它將用連接的流來接收輸入和寫輸出: public void handleConnection(Socket incomingConnection) { try { OutputStream outputToSocket = incomingConnection.getOutputStream(); InputStream inputFromSocket = incomingConnection.getInputStream(); BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputFromSocket)); FileReader fileReader = new FileReader(new File(streamReader.readLine())); BufferedReader bufferedFileReader = new BufferedReader(fileReader); PrintWriter streamWriter = new PrintWriter(incomingConnection.getOutputStream()); String line = null; while((line=bufferedFileReader.readLine())!=null){ streamWriter.println(line); } fileReader.close(); streamWriter.close(); streamReader.close(); } catch(Exception e) { System.out.println("Error handling a client: "+e); e.printStackTrace(); } } 跟在客戶機中一樣,我們用getOutputStream()和getInputStream()來獲取與我們剛創建的Socket相關聯的流。跟在客戶機端一樣,我們把InputStream包裝進BufferedReader,把OutputStream包裝進PrintWriter。在服務器端上,我們需要添加一些代碼,用來讀取目標文件和把內容逐行發送到客戶機。這里是重要的代碼: FileReader fileReader = new FileReader(new File(streamReader.readLine())); BufferedReader bufferedFileReader = new BufferedReader(fileReader); String line = null; while((line=bufferedFileReader.readLine())!=null) { streamWriter.println(line); } 這些代碼值得詳細解釋。讓我們一點一點來看: FileReader fileReader = new FileReader(new File(streamReader.readLine())); 首先,我們使用Socket 的InputStream的BufferedReader。我們應該獲取一條有效的文件路徑,所以我們用該路徑名構造一個新File。我們創建一個新FileReader來處理讀文件的操作。 BufferedReader bufferedFileReader = new BufferedReader(fileReader); 這里我們把FileReader包裝進BufferedReader以使我們能夠逐行地讀該文件。 接著,我們調用BufferedReader的readLine()。這個調用將造成阻塞直到有字節到來。我們獲取一些字節之后就把它們放到本地的line變量中,然后再寫出到客戶機上。完成讀寫操作之后,我們就關閉打開的流。 請注意我們在完成從Socket的讀操作之后關閉streamWriter和streamReader。您或許會問我們為什么不在讀取文件名之后立刻關閉streamReader。原因是當您這樣做時,您的客戶機將不會獲取任何數據。如果您在關閉streamWriter之前關閉streamReader,則您可以往Socket寫任何東西,但卻沒有任何數據能通過通道(通道被關閉了)。 總結一下服務器 在我們接著討論另一個更實際的示例之前,讓我們回顧一下創建和使用ServerSocket的步驟: 1. 用一個您想讓它偵聽傳入客戶機連接的端口來實例化一個ServerSocket(如有問題則拋出 Exception)。 2. 調用ServerSocket的accept()以在等待連接期間造成阻塞。 3. 獲取位于該底層Socket的流以進行讀寫操作。 4. 按使事情簡單化的原則包裝流。 5. 對Socket進行讀寫。 6. 關閉打開的流(并請記住,永遠不要在關閉Writer之前關閉Reader)。 |
The org.snmp4j
classes are capable of creating, sending, and receiving SNMPv1/v2c/v3 messages. A SNMP message is composed of its message header and its PDU payload. This package contains three main groups of classes and interfaces:
The following UML package diagram illustrates the dependencies between the packages of the core SNMP4J API. Users of the API normally only need to use the org.snmp4j
and the org.snmp4j.smi
packages directly.
The following UML class diagram shows the most important classes of the org.snmp4j package and their relationships (relationships to other packages are not shown):.
To exchange a SNMP message with a remote system, that system has to be identified, retransmission, and timeout policy information about the message exchange has to be specified. A remote system is specified with SNMP4J by creating a Target
instance appropriate for the SNMP protocol to be used.
CommunityTarget
has to be used which provides community information in addition to the address, retransmission, and timeout policy information defined by the Target
interface.
UserTarget
has to be used instead. It extends the SecureTarget
abstract class and provides the following User Based Security Model (USM) user information: security name, security level, security model (i.e. USM), and authoritative engine ID. A SNMP message consists of the message's payload, the SNMP Protocol Data Unit (PDU) and a message header. Simplified said, in SNMP4J the message header information is represented by Target
instances and the PDU is represented by one of the following classes:
PDUv1
(SNMPv1)
PDU
(SNMPv2c)
ScopedPDU
(SNMPv3) PDU
instance and a Target
instance have to be created.
import org.snmp4j.PDU; import org.snmp4j.smi.*; ... PDU pdu = new PDU(); pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.1.1"))); // sysDescr pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.2.1"))); // ifNumber pdu.setType(PDU.GETNEXT); ...
import org.snmp4j.ScopedPDU; import org.snmp4j.smi.*; ... ScopedPDU pdu = new ScopedPDU(); pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.2.1"))); // ifNumber pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.2.2.1.10"))); // ifInOctets pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.2.2.1.16"))); // ifOutOctets pdu.setType(PDU.GETBULK); pdu.setMaxRepetitions(50); // Get ifNumber only once pdu.setNonRepeaters(1); // set context non-default context (default context does not need to be set) pdu.setContextName(new OctetString("subSystemContextA")); // set non-default context engine ID (to use targets authoritative engine ID // use an empty (size == 0) octet string) pdu.setContextEngineID(OctetString.fromHexString("80:00:13:70:c0:a8:01:0d")); ...
import org.snmp4j.PDUv1; ... PDUv1 pdu = new PDUv1(); pdu.setType(PDU.V1TRAP); pdu.setGenericTrap(PDUv1.COLDSTART); ...
import org.snmp4j.ScopedPDU; ... ScopedPDU pdu = new ScopedPDU(); pdu.setType(PDU.INFORM); // sysUpTime long sysUpTime = (System.currentTimeMillis() - startTime) / 10; pdu.add(new VariableBinding(SnmpConstants.sysUpTime, new TimeTicks(sysUpTime))); pdu.add(new VariableBinding(SnmpConstants.snmpTrapOID, SnmpConstants.linkDown)); // payload pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.2.2.1.1"+downIndex), new Integer32(downIndex))); ...
CommunityTarget target = new CommunityTarget(); target.setCommunity(new OctetString("public")); target.setAddress(targetAddress); target.setVersion(SnmpConstants.version1);
UserTarget target = new UserTarget(); target.setAddress(targetAddress); target.setRetries(1); // set timeout to 500 milliseconds -> 2*500ms = 1s total timeout target.setTimeout(500); target.setVersion(SnmpConstants.version3); target.setSecurityLevel(SecurityLevel.AUTH_PRIV); target.setSecurityName(new OctetString("MD5DES"));
SNMP message are sent with SNMP4J by using a instance of the SNMP Session
interface. The default implementation of this interface is the Snmp
class.
To setup a Snmp
instance it is sufficient to call its constructor with a TransportMapping
instance. The transport mapping is used by the SNMP session to send (and receive) SNMP message to a remote systems by using a transport protocol, for example the User Datagram Protocol (UDP).
A SNMP4J Snmp
instance supports SNMP v1, v2c, and v3 by default. By sub-classing Snmp
other combinations of those SNMP protocol versions can be supported.
With SNMP4J, SNMP messages can be sent synchronously (blocking) and asynchronously (non-blocking). The Snmp
class does not use an internal thread to process responses on asynchronous and synchronous requests. Nevertheless it uses the receiver threads of the transport mappings to process responses.
Asynchronous responses are returned by calling a callback method on an object instance that implements the ResponseListener
interface. The callback is carried out on behalf of the transport mapping thread that received the response packet from the wire. Thus, if the called method blocks, the delivery of synchronous and asynchronous messages received on the listen port of that transport mapping will be also blocked. Other transport mapping will not be affected. Blocking can be avoided by either using synchronous messages only or by decoupling the processing within the callback method.
import org.snmp4j.*; ... Snmp snmp = new Snmp(new DefaultUdpTransportMapping()); ... ResponseEvent response = snmp.send(requestPDU, target); if (response.getResponse() == null) { // request timed out ... } else { System.out.println("Received response from: "+ response.getPeerAddress()); // dump response PDU System.out.println(response.getResponse().toString()); }
import org.snmp4j.*; import org.snmp4j.event.*; ... Snmp snmp = new Snmp(new DefaultUdpTransportMapping()); ... ResponseListener listener = new ResponseListener() { public void onResponse(ResponseEvent event) { PDU response = event.getResponse(); PDU request = event.getRequest(); if (response == null) { System.out.println("Request "+request+" timed out"); } else { System.out.println("Received response "+response+" on request "+ request); } }; snmp.sendPDU(request, target, null, listener); ...
SNMP4J receives SNMP messages through the listen port of transport mappings. In order to be able to receive responses or requests, that port needs to be set into listen mode. This has to be done by calling the listen()
method of the TransportMapping
instance to start the transport mappings internal listen thread. The internal thread is stopped and the listen port is closed by calling the close()
method on the TransportMapping
instance or the associated Snmp
instance.
The transport mapping just receives the SNMP mesage as a stream of bytes and forwards the message to associated MessageDispatcher
instances. By default, SNMP4J uses one instance of the MessageDispatcherImpl
class for decoding and dispatching incoming messages. That instance is created and used internally by the Snmp
class.
The Snmp
class processes responses to outstanding requests and forwards PDUs of other SNMP messages to registered CommandResponder
listener instances. To receive SNMP messages it is thus sufficient to
TransportMapping
and initialize its listen port by calling TransportMapping.listen()
.
Snmp
instance with the above TransportMapping
.
CommandResponder
interface and register it with the Snmp
instance by calling Snmp.addCommandResponder(CommandResponder)
. When a unhandled SNMP message (thus a SNMP message where no corresponding outstanding request exists) is received, then the processPdu(CommandResponderEvent)
method of the CommandResponder
will be called with the decoded PDU and additional information about the received SNMP message provided by the message processing model that has decoded the SNMP message.
import org.snmp4j.*; import org.snmp4j.smi.*; import org.snmp4j.mp.SnmpConstants; ... TransportMapping transport = new DefaultUdpTransportMapping(new UdpAddress("0.0.0.0/161")); Snmp snmp = new Snmp(transport); if (version == SnmpConstants.version3) { byte[] localEngineID = ((MPv3)snmp.getMessageProcessingModel(MessageProcessingModel.MPv3)).createLocalEngineID(); USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(localEngineID), 0); SecurityModels.getInstance().addSecurityModel(usm); snmp.setLocalEngine(localEngineID, 0, 0); // Add the configured user to the USM ... } snmp.addCommandResponder(this); transport.listen(); ... public synchronized void processPdu(CommandResponderEvent e) { PDU command = e.getPdu(); if (command != null) { ... } }
以下是 org.snmp4j.smi 包下的說明:
Provides classes for the representation of SMIv1/v2 data types (which also includes some basic ASN.1 primitive data types).The
org.snmp4j.smi
classes are capable of BER encoding and decoding themself to/from a byte stream. In addition, the SMI data type classes provide convenient functions for manipulating their content.The
VariantVariable
is a special class that can be used in command responder applications to intercept access to a SMI value.Variable Binding Examples
import org.snmp4j.smi.*; ... VariableBinding vb = new VariableBinding(new OID("1.3.6.1.2.1.1.4.0")); vb.setValue(new OctetString("SNMP4J Text")); ... vb = new VariableBinding(); vb.setOid(new OID(new int[] { 1,3,6,1,2,1,1,2,0 })); ... vb = new VariableBinding(vb.getOid(), new IpAddress("255.255.255.255")); ... vb = new VariableBinding(vb.getOid(), new Gauge32(2^32-1)); int syntax = vb.getSyntax(); if (syntax != SMIConstants.SYNTAX_GAUGE32) { // never reached } else { long value = ((UnsignedInteger32)vb.getValue()).getValue(); System.out.println(vb.getOid() + " = " + value); // prints: 1.3.6.1.2.1.1.2.0 = 4294967295 } ...The following UML class diagram shows the most important classes of the
org.snmp4j.smi
package and their relationships (relationships to other packages are not shown):
以下是 org.snmp.asn1 包中的說明
Provides classes and interfaces for the mapping between Abstract Syntax Notation One (ASN.1) formatted values and their transfer syntax according to the Basic Encoding Rules (BER).The
org.snmp4j.asn1
classes are capable of serializing of ASN.1 formatted values into a byte stream and deserializing the same from a byte stream. There are three groups of classes/interfaces in this package:
BER
class implements the BER serialization and deserialization by providing static methods for encoding/decoding of primitive ASN.1 and Structure of Management Information (SMI) data types.
BERSerializable
interface provides a common interface for all objects that are (de)serializable according to the Basic Encoding Rules (BER).
BERInputStream
and the BEROutputStream
provide optimized implementations for the serialization and deserialization of the InputStream
and OutputStream
abstract classes.
The following UML class diagram shows the most important classes of the org.snmp4j.asn1
package and their relationships (relationships to other packages are not shown):
以下是 org.snmp4j.mp 包中的說明
Provides classes and interfaces for the SNMP message processing.The
org.snmp4j.mp
classes provide services to process SNMP messages. The services provided are defined in theMessageProcessingModel
interface and include the following:
This interface is implemented by the message processing model classes for the SNMP versions 1, v2c, and v3: MPv1
, MPv2c
, and MPv3
.
MessageDispatcherImpl
chooses which message processing model it uses to process an outgoing or incoming SNMP message based on the SNMP version of the message. The SNMP version is either extracted from the message header (incoming message) or from the Target
instance associated with the outgoing PDU (ougoing message).
To be able to match requests and responses SNMP uses request IDs. Since request IDs are created by the command generator, the request IDs are unique within such a command generator only. SNMP4J therefore has to abstract from request IDs and uses PduHandle
instances instead.
If a PDU
is processed for sending by the SNMP4J MessageDispatcherImpl
and the PDU's request ID is set to 0, then a SNMP4J application wide unique ID is generated and set as request ID of the supplied PDU. In any case, the PDU's request ID will be used as transaction ID of the outgoing message. The transaction ID identifies a messages PduHandle
.
If a PDU
is received by the SNMP4J MessageDispatcherImpl
a unique transaction ID is generated so that command responders as well as the message processing model can match requests and responses.
The following UML class diagram shows the most important classes of the org.snmp4j.mp
package and their relationships (relationships to other packages are not shown):
以下是 org.snmp4j.transport 包中的說明
Provides transport protocol mappings for SNMP.The
org.snmp4j.transport
classes are capable of sending and receiving byte messages to and from a network using transport mapping specific transport protocol. All SNMP4J transport mappings have to implement theorg.snmp4j.TransportMapping
interface. SNMP4J supports two transport mappings for the transport protocols UDP and TCP:
DefaultUdpTransportMapping
class.
DefaultTcpTransportMapping
using the java.nio
package. Additional transport mappings can be easily added. It is sufficient to implement the org.snmp4j.TransportMapping
interface and add an instance of that class to the Snmp
(or MessageDispatcher
) object. To be able to lookup a transport mapping by an Address
class via the TransportMappings
(as Snmp
does for notification listeners), a transport mapping has to be registered in a transport mapping registration file. The default file is transports.properties
in the org.snmp4j.transport
package. To use a different file, set the system property org.snmp4j.transportMappings
.
Connection-oriented transport mappings like TCP should implement the ConnectionOrientedTransportMapping
interface to support MessageLengthDecoder
and TransportStateListener
.
The following UML class diagram shows the classes of the org.snmp4j.transport
package and their relationships (relationships to other packages are not shown):
以下是 org.snmp4j.util 包中的說明
Contains table retrieval utilities and multi-threading support classes as well as miscellaneous utility classes.The
org.snmp4j.util
contains the following groups of classes:
TableUtils
can be used to asynchronously retrieve table data effeciently row by row.
MultiThreadedMessageDispatcher
implements the MessageDispatcher
interface and uses the MessageDispatcherImpl
class to dispatch incoming message using the threads of a ThreadPool
. The following UML class diagram shows the classes of the org.snmp4j.util
package and their relationships (relationships to other packages are not shown):