2012年10月29日 星期一

OpenID

I may need to do some programming on OpenID. Here is the list that might be helpful:

  1. http://www.ibm.com/developerworks/java/library/j-openid/index.html
  2. https://developers.google.com/accounts/docs/OpenID
  3. http://www.died.tw/2010/07/google-openid.html
  4. https://code.google.com/p/google-api-java-client/wiki/OAuth2
  5. http://code.google.com/p/google-api-javascript-client/
  6. An OpenID consumer for Attribute Exchange with Google
  7. Using a Dynamic Host Page for Authentication and Initialization 
  8. New JavaScript SDK & OAuth 2.0 based FBConnect Tutorial

2012年10月28日 星期日

最新一期: 【封面故事】iThome 2012年 IT人大調查

過去一直訂閱 iTHome 的電子報,也有閱讀 iTHome 的習慣。最近該刊完成了

【封面故事】iThome 2012年 IT人大調查

內容完整,非常值得 MIS 的學生以及畢業生看看!

2012年10月26日 星期五

文章大概搬完了

今天已經大概把所有的文章從原來的網站搬過來了,總算可以歇口氣。今後,先暫時靠 Google 的網站了,未來再視狀況調整。

Remote Method Invocation 入門


Remote Method Invocation 入門

Many examples in this document are adapted from Java: How To Program (3rd Ed.), written by Deitel and Deitel, and Thinking in Java (2nd Edition), written by Bruce Eckel. All examples are solely used for educational purposes. Hopefully, I am not violating any copyright issue here. If so, please do email me.
Please install JDK 1.3.1_02 or later with Java Plugin to view this page. Also, this page is best viewed with browsers (for examples, Mozilla 0.99 or later, IE 6.x or later) with CSS2 support. This document is provided as is. You are welcomed to use it for non-commercial purpose.

Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

目錄

  1. 前言
  2. Hello World -- RMI
  3. RMI and DB
  4. 查詢 services 清單
  5. RmiJdbc
  6. RMI 的其他議題


CSS 入門


Cascading Style Sheet

看其他教材


串接式的排版樣本(Cascading Style Sheet)是目前最常見的排版 語法,幾乎所有的部落格網站都大量使用 CSS。除了 HTML 之外,還發展 CSS 的主要 目旳在於將文件內容與呈現方式分開,這將使得網頁 的管理比較容易,尤其在開發網頁為介面的資訊系統時特別具有彈性。CSS 目前分成 兩個版本,分別為 CSS1 與 CSS2。 這裡是 W3Schools 所製作的一份 CSS Tutorial,不排斥英文的讀者,我們強烈建議去看看!

HTML Frame

HTML Frame

This document is copyrighted and provided as is. You are welcomed to use it for non-commercial purpose.


HTML 支援的框架總共有兩種,一種是 frameset + frame,另一種是 iframe, 他們的差別以及使用時機,我們會分別描述。首先,我們說明的是 frameset + frame。

更多 HTML table 範例

  1. 沒有邊框的表格
    Eric
  2. 有邊框的表格
    Eric
  3. 缺少部分資料的表格 (1)
    1,11,2
    2,12,22,3
  4. 缺少部分資料的表格 (2)
    1,11,2&nbsp
    2,12,22,3
  5. 設定表格的寬度
    1,11,2&nbsp
    2,12,22,3
  6. 設定表格的 ALIGN (LEFT*, CENTER, RIGHT) 以及 VALIGN (TOP, MIDDLE*, BOTTOM)
    1,11,2&nbsp
    2,12,22,3
  7. 設定表格的欄位寬度
    1,11,2&nbsp
    2,12,22,3
  8. 幫表格設定背景顏色 (bgcolor 可套用在所有表格的標籤內)
    1,11,2&nbsp
    2,12,22,3
  9. 跨欄(colspan)和跨列(rowspan)的表格
    1,1Eric
    2,12,22,3
  10. 表格內包含其他表格
    1,1Eric
    2,12,22,3
    家
    2,12,22,3
  11. 設定標題的表格
    Header
    1,1Eric
    2,12,22,3










HTML Table



HTML Table

This document is copyrighted and provided as is. You are welcomed to use it for non-commercial purpose.
看其他教材

  • Table 的組成元件:
    1. <table> </table>: 宣告表格
    2. <caption> </caption>: 表格標題 (Optional)
    3. <th> </th>: 表格欄位標題 (Optional)
    4. <tr> </tr>: 表格列
    5. <td> </td>: 表格欄
  • 第一個表格:
    老呂
    <table>
    <tr>
    <td>老呂</td>
    </tr>
    </table>
    
  • 比較像表格的表格:
    老呂
    老黃
    <table border="2">
    <tr>
    <td>老呂</td><td>&nbsp;</td><td>&nbsp;</td>
    </tr>
    <tr>
    <td>&nbsp;</td><td>老黃</td><td></td>
    </tr>
    <table>

HTML 表格範例


Form Pages

The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼

在這個簡單的網頁中,我們介紹互動式網頁中最重要的 HTML 元件之一 -- form。 雖然在範例中,我們利用了簡單的 shell 以及 Perl 程式來當作後端的處理程式, 其 URL 可以用來呼叫各種語言寫成的後端程式 -- 例如,php、JSP、Java servlets 等。
  1. GET/POST 與文字欄位
    • HTML form 的基本格式,藉由 mailto 的觸發來執行程式。
      告訴我你是誰:
    • 沒有 submit 的按鈕,但是可以藉由 "Enter" 來觸發後端的程式。由於使用 GET,"Enter" 後請注意網址欄的內容 -- ? 以及 data= 等資料。
      請輸入一段文字 (GET):
    • 在這個範例,還是請注意網址欄的內容,請留意兩個欄位的資料 -- data1 和 data2 -- 之間的分隔符號。另外,也可以試試看 reset 的功能!
      Shell 版
      請輸入第一段文字 (GET):
      請輸入第二段文字 (GET):
    • Perl 版
      請輸入第一段文字 (GET):
      請輸入第二段文字 (GET):
    • 這個範例不再使用 GET,而改以 POST 的方式進行,還是請注意網址欄的內容。 。從這個範例的執行,可以觀察出 GET 和 POST 在網址欄內容的差異。
      Shell 版
      請輸入一段文字 (POST):
    • Perl 版
      請輸入第一段文字 (POST):
      請輸入第二段文字 (POST):  




Java Bluetooth 程式開發入門


Java Bluetooth 程式開發入門

This document is provided as is. Use it at your own risks. You are, however, welcomed to use it for educational, non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟

請勿轉貼
看其他教材

目錄

  1. 藍芽簡介
  2. 開發環境簡介
  3. 程式開發
    1. LocalDevice 的查詢
    2. RemoteDevice 的查詢
    3. 利用 RFCOMM 來交換訊息
    4. 利用 L2CAP 來交換訊息
    5. 利用 OBEX 來傳送檔案

藍芽簡介

Under Construction

開發環境簡介

這一節的說明主要是針對 Linux 的環境,或者更嚴格的說是我還沒有發現 Blue Cove 以前的測試環境。現在 Windows XP SP2 之後已經支援藍芽的驅動程式, 再加上 Blue Cove 以及藍芽設備,你就可以開始開發了。不過,我還是把 以下的說明留下來當參考用。
  1. 兩部安裝了 Linux 的電腦,請確定這個版本的 Linux 內建支援 BlueZ。 我們用的是 Fedora Core 1,你也可以參考 窮人的 Java Bluetooth 開發環境之建置 來建置你的開發環境。
  2. 依照 BlueZ 硬體相容清單 去 採購你的藍芽裝置. 我用的是別人幫我買的 X-Micro 的 Bluetooth USB Dongle. 雖然這個設備並未出現在相容清單內,但是目前 為止使用上都還蠻順利的。你可以在環境架好了以後,再去購買,如果發現不相容 可以馬上退貨。
  3. Linux 環境安裝有 J2SDK 1.4.x (or later) 以及 Rococo Software's Impronto Developer Kit for Linux.
  4. 安裝至少 bluez-libs 和 bluez-utils 套件。
  5. 請在兩部電腦上都利用以下指令來確定你的環境是正確的(# 為註解)
    hciconfig -a                    #來確定你的藍芽裝置是正常運作的
    hcitool scan                    #來確定遠端的藍芽裝置是正常運作的
                                    #假設遠端的藍芽裝置是 00:02:72:01:83:E0
    hcitool info 00:02:72:01:83:E0  #來確定兩部藍芽裝置可以互相連線的
    
  6. 注意: 我發現如果 hcitool info 00:02:72:01:83:E0 出現 Input/Output Error,這可能是你的藍芽裝置的設定需求是要求 auth and encrypt,而 hcitool 是以 noauth and noencrypt 的方式來進行 (跟我們以下的範例程式相同),你可以用以下的指令來取消設定
    hciconfig hci0 noauth
    hciconfig hci0 noencrypt
    
    如果要永久的更改,你必須把 /etc/bluetooth/hcid.conf
    # Authentication and Encryption
    auth enable;
    encrypt enable;
    
    改成
    # Authentication and Encryption
    auth disable;
    encrypt disable;
    
    必且執行 /etc/init.d/bluetooth restart.

程式開發

  1. LocalDevice 的查詢
    1. LocalInfo.java
      import java.io.*;
      import javax.bluetooth.*;
      
      public class LocalInfo
      {
        public static void main(String args[]) throws BluetoothStateException {
          LocalDevice localdev = null;
      
          try {
            localdev = LocalDevice.getLocalDevice();
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: cannot access local device");
            System.exit(1);
          }
      
          System.out.println("a local bluetooth device is found:");
          System.out.println("   Name: " + localdev.getFriendlyName());
          System.out.println("Address: " + localdev.getBluetoothAddress());
      
          System.out.println("\nIts device classes are:");
          DeviceClass devcla = localdev.getDeviceClass();
          System.out.println(devcla.toString());
          System.out.println("Service Class: " + devcla.getServiceClasses());
          System.out.println("Major Device Class: " + devcla.getMajorDeviceClass());
          System.out.println("Minor Device Class: " + devcla.getMinorDeviceClass());
        }
      }
        
  2. RemoteDevice 的查詢
    1. RemoteInfo.java
      import java.io.*;
      import javax.bluetooth.*;
      
      public class RemoteInfo
      {
        public static void main(String args[]) throws BluetoothStateException
        {
          LocalDevice localdev = null;
          MyListener listener = new MyListener();
      
          try {
            localdev = LocalDevice.getLocalDevice();
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: cannot access local device");
            System.exit(1);
          }
      
          DiscoveryAgent agent = localdev.getDiscoveryAgent();
          try {
            agent.startInquiry(DiscoveryAgent.GIAC, listener);
          } catch (BluetoothStateException e) {
            System.out.println("Device unable to inquiry");
            System.exit(2);
          }
        }
      }
      
    2. MyListener.java
      //
      // a Lisntener class that implements DiscoveryListener
      //
      
      import java.io.*;
      import javax.bluetooth.*;
      import javax.microedition.io.*;
      
      public class MyListener implements DiscoveryListener
      {
        private RemoteDevice remote = null;
      
        // 當藍芽裝置發現遠端有藍芽裝置,就會自動呼叫(依照順序)
        // deviceDiscovered() and then inquiryCompleted()
        public void deviceDiscovered(RemoteDevice rt, DeviceClass cod) {
          remote = rt;
          System.out.println("A Remote Device Found:");
          System.out.println("Address: " + rt.getBluetoothAddress());
          try {
            System.out.println("   Name: " + rt.getFriendlyName(true));
          } catch (IOException e) {}
        }
      
        public void inquiryCompleted(int distype) {
          if(distype == INQUIRY_TERMINATED)
            System.out.println("Application Terminated");
          else if(distype == INQUIRY_COMPLETED)
            System.out.println("Inquiry Completed");
          else
            System.out.println("ERROR: Inquiry aborted");
        }
      
        public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
        }
      
        public void serviceSearchCompleted(int transID, int respCode) {
        }
      }
      
    3. 我們也可以將 RemoteInfo 和 MyListener 結合成一個 class,底下的 RemoteOne.java 說明這個做法。
      //
      // Query remote device's information.
      // Combine client and DiscoveryListener into one class
      // compare this program with RemoteInfo and MyListener
      // Advantage: main() can use queried remote device directly
      //
      
      import java.io.*;
      import javax.bluetooth.*;
      import javax.microedition.io.*;
      
      public class RemoteOne implements DiscoveryListener
      {
        public static void main(String args[]) throws BluetoothStateException
        {
          LocalDevice localdev = null;
          RemoteOne listener = new RemoteOne();
      
          try {
            localdev = LocalDevice.getLocalDevice();
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: cannot access local device");
            System.exit(1);
          }
      
          DiscoveryAgent agent = localdev.getDiscoveryAgent();
          try {
            agent.startInquiry(DiscoveryAgent.GIAC, listener);
          } catch (BluetoothStateException e) {
            System.out.println("Device unable to inquiry");
            System.exit(2);
          }
        }
      
        // implement required methods.
        private RemoteDevice remote = null;
      
        public void deviceDiscovered(RemoteDevice rt, DeviceClass cod) {
          remote = rt;
          System.out.println("A Remote Device Found:");
          System.out.println("Address: " + rt.getBluetoothAddress());
          try {
            System.out.println("   Name: " + rt.getFriendlyName(true));
          } catch (IOException e) {}
        }
      
        public void inquiryCompleted(int distype) {
          if(distype == INQUIRY_TERMINATED)
            System.out.println("Application Terminated");
          else if(distype == INQUIRY_COMPLETED)
            System.out.println("Inquiry Completed");
          else
            System.out.println("ERROR: Inquiry aborted");
        }
      
        public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
        }
      
        public void serviceSearchCompleted(int transID, int respCode) {
        }
      }
        
  3. 利用 RFCOMM 來交換訊息:RFListener.java 和 RFDateClient.java 在同一部 電腦執行,而 RFDateServ.java 再另一部電腦執行。一般來說,程式的執行過程 需要先將 server 端的 service 實作出來,然後將該 service 跟 server 登記並 發布,因此你必須先執行 RFDateServ。等到 service 發布後,你就 執行 RFDateClient 讓 client 經由 searchServices() 發現 service 並與 service 取得連接。
    1. RFListener.java
      //
      // a Lisntener class that implements DiscoveryListener
      // From a source code published in Benhui.net claimes that
      // Impronto prefers to have a Lisntener class.
      // conn: RFCOMM
      //
      
      import java.io.*;
      import javax.bluetooth.*;
      import javax.microedition.io.*;
      
      public class RFListener implements DiscoveryListener
      {
        private static RemoteDevice remote;
        private static ServiceRecord first;
      
        public RemoteDevice getDevFound() {
          return remote;
        }
      
        public void deviceDiscovered(RemoteDevice rt, DeviceClass cod) {
          remote = rt;
          System.out.println("A Remote Device Found:");
          System.out.println("Address: " + rt.getBluetoothAddress());
          try {
            System.out.println("   Name: " + rt.getFriendlyName(true));
          } catch (IOException e) {}
        }
      
        public void inquiryCompleted(int distype) {
          if(distype == INQUIRY_TERMINATED)
            System.out.println("Application Terminated");
          else if(distype == INQUIRY_COMPLETED)
            System.out.println("Inquiry Completed");
          else
            System.out.println("ERROR: Inquiry aborted");
        }
      
        // 當裝置從遠端的藍芽裝置發現該裝置上有服務的時候,會先呼叫
        // servicesDiscovered() 然後 serviceSearchCompleted()
        public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
          first = servRecord[0];
      
          // 從 service record 來取得 url
          String url = first.getConnectionURL(first.NOAUTHENTICATE_NOENCRYPT, false);
          try {
            // 與 server 建立連線
            StreamConnection conn = (StreamConnection) Connector.open(url);
      
            String message = "What time is it now?";
            InputStream is = conn.openInputStream();
            OutputStream os = conn.openOutputStream();
            byte[] rbuf = new byte[100];
            os.write(message.getBytes());
            is.read(rbuf);
            System.out.println(new String(rbuf));
            is.close();
            os.close();
            conn.close();
          } catch (Exception e) {
            System.out.println("ERROR: connection error");
          }
        }
      
        public void serviceSearchCompleted(int transID, int respCode) {
          // 依照 respCode 來了解 serviceSearch 的結果
          if(respCode == SERVICE_SEARCH_COMPLETED)
            System.out.println("Services successfully located");
          else if(respCode == SERVICE_SEARCH_TERMINATED)
            System.out.println("Service inquiry was cancelled");
          else if(respCode == SERVICE_SEARCH_DEVICE_NOT_REACHABLE)
            System.out.println("Service connection cannot be established");
          else if(respCode == SERVICE_SEARCH_NO_RECORDS)
            System.out.println("No service found");
          else
            System.out.println("ERROR: service inquiry failed");
        }
      }
        
    2. RFDateClient.java
      //
      // a Client to request Date/Time from server
      // connection: RFCOMM
      //
      
      import java.io.*;
      import javax.bluetooth.*;
      import javax.microedition.io.*;
      
      public class RFDateClient
      {
        public static void main(String args[]) throws BluetoothStateException
        {
          LocalDevice localdev = null;
          RFListener listener = new RFListener();
      
          try {
            localdev = LocalDevice.getLocalDevice();
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: cannot access local device");
            System.exit(1);
          }
      
          DiscoveryAgent agent = localdev.getDiscoveryAgent();
          try {
            // startInquiry() is non-blocking.
            agent.startInquiry(DiscoveryAgent.GIAC, listener);
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: device inquiry failed");
            System.exit(2);
          }
      
          // pause for a while
          while (listener.getDevFound() == null)
            try {
              Thread.sleep(5000);
            } catch (Exception e) {}
          RemoteDevice rd = listener.getDevFound();
      
          // UUID 還得多研究一下
          UUID[] uuidSet = new UUID[1];
          uuidSet[0] = new UUID("102030405060708090A0B0C0D0E0F011", false);
      
          int trans;
          try {
            // 對遠端藍芽裝置 rd 查詢服務
            trans = agent.searchServices(null, uuidSet, rd, listener);
          } catch (BluetoothStateException e) {
            System.out.println("device unable to begin service inquiry");
          }
        }
      }
        
    3. RFDateServ.java
      //
      // a Date/Time server
      // conn: RFCOMM
      //
      
      import java.io.*;
      import javax.bluetooth.*;
      import javax.microedition.io.*;
      import java.util.*;
      
      public class RFDateServ
      {
        public static void main(String args[])
        {
          LocalDevice localdev = null;
          try {
            localdev = LocalDevice.getLocalDevice();
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: cannot access local device");
            System.exit(1);
          }
      
          // show an example usage of getProperty.
          //System.out.println("MTU: " + localdev.getProperty(
          //                   "bluetooth.l2cap.receiveMTU.max"));
      
          UUID uuid = new UUID("102030405060708090A0B0C0D0E0F011", false);
          String url = "btspp://localhost:" + uuid.toString();
      
          try {
            // 1. create service record
            StreamConnectionNotifier n = (StreamConnectionNotifier) Connector.open(url);
      
            // 2. obtain the service record
            // set ServiceRecrod ServiceAvailability (0x0008) attribute to
            // indicate our service is available
            // 0xFF indicate fully available status
            // This operation is optional
            ServiceRecord record = localdev.getRecord(n);
            record.setAttributeValue( 0x0008, new DataElement( DataElement.U_INT_1,0xF
      F ) );
      
            System.out.println("Waiting to be connected...");
      
            // 3.ready to accept connection: will block until a client connects
            StreamConnection conn = n.acceptAndOpen();
      
            // send and receive data
            String message = new Date().toString();
            InputStream is = conn.openInputStream();
            OutputStream os = conn.openOutputStream();
            os.write(message.getBytes());
            byte[] rbuf = new byte[100];
            is.read(rbuf);
            System.out.println("Received: " + new String(rbuf));
            is.close();
            os.close();
            conn.close();
          } catch (Exception e) {
            System.out.println("ERROR: connection failed.");
          }
        }
      }
        
    4. 相同的,我們也提供一個將 client 與 listener 包起來的範例,其他的 就讓讀者自己去試試看。
      //
      // a Client to request Date/Time from server
      // connection: RFCOMM
      //
      
      import java.io.*;
      import javax.bluetooth.*;
      import javax.microedition.io.*;
      
      public class RFClientOne implements DiscoveryListener
      {
        public static void main(String args[]) throws BluetoothStateException
        {
          LocalDevice localdev = null;
          RFClientOne listener = new RFClientOne();
      
          try {
            localdev = LocalDevice.getLocalDevice();
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: cannot access local device");
            System.exit(1);
          }
      
          DiscoveryAgent agent = localdev.getDiscoveryAgent();
          try {
            // startInquiry() is non-blocking.
            agent.startInquiry(DiscoveryAgent.GIAC, listener);
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: device inquiry failed");
            System.exit(2);
          }
      
          // pause for a while
          // NOTE: if there is no remote device, this may go into infinite loop
          // this program also shows that to start services from a client it's
          // better to have a GUI interface.
          while (remote == null)
            try {
              Thread.sleep(5000);
            } catch (Exception e) {}
      
          UUID[] uuidSet = new UUID[1];
          uuidSet[0] = new UUID("102030405060708090A0B0C0D0E0F011", false);
      
          int trans;
          try {
            trans = agent.searchServices(null, uuidSet, remote, listener);
          } catch (BluetoothStateException e) {
            System.out.println("device unable to begin service inquiry");
          }
        }
      
        // implement required listener methods.
        private static RemoteDevice remote = null;
        private static ServiceRecord first = null;
      
        public void deviceDiscovered(RemoteDevice rt, DeviceClass cod) {
          remote = rt;
          System.out.println("A Remote Device Found:");
          System.out.println("Address: " + rt.getBluetoothAddress());
          try {
            System.out.println("   Name: " + rt.getFriendlyName(true));
          } catch (IOException e) {}
        }
      
        public void inquiryCompleted(int distype) {
          if(distype == INQUIRY_TERMINATED)
            System.out.println("Application Terminated");
          else if(distype == INQUIRY_COMPLETED)
            System.out.println("Inquiry Completed");
          else
            System.out.println("ERROR: Inquiry aborted");
        }
      
        public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
          first = servRecord[0];
      
          String url = first.getConnectionURL(first.NOAUTHENTICATE_NOENCRYPT, false);
          try {
            StreamConnection conn = (StreamConnection) Connector.open(url);
            String message = "What time is it now?";
            InputStream is = conn.openInputStream();
            OutputStream os = conn.openOutputStream();
            byte[] rbuf = new byte[100];
            os.write(message.getBytes());
            is.read(rbuf);
            System.out.println(new String(rbuf));
            is.close();
            os.close();
            conn.close();
          } catch (Exception e) {
            System.out.println("ERROR: connection error");
          }
        }
      
        public void serviceSearchCompleted(int transID, int respCode) {
          if(respCode == SERVICE_SEARCH_COMPLETED)
            System.out.println("Services successfully located");
          else if(respCode == SERVICE_SEARCH_TERMINATED)
            System.out.println("Service inquiry was cancelled");
          else if(respCode == SERVICE_SEARCH_DEVICE_NOT_REACHABLE)
            System.out.println("Service connection cannot be established");
          else if(respCode == SERVICE_SEARCH_NO_RECORDS)
            System.out.println("No service found");
          else
            System.out.println("ERROR: service inquiry failed");
        }
      }
        
  4. 利用 L2CAP 來交換訊息
    1. L2Listener.java
      //
      // a Lisntener class that implements DiscoveryListener
      // From a source code published in Benhui.net claimes that
      // Impronto prefers to have a Lisntener class.
      // conn: L2CAP
      //
      
      import java.io.*;
      import javax.bluetooth.*;
      import javax.microedition.io.*;
      
      public class L2Listener implements DiscoveryListener
      {
        private static RemoteDevice remote;
        private static ServiceRecord first;
      
        public RemoteDevice getDevFound() {
          return remote;
        }
      
        public void deviceDiscovered(RemoteDevice rt, DeviceClass cod) {
          remote = rt;
          System.out.println("A Remote Device Found:");
          System.out.println("Address: " + rt.getBluetoothAddress());
          try {
            System.out.println("   Name: " + rt.getFriendlyName(true));
          } catch (IOException e) {}
        }
      
        public void inquiryCompleted(int distype) {
          if(distype == INQUIRY_TERMINATED)
            System.out.println("Application Terminated");
          else if(distype == INQUIRY_COMPLETED)
            System.out.println("Inquiry Completed");
          else
            System.out.println("ERROR: Inquiry aborted");
        }
      
        public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
          first = servRecord[0];
      
          // 需增加 url += ";ReceiveMTU=512;TransmitMTU=512";
          String url = first.getConnectionURL(first.NOAUTHENTICATE_NOENCRYPT, false);
          url += ";ReceiveMTU=512;TransmitMTU=512";
          try {
            L2CAPConnection conn = (L2CAPConnection) Connector.open(url);
            String message = "Hello Server";
            int is = conn.getReceiveMTU();
            int os = conn.getTransmitMTU();
            byte[] rbuf = new byte[is];
            conn.send(message.getBytes());
            conn.receive(rbuf);
            System.out.println("Received: " + new String(rbuf));
            conn.close();
          } catch (Exception e) {
            System.out.println("ERROR: connection error");
          }
        }
      
        public void serviceSearchCompleted(int transID, int respCode) {
          if(respCode == SERVICE_SEARCH_COMPLETED)
            System.out.println("Services successfully located");
          else if(respCode == SERVICE_SEARCH_TERMINATED)
            System.out.println("Service inquiry was cancelled");
          else if(respCode == SERVICE_SEARCH_DEVICE_NOT_REACHABLE)
            System.out.println("Service connection cannot be established");
          else if(respCode == SERVICE_SEARCH_NO_RECORDS)
            System.out.println("No service found");
          else
            System.out.println("ERROR: service inquiry failed");
        }
      }
        
    2. DateClient.java
      //
      // a Client to request Date/Time from server
      // connection: L2CAP
      // usage: you can use l2ping to check if the connection is up and running.
      //
      
      import java.io.*;
      import javax.bluetooth.*;
      import javax.microedition.io.*;
      
      public class DateClient
      {
        public static void main(String args[]) throws BluetoothStateException
        {
          LocalDevice localdev = null;
          L2Listener listener = new L2Listener();
      
          try {
            localdev = LocalDevice.getLocalDevice();
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: cannot access local device");
            System.exit(1);
          }
      
          DiscoveryAgent agent = localdev.getDiscoveryAgent();
          try {
            // startInquiry() is non-blocking.
            agent.startInquiry(DiscoveryAgent.GIAC, listener);
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: device inquiry failed");
            System.exit(2);
          }
      
          // pause for a while
          while (listener.getDevFound() == null)
            try {
              Thread.sleep(5000);
            } catch (Exception e) {}
          RemoteDevice rd = listener.getDevFound();
      
          UUID[] uuidSet = new UUID[1];
          uuidSet[0] = new UUID("102030405060708090A0B0C0D0E0F011", false);
      
          int trans;
          try {
            trans = agent.searchServices(null, uuidSet, rd, listener);
          } catch (BluetoothStateException e) {
            System.out.println("device unable to begin service inquiry");
          }
        }
      }
        
    3. DateServ.java
      import java.io.*;
      import javax.bluetooth.*;
      import javax.microedition.io.*;
      
      public class DateServ
      {
        public static void main(String args[])
        {
          // Since no service record is needed, LocalDevice is not needed.
      
          // for L2CAP, protocol name btl2cap is used.
          UUID uuid = new UUID("102030405060708090A0B0C0D0E0F011", false);
          String url = "btl2cap://localhost:" + uuid.toString() +";ReceiveMTU=512;Tran
      smitMTU=512";
      
          try {
            // 1. create service record
            L2CAPConnectionNotifier n = (L2CAPConnectionNotifier) Connector.open(url);
      
            System.out.println("Waiting to be connected...");
      
            // 3.ready to accept connection: will block until a client connects
            L2CAPConnection conn = n.acceptAndOpen();
      
            // send and receive data
            String message = "Hello World";
            int is = conn.getReceiveMTU();
            int os = conn.getTransmitMTU();
            conn.send(message.getBytes());
            byte[] rbuf = new byte[is];
            conn.receive(rbuf);
            System.out.println("Received: " + new String(rbuf));
            conn.close();
          } catch (Exception e) {
            System.out.println("ERROR: connection failed.");
          }
        }
      }
        
  5. 利用 OBEX 來傳送檔案 The Object Exchange (OBEX) protocol 允許客戶端程式利用 pull (onGet()) 或者 push (onPut())的方式來下載或者上傳檔案,在以下的範例中, 我們展示如何從 server 端下載一個 XML 的檔案到客戶端。
    1. OBEXServ.java
      //
      // a simple file server
      // conn: OBEX
      //
      
      import java.io.*;
      import javax.bluetooth.*;
      import javax.obex.*;
      import javax.microedition.io.*;
      
      public class OBEXServ extends ServerRequestHandler
      {
        // override onGet() in ServerRequestHandler
        public int onGet(Operation op) {
          try {
            HeaderSet headers = op.getReceivedHeaders();
      
            // receive the name of the file requested by clients
            String fname = (String) headers.getHeader(HeaderSet.NAME);
            DataOutputStream os = op.openDataOutputStream();
      
            DataInputStream is = new DataInputStream(
                                 new FileInputStream(fname));
      /*
            // the following two lines are from RocoSoft User Guide
            // 還搞不清楚為什麼手冊要這樣子寫?
            InputConnection fconn = (InputConnection) Connector.open(fname);
            DataInputStream is = fconn.openDataInputStream();
      */
      
            // confirm file name with clients
            HeaderSet rephead = createHeaderSet();
            rephead.setHeader(HeaderSet.NAME, fname);
            op.sendHeaders(rephead);
      
            // send the file to the client
            int data;
            while((data = is.read()) != -1) {
              os.write((byte) data);
            }
      
            is.close();
            os.close();
            op.close();
          } catch (IOException e) {
            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
          }
      
          return ResponseCodes.OBEX_HTTP_OK;
        }
      
        public static void main(String args[]) {
          // create a request handler
          OBEXServ handler = new OBEXServ();
      
          UUID uuid = new UUID("102030405060708090A0B0C0D0E0F011", false);
          String url = "btgoep://localhost:" + uuid.toString();
      
          try {
            // 1. create service record
            SessionNotifier n = (SessionNotifier) Connector.open(url);
      
            System.out.println("Waiting to be connected...");
      
            // 3.ready to accept connection: will block until a client connects
            n.acceptAndOpen(handler);
            // send and receive data is now covered by onGet() or onPut()
      
          } catch (Exception e) {
            System.out.println("ERROR: connection failed.");
          }
        }
      }
        
    2. OBEXClient.java
      //
      // a Client to request file from server
      // connection: OBEX
      //
      
      import java.io.*;
      import javax.bluetooth.*;
      import javax.obex.*;
      import javax.microedition.io.*;
      
      public class OBEXClient implements DiscoveryListener
      {
        public static void main(String args[]) throws BluetoothStateException
        {
          LocalDevice localdev = null;
          OBEXClient listener = new OBEXClient();
      
          try {
            localdev = LocalDevice.getLocalDevice();
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: cannot access local device");
            System.exit(1);
          }
      
          DiscoveryAgent agent = localdev.getDiscoveryAgent();
          try {
            agent.startInquiry(DiscoveryAgent.GIAC, listener);
          } catch (BluetoothStateException e) {
            System.out.println("ERROR: device inquiry failed");
            System.exit(2);
          }
      
          // pause for a while
          // NOTE: if there is no remote device, this may go into infinite loop
          // this program also shows that to start services from a client it's
          // better to have a GUI interface.
          while (remote == null)
            try {
              Thread.sleep(5000);
            } catch (Exception e) {}
      
          UUID[] uuidSet = new UUID[1];
          uuidSet[0] = new UUID("102030405060708090A0B0C0D0E0F011", false);
      
          int trans;
          try {
            trans = agent.searchServices(null, uuidSet, remote, listener);
          } catch (BluetoothStateException e) {
            System.out.println("device unable to begin service inquiry");
          }
        }
      
        // implement required listener methods.
        private static RemoteDevice remote = null;
        private static ServiceRecord first = null;
      
        public void deviceDiscovered(RemoteDevice rt, DeviceClass cod) {
          remote = rt;
          System.out.println("A Remote Device Found:");
          System.out.println("Address: " + rt.getBluetoothAddress());
          try {
            System.out.println("   Name: " + rt.getFriendlyName(true));
          } catch (IOException e) {}
        }
      
        public void inquiryCompleted(int distype) {
          if(distype == INQUIRY_TERMINATED)
            System.out.println("Application Terminated");
          else if(distype == INQUIRY_COMPLETED)
            System.out.println("Inquiry Completed");
          else
            System.out.println("ERROR: Inquiry aborted");
        }
      
        public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
          first = servRecord[0];
      
          String url = first.getConnectionURL(first.NOAUTHENTICATE_NOENCRYPT, false);
          try {
            ClientSession conn = (ClientSession) Connector.open(url);
      
            // check to see if OBEX connection is successful
            HeaderSet reshead = conn.connect(null);
            int status = reshead.getResponseCode();
            if(status != ResponseCodes.OBEX_HTTP_OK) {
              System.out.println("OBEX connection error!!");
              System.exit(1);
            }
      
            System.out.println("Getting object from server");
      
            // get object from server
            HeaderSet headset = conn.createHeaderSet();
            headset.setHeader(HeaderSet.NAME, "test.xml");
            Operation op = conn.get(headset);
            InputStream is = op.openInputStream();
            ByteArrayOutputStream os = new ByteArrayOutputStream();
      
            System.out.println("Reading data from server");
      
            // read data from server
            int data;
            while((data = is.read()) != -1)
              os.write((byte) data);
            System.out.println("Size: " + os.size());
            System.out.println(os.toString());
      
            // Although this is not necessary required, but if this is not inserted,
            // I got IO error. And this type of IO error requires some hardware
            // reset.
            Thread.sleep(5000);
      
            is.close();
            os.close();
            conn.close();
          } catch (Exception e) {
            System.out.println("ERROR: connection error");
          }
        }
      
        public void serviceSearchCompleted(int transID, int respCode) {
          if(respCode == SERVICE_SEARCH_COMPLETED)
            System.out.println("Services successfully located");
          else if(respCode == SERVICE_SEARCH_TERMINATED)
            System.out.println("Service inquiry was cancelled");
          else if(respCode == SERVICE_SEARCH_DEVICE_NOT_REACHABLE)
            System.out.println("Service connection cannot be established");
          else if(respCode == SERVICE_SEARCH_NO_RECORDS)
            System.out.println("No service found");
          else
            System.out.println("ERROR: service inquiry failed");
        }
      }
        




Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

窮人的 Java Bluetooth 開發環境之建置


窮人的 Java Bluetooth 開發環境之建置


Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

  1. Log:
    1. 文件草稿始於 08/27/2004.
    2. Updated on 08/12/2005.
  2. 動機: 前一段時間對於手機藍芽程式的開發產生興趣, 可是又沒有很多經費,又希望開發的藍芽程式能以 Java 為主,但是又不想因為 只是想試試看而大費周章重新切割自己的硬碟,所以開始到處尋尋覓覓。 經過蠻曲折的過程,決定將開發環境設定為最精簡的 Knoppix(可以利用 光碟開機成 Linux 而不須對現有的磁碟進行分割,並且只需要一個 FAT/FAT32 的分割區就可以把需要安裝的程式放進去), Bluez (內建在 Linux Kernel 的藍芽驅動程式), 以及 Rococosoft's Impronto Developer Kit for Linux(經過註冊可以免費下載,這個開發工具 提供一個軟體層使得符合 JABWT 的 Java APIs 能與底層的 Bluez 驅動 程式溝通)。如此一來,除了藍芽裝置以外,幾乎不需要花太多的錢 就可以擁有一個 Java 的藍芽開發環境。
  3. 現況: 對很多人來說,Linux 是一個遙不可及的作業系統, 他們只有在 Windows 的環境下才覺得舒服。這裡要提供一個好消息,從 Windows XP SP2 之後,XP 系統有了自己的 Bluetooth 的驅動程式(類似 Linux 的 Bluez),而更好的消息是現在已經有了一個 Open Source 的 Java Stack 被開發出來,他就是 Blue Cove(類似 Linux 的 Impronto)。剛剛試了一下,接在 PC 上的 X-Micro USB Dongle 可以 正確無誤的偵測到 Sony Ericsson P900 和 Nokia 7650。 等到 Blue Cove 成熟到能支援 JSR-82 的所有 APIs 的時候,這份說明 似乎就沒有什麼用了。
  4. 標準宣告:你自己要負責自己所做的事,你自己要了解你自己所有的 風險. This material is provided as is. Use at your own risk.
  5. 參考資料
    1. KNOPPIX 中文交流網
    2. Knoppix's Poor Mans Install for Knoppix 3.4 (or Later).
    3. BlueZ: Official Linux Bluetooth Protocol Stack.
    4. Benhui.net
    5. Rococo Software

目錄

  1. Preparation
  2. Let's Go!

Preparations

  1. 你至少需要一個 FAT/FAT32 分割區,而且這個分割區有足夠大的可用空間, 在我的範例中我用了 1GB ,依據我安裝完的情形,300MB 應該夠用. 目前的情形 (08/2004), NTFS 分割區是不可以的. 在以下的範例中,我假設這個 FAT/FAT32 分割區 在第一個 ide 硬碟的第一個分割區,也就是 /dev/hda1.
  2. 取得一份支援 BlueZ 的 Knoppix CD. 我使用 Knoppix BV1AL 06-18-2004. 以下的範例也以它為例.
    1. 我也試過 Knoppix LTH 08/2004 版以及 Knoppix 3.4 標準版,使用上都大同小異。 只是使用 Knoppix 3.4 標準版的時候,記得要把固定家目錄(Permanent Home)的 擁有者改成 knoppix,指令是 cd /home; chown -R knoppix:knoppix knoppix.
  3. 依照 BlueZ 硬體相容清單 去 採購你的藍芽裝置. 我用的是別人幫我買的 X-Micro 的 Bluetooth USB Dongle. 雖然這個設備並未出現在相容清單內,但是目前 為止使用上都還蠻順利的。你可以在環境架好了以後,再去購買,如果發現不相容 可以馬上退貨。

Let's Go!

  1. Knoppix 的設定與安裝
    1. 可以開始了,首先調整你的電腦使得它能從光碟開機,並以 Knoppix CD 開機, 請在 boot: 的提示下輸入 rootEnter.
    2. 開機後,請設定固定家目錄。雖然依照文件我可以利用 系統控管 --> 設定家目錄, 可是我卻無法成功的完成,因此我是開啟一個 X-Shell 並輸入下列指令來 完成:(# 字號後面是註解不必輸入)
        #產生 knoppix.img 這將會變成你的固定家目錄
        dd if=/dev/zero of=/mnt/hda1/knoppix.img bs=1M count=1024
        losetup /dev/loop1 /mnt/hda1/knoppix.img
        mkfs -t ext2 /dev/loop1   #將 knoppix.img 格式化為 ext2
        mount -t ext /dev/loop1 /mnt/test #將 knoppix.img 掛進來
        df -k    # you should be able to see /mnt/test is mounted and is of size 1G
        umount /dev/loop1
        losetup -d /dev/loop1
        
    3. 重新啟動 Knoppix CD 只是這次在 boot: 的提示下輸入 root home=scan 或者 root home=/dev/hda1/knoppix.img. 以後每一次開機,也都使用同樣的指令,如此你所安裝的軟體就可以存在你的固定家目錄.
      1. 你可以留意開機的過程中,應該會出現把 /dev/hda1/knoppix.img 設定為 /home/root 的訊息;或者
      2. 你可以在開機後,打開一個 X-Shell,並輸入 df -k 來查看 /dev/hda1/knoppix.img 是否已經被掛到 /home/root?如果沒有,你要仔細檢查剛剛的步驟是否正確,或者找人幫忙。
    4. 確定一切無誤後再繼續下一個步驟。
  2. 將下列軟體安裝到 /home/root.
    1. 安裝 J2SE 1.4.x or above
      1. 我下載 j2sdk-1_4_2_05-linux-i586.bin 到 /home/root
      2. 在 /home/root 執行 sh ./j2sdk-1_4_2_05-linux-i586.bin
      3. 然後執行 ln -s j2sdk-1_4_2_05 jdk
    2. 安裝 BlueZ packages 以及 Apache Ant. BlueZ 至少需要安裝 bluez-libs and bluez-utils.
      1. 我下載 bluez-libs-2.10.tar.gz, bluez-utils-2.10.tar.gz, and apache-ant-1.6.2-src.tar.gz 到 /home/root
      2. 在 /home/root 執行 tar zxvf bluez-libs-2.10.tar.gz
      3. cd bluez-libs-2.10
      4. ./configure --prefix=/home/root
      5. make
      6. make install
      7. 重複類似的動作安裝 bluez-utils-2.10.tar.gz and apache-ant-1.6.2-src.tar.gz
    3. 製作一個適當的 ~/.bashrc 檔並且每一次打開一個新的 X-Shell 的時候都 執行 source ~/.bashrc. (我的 ~/.bashrc 提供給你們參考.)
      #!/bin/sh
      export PATH=.:$HOME/sbin:$HOME/bin:$HOME/jdk/bin:$PATH
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/lib
      export MANPATH=$MANPATH:$HOME/man
      export CLASSPATH=$CLASSPATH:.:$HOME/share/java/:$HOME/share/java/idev_bluez.jar
      
    4. 試著將你的藍芽裝置接好,然後執行下列的指令看看你的藍芽裝置是否已經被成功的 驅動。
      modprobe bluez
      modprobe hci_usb
      modprobe l2cap
      hciconfig hci0 up
      hciconfig -a
      
      我使用的 X-Micro USB Bluetooth Dongle 會列印出以下的訊息:
      hci0:   Type: USB
              BD Address: 00:02:72:01:83:CC ACL MTU: 192:8  SCO MTU: 64:8
              UP RUNNING PSCAN ISCAN 
              RX bytes:346 acl:0 sco:0 events:11 errors:0
              TX bytes:36 acl:0 sco:0 commands:10 errors:0
              Features: 0xff 0xff 0x0f 0x00 0x00 0x00 0x00 0x00
              Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3 
              Link policy: 
              Link mode: SLAVE ACCEPT 
              Name: 'BT1'
              Class: 0x000000
              Service Classes: Unspecified
              Device Class: Miscellaneous, 
              HCI Ver: 1.1 (0x1) HCI Rev: 0x20d LMP Ver: 1.1 (0x1) LMP Subver: 0x20d
              Manufacturer: Cambridge Silicon Radio (10)
      
    5. 請確定一切無誤之後,才執行下一個步驟。
  3. 安裝 Rococosoft's Impronto Developer Kit for Linux
    1. 首先你要跟 Rococosoft 註冊(註冊是免費的)。註冊之後, Rococosoft 會 email 給你一份授權檔(這個檔案的名稱是 LinuxLicense.txt,這是執行 Impronto 時一定要的)以及一對 username and password. 你可以下載 Impronto Developer Kit for Linux.
    2. 我將 impronto-1.3-1.i386.rpm 下載到 /home/root
    3. 在 /home/root 執行 alien --to-tgz impronto-1.3-1.i386.rpm 這個步驟會產生 impronto-1.3.tgz.
    4. 在 /home/root 執行 tar zxvf impronto-1.3.tgz
    5. 將 LinuxLicense.txt 複製到 /home/root/share/java
    6. 你需要設定適當的 LD_LIBRARY_PATH and CLASSPATH 環境變數. 但是,如果 你之前已經安裝了我的 ~/.bashrc,你無須做任何設定。如果你需要比較細部的說明, 請參考 /home/root/share/doc/impronto-1.3/user_guide.pdf.
    7. 請參考 /home/root/share/doc/impronto-1.3/user_guide.pdf 看看你是否能 成功的執行 Browser?如果可以,恭喜你,你已經成功的完成環境的建置,再來 就要好好 k Java APIs 來開發藍芽程式了。如果有任何問題,你需要仔細檢查 剛剛的步驟或者參考 /home/root/share/doc/impronto-1.3/user_guide.pdf.
    8. 一個最常見的問題就是 CLASSPATH 的設定與使用. 如果你完全依照我的 範例來做,以下是我執行 Browser 的步驟:
        cd /home/root/share/doc/impronto-1.3/examples/browser
        ant -f build-example.xml
        java -cp $CLASSPATH:classes com.rococosoft.impronto.examples.browser.Browser
        



Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

2012年10月25日 星期四

把玩"魔術師" -- 幫 boot.img 換 kernel


把玩"魔術師" -- 幫 boot.img 換 kernel

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼


從 Android 的架構圖中,以及自己的經驗,kernel 是一部 Android 手機,尤其是 如觸控螢幕、相機、GPS 等硬體設備,是否能正確運作的重要元素。有時候,我們 取得了一個 ROM,或許你只想把它的 kernel 換掉以便測試在同樣的環境下,使用 不同的 kernel 是否可以得到更好的硬體支援,那麼幫 boot.img 更換 kernel 的步驟就很重要了。 假設我們已經有了 Magic 32A 的 kernel 以及 wlan.ko,且假設我們已經取得了 給 Magic 32B 的 boot.img,因此我們需要手動產生給 32A 的 boot.img, 這是因為 32A 的 address 和 32B 的不同所導致。假設你已經依據文件將 kernel 編譯好了,也有了 boot.img;一般來說,其步驟包含:
  1. 下載 PortTools.rar。這個檔案包含了一個 Linux 的執行檔以及幾個 Perl 的執行檔,請將其 解壓所後放置於 ~/tools(或者你喜歡的任意目錄)。
  2. 將 boot.img 從 ~/froyo/out/target/product/sapphire 複製進來。
  3. 將 zImage 從 ~/froyo/kernel/arch/arm/boot 或者 CM6 EBI1/32A kernel 的壓縮檔內複製進來。

把玩"魔術師" -- 編譯 AOSP 2.2 (Froyo) 原始碼


把玩"魔術師" -- 編譯 AOSP 2.2 (Froyo) 原始碼

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

在不斷嘗試編譯 Android 2.2 的過程中,我發現了三份比較重要的文件,分別是 編譯在G1上運行的android 2.2(froyo)代碼編譯在G1上運行的android 2.2(froyo)代碼_正規版、以及 [HOW-TO] Compile AOSP Froyo + [ROM] Latest AOSP Froyo for Sapphire。經過幾個階段的測試、彙整,目前得到以下的方式最為完整。 之前所寫的另一個版本,也提供在 這裡 僅供參考。 既然能編譯出一個堪用的 HTC Magic 32A ROM,自然要把過程寫一下給有興趣的人試試看。 不過,這絕對不是一份任何人都一定看得懂的(我有些部分也還在摸索);因為 是給自己當筆記,我只能先假設你懂一些 Linux 的基本概念。我把編譯的過程 分成幾個步驟來說:(以下的步驟絕大部分都是在 Ubuntu/Linux 上執行)
  1. 你需要幫自己安裝一部執行 Linux 的電腦,AOSP 的文件建議使用 Ubuntu,而我安裝的是 Ubuntu 10.04 x86 32 bit 版。為了不影響平常的 使用,我是將 Ubuntu 安裝在一個名為 VirtualBox 的虛擬機器上;VirtualBox 是一套 Windows 的免費軟體,當在 VirtualBox 內執行 Ubuntu 的時候,電腦 基本上就同時執行 Windows 和 Ubuntu。當你將 Ubuntu 安裝完後,記得先用 "更新管理員"將所有軟體進行更新。

把玩"魔術師" -- 編譯 AOSP 2.1 (Eclair) 原始碼


把玩"魔術師" -- 編譯 AOSP 2.1 (Eclair) 原始碼

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

為了達到第一個目標 -- 自己編譯 AOSP(Android Open Source Project),便開始收集 資料,目前進度極緩慢,所以先把一些有用的資料集結在此,做個筆記,有機會再讓它 成為一篇完整的文章。在慢慢達成目標的過程,分成幾個階段,分別描述如下: 第一階段主要是參考 [HOW-TO] Compile AOSP Froyo + [ROM] Latest AOSP Froyo for Sapphire 這一串討論文。在編譯過程充滿了一些完全不知道要如何處理的困難(看起來基本功 不紮實),就算勉強解決了,可惜編譯出來的 image 檔無法在 Magic 上執行,也找不到 答案。這個階段有幾次想放棄,想想看:都這麼老了還玩?有沒有搞錯? 雖然這一階段的挫折最大,但是以下的文件也有不少是來自這一串討論文。
第二階段主要是參考 編譯 android 2.1 (eclair) 源碼 For HTC G1,這個文章是編譯給 HTC Dream 的,基本上這篇文章是參考一篇日文的文章 EclaironADP1andADP2,雖然看不懂日文,但是從指令可以看出想要編譯給 ADP2 (根據 Android FAQ,ADP2 是 Magic 32B),應該也可行。看文章跟著做的過程中,有不懂得就順便 Google 一下找答案,有 答案的,我會順便把它筆記下來。這一次的編譯,過程輕鬆多了,從頭到尾只 碰到如下的錯誤訊息
target StaticLib: libwebcore (out/target/product/sapphire-open/obj/STATIC_LIBRARIES/libwebcore_intermediates/libwebcore.a)
make: execvp: /bin/bash: Argument list too long (中文的錯誤訊息是"引數列項目過長")
make: *** [out/target/product/sapphire-open/obj/STATIC_LIBRARIES/libwebcore_intermediates/libwebcore.a]

把玩"魔術師"


把玩"魔術師"

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼


目錄

  1. 編譯 AOSP 2.1 (Eclair) 原始碼
  2. 編譯 AOSP 2.2 (Froyo) 原始碼

大概是在 2009 年的 7 月買了人生第一部 Android 手機 -- HTC Magic,買它的原因 不外乎當初玩 Unix/Linux 的情形類似,因為畢竟 Android 的核心還是 Linux, 而且開發應用程式的程式語言是 Java(看的懂得人,不要暗笑,會內傷 ;-) )。 買了之後,玩了一陣子,也試著開發 一些小程式,算是滿意的;可是隨著 Google 不斷的推出,不到一年的時間,Android 的版本快速的從 1.5,發展到 2.2,甚至有傳言 2010 年的 10 月 Google 會推出 3.0 。 (其實這樣快速的大變動,是好的嗎?如果從企業經營的資訊策略 來看,會有什麼影響?要不要慢一點?) 這樣的發展,有什麼影響,目前還不能一探全貌,但是對我來說有下列幾點感想:

如何在 Linux 環境不中斷的利用 BT 下載檔案

使用 Linux (或者 Unix-like) 作業的使用者,在大部分的情形下會利用像 putty 或者 telnet 之類的軟體進行遠端登入,在登入可以做許多的事情,可惜的是在大部分的情形下,當你登出的時候,你執行的程式也跟著結束,這樣的情形非常不方便,尤其當你利用 BT 下載像是 Fedora Core 的 DVD 影像檔的時候。
在這裡,我把我的使用步驟記錄下來,當做筆記。我假設你已經安裝了 screen 和 bittorrent 的套件(請參考你的 Linux 安裝方式,我的以 Debian 為例)。
  1. 首先輸入 screen 來啟動 screen。
  2. ctrl-actrl-c 產生一個新的 screen 視窗(session)。你可以按 ctrl-actrl-w 使得螢幕下方出現編號 0 和 1 的兩個視窗。
  3. 輸入 btdownloadcurses --max_upload_rate 10 XXX.torrent 利用 btdownloadcurses 來下載 XXX.torrent 內容記載的檔案,其中 --max_upload_rate 10 代表上傳的速限為 10KB。
  4. 你可以觀察 BT 的下載情形,或者離開。離開 screen 而 讓 BT 繼續下載的方式是按 ctrl-actrl-d(d 代表 detach)。然後,你就可以像一般的情形一樣來登出。
  5. 之後,你可以重新登入檢查或者結束之前的 screen 視窗。假設你已經重新登入了,你可以輸入 screen -x 重新連上之前的 BT 畫面。如果 BT 還沒結束,你可以像之前一樣的離開 screen 畫面;若 BT 已經下載完畢,你可以結束 BT,並你可以一路的輸入 exit 結束每一個 screen 視窗直到登出為止。

多重開機:Vista/Windows 7 和 XP Pro 分別安裝於兩個硬碟上

從開始玩電腦以來,我就很喜歡嘗試不同的東西,作業系統的安裝、使用就是其中一種,其中包含這個部落格裡面寫的各式各樣的 Linux (含 Mac OSX)。就是因為有這個習慣,如果狀況允許,我用的電腦都有兩顆硬碟,而硬碟的第一個分割區就是我用來安裝測試用的作業系統,例如:在正式使用 Vista 以前,我把 XP 安裝在第一顆硬碟,而 Vista 就安裝在第二顆硬碟;又例如,想測試主機板究竟支不支援 AHCI/RAID 功能,也可以把作業系統試著安裝在其中一顆硬碟。
最近因為 Windows 7 Beta 被公開下載,又聽說其同時擁有 XP 和 Vista 的優點,因此當然要測試一下。測了兩天之後,覺得真的不錯,於是決定不要再從 BIOS 來調啟動順序,而決定要走多重開機的方式。雖然之前在啟動安裝於外接式 USB 硬碟的 Ubuntu中 討論過如何使用 bcdedit 和 Grub4DOS 來設定多重開機,但是總是覺得:已經有了 Spfdisk 這個好工具,為什麼要哪麼麻煩去搞定複雜的 bcdedit?於是就決定還是使用 Spfdisk,可是這下子慘了,不同的分割區在不同的作業系統下會自動隱藏(從 NTFS Type 07 變成隱藏式 NTFS Type 17)。這下子麻煩了,因為這一招在 Vista 不會,我想八、九成是 Windows 7 Beta 的臭蟲,可是如果不是呢?要不要解決?
由於 Windows 7 不錯用,想想以後一定會遇上同樣的問題,所以就花了一些時間把這個問題解決,因為解決的方法適用於在兩顆硬碟多重開機 Vista 和 XP,或者 Windows 和 XP(或許還有無限種可能,因為使用 Grub),所以就把解決方法寫下來。
  1. 首先,先經由 BIOS 調整,讓開機順序從 Vista/Windows 7 的硬碟開機(因為我們要借用 Vista/Windows 7 的 boot manager)。
  2. 下載並安裝 EasyBCD。EasyBCD 是個免費軟體(當然作者也希望使用者可以樂捐;在台灣用 PayPal 極度不方便,所以幫作者打一下廣告),它會使得你在修改 BCD registry(也就是經由 bcdedit 來完成的事)變得非常簡單,而且最有趣的就是它也支援 Grub。
  3. 執行 EasyBCD:請注意閱讀,你的畫面跟下列畫面應該會有不同。
    • 執行 EasyBCD 之後的第一個畫面如下。右邊紅色框框內的設定,是第一次使用的情形,只是它的名稱是 Windows 7;至於,我是如何把它改成 Windows 7 Ultimate,等下會說。然後請按下 ‘Add/Remove Entries’(左邊的紅色框框)來新增多重開機的作業系統。
      bcd1
    • 進入的畫面如下圖所示,這張圖是直接引用 EasyBCD 官網的圖片,因為我已經安裝了 NeoGrub,所以看不到這個畫面。請依照圖中上面的紅色框框(也就是 NeoGrub)點一下,然後會出現如圖中下面的紅色框框,然後請按下 ‘Install NeoGrub’ 來安裝 NeoGrub(是一種 Grub 的變形)。之後的 XP 的啟動,是讓 Vista/Windows 7 的 boot manager 來啟動 NeoGrub,最後再由 NeoGrub 來啟動 XP。雖然好像有點複雜,但是這大概是唯一的方式,除非 Spfdisk 的新版能解決這個問題(或者 Window 7 正式版能解決分割區不見的問題)。
      bcd2
    • 安裝了 NeoGrub 之後,畫面如下。請按下紅色框框中的 ‘Configure’ 來進行 NeoGrub 的設定。按下之後,會跳出一個筆記本的視窗,我們需要輸入一些設定值。 bcd3
      請將下列的設定複製到筆記本內:

      default 0
      timeout 0
      title Windows XP Professional
      root (hd0,0)
      savedefault
      makeactive
      map (hd1) (hd0)
      map (hd0) (hd1)
      chainloader +1

      我們需要對這個檔案做一些說明,若你需要進一步了解,可以在 Google 大神上搜尋 Grub 或者 menu.lst 都可以,不論是 Linux 版本還是 Windows 的版本都可以。
      從檔案中的第 3 行(title ….)開始一直到結束,代表一個開機的選項。title 後面接的是選項中的名稱;因為我們希望啟動 Windows XP Professional,所以就把這個名稱輸入進去,你可以依照你的喜好來輸入(但是中文應該不支援吧 [喔,某些版本的 Grub 可以支援])。
      title 之後的設定,除了有 hd0 和 hd1 的敘述之外,可以全部不改。我的電腦有兩顆硬碟,XP 安裝在 hd0 的 第一個分割區(也就是分割區 0),所以才有 ‘root (hd0,0)’ 的設定;如果你的 XP 安裝在 hd1 的第一個分割區,那就要改成 ‘root (hd1,0)’。可是要如何知道哪一顆硬碟是 hd0,哪一顆硬碟是 hd1 呢?請利用控制台的磁碟管理功能來看,裡面的磁碟 0 就是 hd0,而磁碟 1 就是 hd1。
      最後,如果你的設定是 ‘root (hd1,0)’,那麼你就需要把 map 的那兩行改成:

      map (hd0) (hd1)
      map (hd1) (hd0)

      至於,’default 0′ 指的就是預設的情形,會啟動第一個開機選項,也就是 XP;而 ‘timeout 0′ 的意思是說不必出現開機選項的畫面,直接啟動 XP。你可以把 ‘timeout 0′ 改成 ‘timeout 10′ 來觀察一下開機時的變化。
    • 這個步驟可有可無。藍色框框內可以選擇你預設啟動的作業系統,而紅色框框內,可以允許你將開機的選項名稱改成你喜歡的名稱;例如,預設是 Windows 7,你就可以在這裡把 Windows 7 改成 Windows 7 Ultimate。
      bcd4
安裝好了之後,可以試著重新開機,試試開進不同的作業系統有沒有問題。最後,為了避免不小心刪掉另一個作業系統的檔案或者資料,我的習慣是當我在 A 作業系統時,我會利用控制台把 B 作業系統的開機分割區的磁碟機代碼移除;同樣的,在 B 作業系統時,我會利用控制台把 A 作業系統的開機分割區的磁碟機代碼移除。

啟動安裝於外接式 USB 硬碟的 Ubuntu

距離完成前一篇直接從外接式 USB 硬碟將 Gentoo 啟動的時間,算算已經超過兩年多了,時間過的實在是超嚇人的。這一次又再度嘗試將 Ubuntu 安裝在外接式 USB 硬碟,主要原因是擁有一個 40G 的 USB 硬碟已經有了一陣子了,我把它分成兩個部份,一個部份是用來備份(約 20GB),另一個部份把它當作 Ubuntu 的開機硬碟,這樣子上班和回家都可以玩同一套 Ubuntu。安裝的過程中也發現了一些小狀況,除了利用這個文件作個紀錄之外,也順便做一些筆記,或許將來用得上。以下,我會分成幾個部分來探討,但是最重要的是你一定要確定兩部(或者多部電腦)之間,硬體上會不會有很大的差異,如果是,安裝的過程以及之後的使用都會比較麻煩;舉個例子來說,假設你上班的電腦是 ATI 的顯示卡,而家裡的電腦是 nVidia 的顯示卡,那麼你要如何使用 X 的介面?更不要說你想使用像是 beryl 或者 compiz 這一類的超強的 3D X 介面了。如果你也想像我這樣做,請你務必考慮清楚,因為我的狀況比較單純,家裡和上班的電腦都是 nVidia 的卡。

多重開機

電腦內的硬碟首先安裝了 Windows XP Pro 在第一個分割區,然後才把 Windows Vista 安裝在第二個分割區,開機的 boot manager 自然就是 Vista 的開機管理程式。由於不想利用類似 spfdisk 的開機程式把資料寫到 MBR,因此想利用 Grub4DOS 來啟動 XP、Vista、和 Ubuntu。 將 Grub4DOS 以及 Vista 合用的時候,我們無法使用 Grub4DOS (使用的是 0.4.2 版) 所提供的 grldr.mbr 來啟動,原因以及目前的解決方式都在利用NT的啟動管理器引導GRLDR.MBR這一篇的討論中。
安裝的過程中,我們主要參考 Grub4DOS 的 Grub4dos tutorial,對於文件中不是很清楚的地方,我會說明一下。首先,有關於 bcdedit 的用法,在文件中列出四個指令如下:
   bcdedit /create /d "Start GRUB4DOS" /application bootsector
   bcdedit /set {id} device boot
   bcdedit /set {id} path \grldr.mbr
   bcdedit /displayorder {id} /addlast
可是難道真的要輸入 {id}嗎?其實,在第一個指令輸入之後,電腦會輸出一個類似 {xxxx-xxxx-xxxx-xxxx} 的 ID,請你把這個 {xxxx-xxxx-xxxx-xxxx} 帶入第二、三、以及四個指令內。
再來就是依照文件,我們需要 "copy grldr.mbr to C:\, grldr and menu.lst to the root directory of any FAT16/FAT32/NTFS/EXT2 partition."。這個地方花了我不少時間,其實,以我的情形來說,grldr.mbr、grldr、和 menu.lst 都是安裝到第一個分割區內;也就是說,如果我開機進入 XP,那麼這三個檔以及 grub 資料夾以及其所有的檔案,都應該在 C 槽;如果我開機進入 Vista,那麼這三個檔以及 grub 資料夾以及其所有的檔案,都應該在 D 槽。
最後,我們需要電腦準備 menu.lst,其內容如下:
timeout 20
default 0
splashimage (hd0,0)/grub/xp2008.xpm.gz

# boot up menu
# 方法一
title  HOME:Ubuntu 7.10
root  (hd1,0)
kernel  /boot/vmlinuz-2.6.22-12-generic root=/dev/sdb1 ro quiet splash locale=zh_TW
initrd  /boot/initrd.img-2.6.22-12-generic
quiet

title  Ubuntu 7.10 (recovery mode)
root  (hd1,0)
kernel  /boot/vmlinuz-2.6.22-12-generic root=/dev/sdb1 ro single
initrd  /boot/initrd.img-2.6.22-12-generic

title Load Windows XP Pro
root (hd0,0)
chainloader (hd0,0)/ntldr


title Load Windows Vista
root (hd0,1)
chainloader (hd0,0)/bootmgr

# 方法二
title Find and Boot Ubuntu
fallback 5
find --set-root /sbin/init
configfile /boot/grub/menu.lst

title  Ubuntu, memtest86+
root  (hd1,0)
kernel  /boot/memtest86+.bin
quiet
這個 menu.lst 有幾個地方特別值得說明一下。首先,我們可以在安裝完 Ubuntu 之後,特別去看一下安裝的 kernel 是什麼,一旦知道正確的檔案全名,我們就可以像"方法一"的方式把它定義在 menu.lst 內。這樣的作法有一個很大問題,那就是每一次做了軟體更新之後,你的 kernel 可能也被更新了,那麼你又得來修改 menu.lst。找了一下 Grub4DOS 的文件後,發現了"方法二"的 作法,它可以利用 find 的指令找到最新的 kernel 並載入,非常的方便,試用了幾天之後,我已經把它移到第一個預設啟動的位置。最後,請特別注意一下,啟動 XP 和 Vista 的方式,它們都是利用 chainloader 的指令進行,而且啟動的程式 (hd0,0)/ntldr (給 XP 的) 和 (hd0,0)/bootmgr (給 Vista 的) 都在 (hd0,0),也就是第一個分割區。我想在安裝 Vista 的時候,Vista 也把一些必要的檔案安裝於 XP 的分割區,我實在不是很喜歡這個方式,不過還沒找出其他方式以前,就先這樣嘍。
如果你是利用 DHCP 的方式取得 IP 位址的,每一次重新開機 /etc/resolv.conf 都會被重新設定。如果你希望固定加入(例如)中華電信的 DNS,那麼你可以把下一行加到 /etc/dhcp3/dhclient.conf 檔案內。
prepend domain-name-servers 168.95.1.1, 168.95.192.1;

另一個安裝 Ubuntu 常發生的討厭問題就是時區的設定,在安裝的時候,Ubuntu 特別強調應該將 UTC 設為 yes,可是如果你像我是多重開機,那麼你在 Ubuntu 和 Windows 的時間就會不一樣。所以,在安裝 Ubuntu 的時候,如果你要多重開機,請記得把 UTC 設為 no。如果你已經依照 Ubuntu 的建議設成 yes 的話,你可以依照 Ubuntu 筆記中的"修正 Ubuntu 和 Windows 時間不同步"來修改。

無線網卡

家裡的電腦只能利用無線上網,手邊有 Corega 的 CG-WLUSB2GLV2 USB 無線網卡,在 Ubuntu 環境下嘗試利用這張網卡上網的過程問題重重。本來,USB 硬碟中已經安裝了Ubuntu 的 6.10 版(也就是 Edgy),但是不論是使用 ndiswrapper 或者是 ZD1211、ZD1211B、ZD1211rw 都沒有辦法讓 Ubuntu 認識這張網卡。曾經試圖 email Corega 的技術支援部門,問他們這張卡的 Chipset 是哪一種,有沒有 Linux 的驅動程式等三個問題,只可惜只收到技術支援部門的簡短回答:不支援 Linux;對於這樣簡短的回答,我以後再也不會買任何 Corega 的產品(除非迫不得已)。
在沒有網路連線的情形下,我也不可能把電腦搬來搬去只為了下載 Kernel 的 source 來 compile,在想不出其他更好的辦法下,而且想要試試 Ubuntu 7.10 版,所以在它還剩十天才正式發行的情形下,就把 RC 版下載來試試看。皇天不負苦心人,雖然 ZD1211 的驅動程式仍然不能用,但是 ndiswrapper 終於可以認識 CG-WLUSB2GLV2,wlan0 也終於出現在 ifconfig 和 iwconfig 的輸出了。從 CG-WLUSB2GLV2 的 XP 驅動程式,我猜 CG-WLUSB2GLV2 的 chipset 應該是奇怪的 zd1211bu。可是奇怪的是 lsusb 也找不出 chipset 的資料,XP 內也看不出。ndiswrapper 的安裝方式請參考WifiDocs/Driver/Ndiswrapper
在找資料的過程中,我發現無線網卡在 Linux 的環境中的支援實在不是很完整,安裝也不容易,如果你也想在 Linux 嘗試無線連線,可以參考WifiDocs/WirelessCardsSupported。尤其文章中特別指出 Ralink 2500/RT2400 and Realtek RTL8180 chipsets 的支援比較完整,你如果想買一片無線網卡,或許這些 chipset 的產品可以考慮考慮。

如何調整

  • Ubuntu 7.10 版的預設 3D 特效是利用 compiz。用了幾天之後,我覺得它並沒有 Beryl 的穩定。再試個幾天,如果還是不滿意,考慮刪除 compiz,改用 Beryl。
  • 有關於 X 的調整:由於我一開始安裝的是 Ubuntu 的 6.10 版(也就是 Edgy;我不確定最新的 7.04 Feisty 版是不是相同),在兩部電腦上的 xorg.conf 的設定無法互相使用。這個時候有兩個方式可以暫時解決:第一個方法就是把 /etc/X11/xorg.conf 移除掉(請重新命名,不要真的移除),第二個方式就是重新設定,設定的語法:dpkg-reconfgure xserver-xorg。設定後,可以利用 /etc/init.d/gdm restart 來重新啟動 X。
  • 或者可以立刻安裝 nVidia 的驅動程式(如果你的不是,你就得到 google 再找找看了),安裝的方式就是輸入
    1. sudo apt-get update
    2. sudo apt-get install nvidia-glx
    3. sudo nvidia-xconfig --add-argb-glx-visuals --composite
    4. 重新啟動 X (同時按 Ctrl+Alt+Backspace 鍵)
  • 在我安裝完 nVidia 的驅動程式之後,不論再哪一部電腦,X 都跑的很正常。ㄛ,也因為如此,我也安裝了 Beryl,真的好看而且感覺不出來速度變慢,強力推薦。

利用 tar 來備份

tar 是一個很強的工具,我利用它來作備份。首先,我先準備一份我想要備份的檔案清單,清單中可以包含檔案名稱或者是目錄名稱。如果是目錄名稱,它會將該目錄下的所有檔案(包含隱藏檔案)都備份起來。假設這份清單的檔案名稱為 backup.list,清單的內容如下:
/etc/fstab
/etc/X11/X86Config-4
/home/john/
這就表示我要備份的資料有 /etc/fstab 和 /etc/X11/X86Config-4 這兩個檔案,以及 /home/john 內的所有檔案。
再來我們就執行 tar -T backup.list -zcvf backup.tar.gz 這個指令。這個指令會將 backup.list 中的所有檔案 tar 成一個檔案 backup.tar.gz。其中 z 代表壓縮(gz 的壓縮方式)。
假設在你的備份清單中,你不希望有某些檔案或者目錄被備份,你可以建立例如如下的檔案 exclude.list,
/home/john/.mozilla
這時候的備份指令就是 tar -T backup.list -X exclude.list -zcvf backup.tar.gz

升級 Debian Kernel

Update:
依據 Upgrade Sarge to Etch, 我們可以利用下列的步驟來找尋最新的,而且符合自己的 Kernel:
  1. apt-get update 來更新最新的套件紀錄。
  2. apt-cache search linux-image 來找尋最符合你的需要的 kernel image。目前升級到 Debian Etch 之後,好像內建的 Kernel 已經支援 SMP,而不需要另行安裝,不過這一個部份,我沒多大的把握,也懶得再找答案了。
  3. apt-get install linux-image-XXXX 來安裝 kernel。
  4. 重新開機。
  5. 重新開機之後,如果一切無誤,建議將之前的 Kernel 移除掉,移除的指令是 apt-get remove linux-image-XXXX,在 menu.lst 中的項目也會被一併清除。
------------------------------------------------------------------------
現在的 Pentiun 4 絕大部分支援 Hyper-Threading,而 Debian 的預設安裝 Kernel 卻是非支援 Hyper-Threading(或者更準確的說非支援 SMP)的 Kernel,因此安裝完系統之後,可以考慮升級你的 Kernel,Kernel 的升級方式很簡單(我參考的文件是 http://www.cs.utexas.edu/users/suriya/kernel_2.6_migration.html 這一份資料):
  1. 先到 http://packages.debian.org/stable/base/ 去找你要的 Kernel,例如,假設我們想安裝的 Kernel 是 kernel-image-2.6.8-2-686-smp。
  2. 輸入apt-get install kernel-image-2.6.8-2-686-smp 即可,apt-get 也會幫你修改如 Grub 開機程式的設定檔。
  3. 重新開機。

安裝 Apache2 + MySQL + Tomcat5 + PHP5

我在這裡先把重要的設定檔給儲存下來,免得當掉以後又得從新再找一次。
  • 我希望能夠掌握安裝的系統設定狀況,因此採用比較複雜的下載原始套件,並手動安裝。如果這不是你需要的,你不必在往下閱讀了巷。 Debian 的基本系統少了一些套件,如果我印象沒錯,只需要再安裝 openssl, libssl-dev, libxml2, and libxml2-dev。
  • install MySQL
  • install Apache2: (2.0.55 was installed)
    • ./configure --enable-so --enable-ssl --enable-rewrite
    • 你上一次使用 configure 的指令可以在 config.status 內看到
    • 將下列資訊加到 httpd.conf 的後面
      #
      # 所有自己加上的設定從這裡開始
      #
      # 以下加的副檔名處理規則是給 PHP 用的
      #
      AddType application/x-httpd-php .php .phtml
      AddType application/x-httpd-php-source .phps
      
      #
      # 加入其他模組的設定,下例中包含加入 mod_jk 和 mod_ssl
      #
      
      # mod_jk 設定檔
      Include /usr/local/tomcat/conf/mod_jk.conf
      
      # mod_ssl 設定檔
      
          Include /usr/local/apache2/conf/ssl.conf
      
      
      # 指向 Squirrel Mail 的安裝路徑
      # 也就是 Squirrel Mail 安裝於 /usr/local/squirrelmail-1.4.5,而不需要
      # 安裝於 apache 的 htdocs 目錄內;但是在使用上,卻像是安裝於
      # htdocs 目錄內的 webmail 目錄。
      Alias /webmail /usr/local/squirrelmail-1.4.5
      
    • conf/ssl.conf 檔案: 需要更改的部份為新增 VirtualHost 的定義。另外,非常重要的,你必須把 <IfDefine HAS_SLL> or <IfDefine SSL> 的 tags 刪除掉,這個部份當初耗掉我不少時間。另外,你需要利用 OpenSSH 的工具來為 web server 產生 key 和 certificate。
      ##
      ## SSL Virtual Host Context
      ##
      
      
      #   General setup for the virtual host
      DocumentRoot /usr/local/apache2/htdocs
      ServerName xml.nchu.edu.tw
      ServerAdmin jllu@nchu.edu.tw
      ErrorLog /usr/local/apache2/logs/error_log
      TransferLog /usr/local/apache2/logs/access_log
      
  • install PHP: (5.0.4 was installed)
    • go ftp://ftp.cac.washington.edu/imap/ and download c-client.tar.Z. Unpack it and install with "make ldb" for Debian. Then, follow instructions to copy all *.h, *.c, and *.a to /usr/local/imap-2004g.
    • ./configure --with-apxs2=/usr/local/apache2/bin/apxs --with-mysql=/usr/local/mysql --with-openssl --with-imap=/usr/local/imap-2004g
    • 如果不清楚的話,請參考 Apache 2.0 on Unix systems.
    • 必要的話,可以修改 php.ini,它位於 /usr/local/lib。依照中文化的要求,將 default_charset 設為 "big5"。
  • install Tomcat5: (5.5.9 was installed)
    • 請先依照A Tutorial on Installing and Using Jakarta Tomcat 5.5 for Servlet and JSP Development 來安裝 Tomcat5.
    • install mod_jk (注意,mod_jk2 已經不被 support 了),你需要將 mod_jk.so 安裝到 /usr/local/apache2/modules 內,並設計好 mod_jk.conf 和 workers.properties 兩個檔案。
    • mod_jk 的產生方式為:./configure --with-apxs=/usr/local/apache2/bin/apxs --with-java-home=/usr/jdk --with-java-platform=2 --enable-jni
    • mod_jk.conf 檔案
      # Load mod_jk module
      LoadModule    jk_module  modules/mod_jk.so
      # Declare the module for  (remove this line on Apache 2.0.x)
      #AddModule     mod_jk.c
      # Where to find workers.properties
      JkWorkersFile /usr/local/tomcat/conf/workers.properties
      # Where to put jk logs
      JkLogFile     /usr/local/apache2/logs/mod_jk.log
      # Set the jk log level [debug/error/info]
      JkLogLevel    info
      # Select the log format
      JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "
      # JkOptions indicate to send SSL KEY SIZE,
      JkOptions     +ForwardKeySize +ForwardURICompat -ForwardDirectories
      # JkRequestLogFormat set the request format
      JkRequestLogFormat     "%w %V %T"
      # 如果 URL 的最後包含 servlet/ 再加上任何名稱,就把處理工作交給名為 ajp13
      # 的 worker 來處理,而 Apache 就不再處理了
      JkMount  /*/servlet/* ajp13
      
      # 如果 URL 的最後包含 .jsp,就把處理工作交給名為 ajp13
      # 的 worker 來處理,而 Apache 就不再處理了
      JkMount  /*.jsp ajp13
      
    • workers.properties 檔案
      workers.tomcat_home=/usr/local/tomcat
      workers.java_home=/usr/jdk
      ps=/
      worker.list=ajp13
      worker.ajp13.port=8009
      worker.ajp13.host=localhost
      worker.ajp13.type=ajp13
      #
      # Specifies the load balance factor when used with
      # a load balancing worker.
      # Note:
      #  ----> lbfactor must be > 0
      #  ----> Low lbfactor means less work done by the worker.
      worker.ajp13.lbfactor=50
      
      #
      # Specify the size of the open connection cache.
      worker.ajp13.cachesize=10
      worker.ajp13.cache_timeout=600
      worker.ajp13.socket_keepalive=1
      worker.ajp13.recycle_timeout=300
      # Additional class path components.
      worker.ajp13.class_path=$(workers.tomcat_home)$(ps)common$(ps)lib$(ps)servlet-ap
      i.jar
      

Installation of Software RAID with Debian Sarge

This note is provided so that I can re-do what I did easily without going thru pages of google search results. In fact, as of writing, there is no article that clearly identify the steps to build SATA RAID arrays when installation.
Note that, upon requests, I may translate this document into traditional Chinese.
I purchased an ASUS AP130-E1 which came with a Promise FastTrak SATA 150 TX4 (with chip PDC-20319) and two SATA hard disks. To make it as a server, I installed Debian v3.1r0. The Debian installation CD was started using linux26 and these two SATA HDs were recognized as sda and sdb. To make these two HDs as a RAID-1 array, the following (rough) steps were taken:
  1. partition sda into sda1 (as /), sda5 (as /usr), sda6 (as /var), sda7 (as swap), sda8 (as /tmp), and sda9 (as /home). It is important to note that you have to make the type of these partitions as 'fd' (i.e. raid partition)
  2. partition sdb identical to sda.
  3. create software RAID-1 (based on Serial ATA (SATA) chipsets — Linux support status, the Promise card is actually a faik raid controller and thus only software RAID can be used).
  4. In my system, I created md0 (sda1 and sdb1), md1 (sda5 and adb5), md2 (sda6, sdb6), md3 (sda7, sdb7), md4 (sda8, sdb8), and md5 (sda9, sdb9).
  5. After md* were created, you need to define the partition type and mount point for each md. In my system, md0 is defined as ext3 and mounted in '/', md1 as reiserfs and '/usr', md2 as reiserfs and '/var', md3 as swap, md4 as reiserfs and '/tmp', and md5 as reiserfs and '/home'.
  6. To make both SATA hard disks bootable, I marked both /dev/sda1 and /dev/sdb1 as bootable.
If you wish to use LVM so that your partitions become resizable, please refer to LVM HOWTO.
If you have a Debian system up and running and simply wish to make SATA hard disks as RAID arrays, please refer to an article entitled Installing Debian with SATA based RAID.


How do you know software RAID is up and running?

Note that, upon requests, I may translate this document into traditional Chinese.
How do you know that your SATA RAID arrays are up and running? It was tough for me to find out the answer even googled thru many web pages. I actually had once found that the RAID arrays I created were only partially running. There are two places that you can be ensured that the SATA RAID arrays are up and running. In my system, it looks like these:
  1. check your filesystem disk space usage by issuing df -k and verify that the filesystems are /dev/md*.
    Filesystem             1K-區段      已用     可用 已用% 掛載點
    /dev/md0                255799     66585    175566  28% /
    tmpfs                   453368         0    453368   0% /dev/shm
    /dev/md5              80089452    124612  79964840   1% /home
    /dev/md4                393452     32840    360612   9% /tmp
    /dev/md1              48827944    391588  48436356   1% /usr
    /dev/md2              29293500     84732  29208768   1% /var
    
  2. check your /proc/mdstat and make sure both partitions are U which means up and running. For example, in my system, md0 have two partitions which are sda1 and sdb1 and both partitions are [UU]. If any partition is [_], then it means one partition is not running.
    Personalities : [raid0] [raid1] [raid5]
    md1 : active raid1 sda5[0] sdb5[1]
          48829440 blocks [2/2] [UU]
    
    md2 : active raid1 sda6[0] sdb6[1]
          29294400 blocks [2/2] [UU]
    
    md4 : active raid1 sda8[0] sdb8[1]
          393472 blocks [2/2] [UU]
    
    md5 : active raid1 sda9[0] sdb9[1]
          80091904 blocks [2/2] [UU]
    
    md3 : active raid1 sda7[0] sdb7[1]
          1951744 blocks [2/2] [UU]
    
    md0 : active raid1 sda1[0] sdb1[1]
          272960 blocks [2/2] [UU]
    
    unused devices: 
    

2012年10月22日 星期一

Javascript 與 Java 合用


JavaScript 存取 Java Applet 的函數







在本例中,我們使用 JavaScript 的事件處理程序 onClick 去 呼叫 Java Applet,並將輸入欄位中的值由 Java Applet 顯示出來。 說明︰
  • Form 的內容
    <form name="form1">
    <input type="text" name="text1">
    <input type="button" value="變換文字"
           onClick="document.ControlJava.SetMessage(document.form1.text1.value);">
    </form>
    <applet name="ControlJava" code="ControlJava.class" width="450" height="120">
    </applet>
    
  • Applet 的內容
  • Java applets 也有一些困擾。例如,這個網頁的 applet 在 05/28/2007 之前 是利用JDK 1.2 版所 compile 的,在當時可以正確無誤的呈現中文。可以在 JDK 1.5 給 browser 的外掛程式之下卻無法正確的顯示中文,我必須重新以 JDK 1.5 compile 這個 applet 之後才能正確的顯示。希望以後,這類的困擾可以減少。


Javascript 與 Perl 合用


主從式架構



一個好的主從式的應用程式, 應該在使用者輸入時便立即確認其輸入值是否為 valid (可用 VBScript, JScript, JavaScript, PerlScript 等.), 然後才將 確認過的資料後傳至伺服器端處理程式 (如 CGI, ASP 等)。在以下範例中,使用者 可以在郵遞區號內輸入“aaa“,然後按”確定“,網頁會告訴你輸入的郵遞區號必須是數字。
範例:
郵遞區號: 傳真號碼:

form 原始碼:
<form name=form1 action="vb6.pl">
郵遞區號: <input type=text name=zip size=5 onBlur="validate(this.form)">
傳真號碼: <input type=text name=fax size=15>
<input type=submit value="確定">
</form>

javascript 原始碼:
<script language="javascript">

  function validate(tform1)
  {
    // 確保輸入的資料長度為三
    if(tform1.zip.value.length != 3)
    {
      alert("Please enter a numeric, 3-digit code.");
      // 既然錯了,強迫再回去輸入
      tform1.zip.focus();
    }
    else
    {
      // 確保輸入的是數字
      if(isNaN(tform1.zip.value))
      {
        alert("Please enter a numeric, 3-digit code.");
        tform1.zip.focus();
      }
      else
      {
        // 確保郵遞區號介於 100 與 999 之間
        zip_num = parseInt(tform1.zip.value);
        if (zip_num > 999 || zip_num < 100 )
        {
          alert("Please enter a numeric, 3-digit code.");
          tform1.zip.focus();
        }
      }
    }
  }
</script>

Perl 原始碼:
#!/usr/local/bin/perl
require 'forms-lib.pl';

print "content-type: text/html\n\n";
%input = &GetFormInput();
$zip = $input{'zip'} || "402";
$fax = $input{'fax'} || "(04) 3742337";
print <<EndofMessage;
<html>
<head><title>A Sample Client-Server Application</title></head>

<body>
<h3 align=center>A Sample Client-Server Application</h3>
In this simple application, we use Javascript as the front-end
application which is used to validate inputs, and then submit
it to the server using CGI. The CGI program is written in Perl.
<hr>
<center>
<table border=1>
<tr>
<th>Zip Code</th><th>Fax Number</th>
</tr>
<tr>
<td>$zip</td><td>$fax</td>
</tr>
</table>
</center>
</body>
</html>
EndofMessage

Dynamic HTML (III)


Dynamic HTML (III)



範例:同時利用 DOM 以及 CSS 來動態呈現選項的結果。在實務上,我們經常 會依照使用者選擇的結果,來呈現某些特定的結果。例如,在本範例中, 使用者可以選擇"高雄"(我出生以及成長的地方)或者"台中"(我回台之後 工作的城市),然後程式會依照使用者選擇的都市來顯示該都市的行政區域。 請注意: 這個作法其實不是很好,因為載入網頁的時候,我們必須 先把所有的選項資料都載入,這容易造成不必要的資料傳送,而且資料的維護上 也比較不方便,我們建議採用 Ajax 的方式進行,這個範例只是用來展現 DOM 和 CSS 合用下可以完成的工作範例。
城市:
行政區:

原始碼:
<script language="javascript">

  function display(str) {
    if(str == '--') {
      document.getElementById('label').className = "off";
      document.myform.ks.className = "off";
      document.myform.chung.className = "off";
    } else {
      document.getElementById('label').className = "on";
      if(str == '高雄') {
        alert(str);
        document.myform.ks.className = "on";
        document.myform.chung.className = "off";
      } else {
        document.myform.ks.className = "off";
        document.myform.chung.className = "on";
      }
    }
  }

</script>

說明:
  • 首先,我們說明 form 元件的設計方式,其原始碼如下:
    01  <form name="myform">
    02  城市:
    03  <select name="city"
    04          onChange="display(document.myform.city.options[selectedIndex].text);">
    05  <option value="none" selected="1">--</option>
    06  <option value="ks">高雄</option>
    07  <option value="chung">台中</option>
    08  </select><br/>
    09  <div id="label" class="off">行政區:</div>
    10  <select name="ks" class="off">
    11  <option value="1" selected="1">苓雅區</option>
    12  <option value="2">三民區</option>
    13  <option value="3">新興區</option>
    14  </select>
    15  <select name="chung" class="off">
    16  <option value="1" selected="1">中區</option>
    17  <option value="2">北屯區</option>
    18  <option value="3">南區</option>
    19  <option value="4">北屯區</option>
    20  </select>
    21  </form>
    
    跟之前的範例相同,我們把 form 元件命名為 myform。這個 form 主要由 三個下拉式選單所組成,第一個是第 03 到 08 行組成的,主要用來讓使用者 選擇都市;第二個選單(第 10 到 14 行)以及第三個選單(第 15 到 20 行) 分別用來代表高雄以及台中的行政區域。 雖然定義了三個下拉式選單,但是一開始只需要呈現第一個選單,其他兩個選單 不需要出現。為了達成這個目的,我們定義了如下的 CSS 樣式:
    <style type="text/css">
      .off { display: none }
      .on  { display: inline }
    </style>
    
    我們利用 CSS 的 display: none 來隱藏下拉式選單;如果需要該 元件顯示出來,我們可以將其值動態的改成 display: inline 即可。 由於 .off 代表隱藏,所以在第 09、10、以及 15 行的元件中,我們都設定了 class=off 來隱藏標籤以及選單。
  • 除了 form 被命名為 myform 之外,城市的下拉式選單被命名為 city、 高雄的區域選單被命名為 ks,台中的區域選單被命名為 chung,而行政區標籤 被命名為 label。有了這些名稱的設定,我們就可以 document.myform.ks 很輕易的取得高雄行政區域選單。
  • 如第 03 和 04 行所示,下拉式選單的事件為 onChange;也就是下拉式 選單只要一改變,便會觸發 onChange 事件。這樣的設計可以使得一旦使用者 選完了都市之後,便立刻執行 display() 函數。在執行 display() 之前, 我們必須知道使用者究竟選擇了哪一個選項(也就是都市)。為了解決這個問題, Javascript 定義了一個保留字 selectedIndex 來代表使用者選擇了 哪一個選項,因此 options[selectedIndex].text 代表某個下拉式選單 使用者所選擇的項目(option)的文字內容。若使用者在城市下拉式選單中 (document.myform.city)選擇了高雄,那麼 document.myform.city.options[selectedIndex].text 的文字內容 為 高雄
  • 從城市的下拉式選單中,我們可以看到,會傳到 display() 的參數值只可能 有 --高雄、或者台中。如果 display() 接收到的值 是 --,則程式需要把 label、ks、和 chung 隱藏起來;若接收到的是 高雄,則程式需要把 label 和 ks 顯示出來,並把 chung 隱藏起來;若接收到的是 台中,則程式需要把 label 和 chung 顯示出來,並把 ks 隱藏起來。 顯示或者隱藏元件的方式都是將該元件的 class 值做修正;例如,如果想把 label 的 class 值從 off 改成 on,則我們需要執行 document.getElementById('label').className = "off";;其中 document.getElementById('label') 會找到 id 為 label 的標籤,而修改 CSS 的 class selector 的屬性是 className。相同的,如果要將 ks 隱藏起來,我們需要執行 document.myform.ks.className = "off";
  • 在這個範例,目前還有一個不是很清楚的地方在於 div 的究竟應該 使用 name 還是 id 的方式來命名。之前曾經試過利用 document.myform.label 來定位,label 也曾用過 nameid 來定義過,結果就是不行,只好使用 document.getElementById() 來定位。究竟 name 和 id 的使用要注意 什麼,它們有什麼樣的差別,請參考 Difference between name= and id= 這篇文章。 基本上,name 和 id 是歷史所造成的,也是廠商競爭所造成的,不同的瀏覽器會有 不同的處理方式,因此我們碰到問題時可以參考規則書,另外就是利用不同 的瀏覽器來測試你的網頁。依照 HTML 4.01 版的規格書, 其定義 <div> 標籤只能出現 id 屬性,而沒有 name,而對於 id 的處理方式 Firefox 和 IE 卻不同,而兩者通用的方式是使用 document.getElementById()









Dynamic HTML (II)


Dynamic HTML (II)



範例:我們定義了三個訊息,而這些訊息存放在陣列中;只要使用者在 "Click to change" 的按鈕上按一下,這些訊息就會順序的顯示出來。

原始碼:
<script language="javascript">
<!--
  var count = 0;
  var msg = new Array(3);
  msg[0] = 'XML Rules';
  msg[1] = 'XML 加油';
  msg[2] = '學習 XML 超簡單';

  function change() {
    count++;
    count = count % 3;
    document.myform.elements[0].value=msg[count];
  }
// -->
</script>

說明:許多有用的 DHTML 網頁都跟 form 元件的使用有關,在這個範例中,我們 說明 Javascript 如何與 form 元件互動。
  • 範例中,呈現的是一個 form 元件,該元件的原始碼如下:
    <form name="myform">
    <input type="text" value="XML Rules">
    <input type="button" onClick="change();" value="Click to change"/>
    </form>
    
    有幾個地方請特別注意一下:第一個是 form 元件的名稱被命名為 myform; 第二個是 button 元件中,我們定義了一個 onClick 事件;若該事件被觸發,則 瀏覽器執行 change() 函數。
  • 之前提到的 JavaScript Hierarchy 說明了物件的架構,例如 form 裡面的第一個 element,可以以 elements[0] 來代表,同樣的,一個網頁裡的第一個 img 元素,也可以以 document.images[0] 來代表。只是這一類的表達法有可能因為不小心修改了網頁的內容而讓 JavaScript 的程式無法正確執行。
  • 有了以上的說明,我們知道每一次使用者按了一下 "Click to change" 按鈕, 則 form 中的文字欄位內的內容就會改變。form 中的文字欄位元件其實也就是 document.myform.elements[0] 物件,而更改其內容只需要把它的 value 值更改即可。
  • 雖然不少網頁用的標籤是 <button>,但是如果之前的 button 是用
    <button onClick="change();">Cick to change</button>
    
    來完成,則只有 IE 能夠正確的執行,Firefox 卻無法正確執行。





Dynamic HTML (I)


Dynamic HTML (I)



範例:請將滑鼠移到範例中藍色的圓圈上,你會發現他會變成紅色圓圈。當你把滑鼠移出,又會回到藍色的圓圈。 : onMouseOver

原始碼:
<script language="javascript">
<!--
  // 預載 images
  if(document.images)
  {
    img_on = new Image(); img_on.src="/~jlu/gifs/redball.gif";
    img_off = new Image(); img_off.src="/~jlu/gifs/blueball.gif";
  }

  function flip(pic)
  {
    if(pic == 1)
      document.mypic.src=img_on.src;
    else
      document.mypic.src=img_off.src;
  }
// -->
</script>

說明:這個範例中,牽涉到的觀念有:事件處理(含 onMouseOver 和 onMouseOut)以及 網頁的 DOM 架構以及其處理方式。
  • 首先,我們先看 HTML 元件的部分,其原始碼如下:
    <img name="mypic" border="0" src="/~jlu/gifs/blueball.gif"
     onMouseOut="flip(2);" onMouseOver="flip(1);"/>
    
    該元件是一個圖形元件(也就是藍色的球),而且該元件包含兩個事件:一個是當 滑鼠進入該元件時會觸發的 onMouseOver 事件,另一個是當滑鼠離開該元件時會觸發的 onMouseOut 事件。從原始碼中,我們可以看出,當滑鼠進入的時候,瀏覽器會執行 flip(1),而離開的時候,會執行 flip(2)
  • 設計這個範例的時候,我們希望剛開啟網頁的時候,藍色的球(也可以使用其他 的圖)會出現,這個部分已經定義在 <img> 內;而當滑鼠進入元件的時候, 會出現紅色的球;然後,當滑鼠離開元件的時候,又會恢復藍色的球。依據這樣的 設計,當 onMouseOver 事件被觸發時,flip(1) 的目標就是把藍色的球換成 紅色的球;也就是說,程式需要把 <img> 標籤內 src 的值從 /~jlu/gifs/blueball.gif 改成 /~jlu/gifs/redball.gif。 為了能夠快速的呈現不同的圖形,我們必須在網頁載入的時候先把圖形也載入,而 為了完成這個目的,我們定義了如下的程式碼:
      if(document.images)
      {
        img_on = new Image(); img_on.src="/~jlu/gifs/redball.gif";
        img_off = new Image(); img_off.src="/~jlu/gifs/blueball.gif";
      }
    
    這一段程式碼會在網頁載入的時候就執行。這段程式碼先檢查網頁中(document 物件)是否有任何的 <img> 標籤(images 物件),如果有,則 產生 Image 物件。從程式碼中,我們看到程式總共產生了兩個 Image 物件, 分別是 img_on 和 img_off,而且分別代表 redball.gif 和 blueball.gif。
  • 完成了 Image 物件的載入之後,我們就可以解釋 flip() 函數了。當 onMouseOver 事件被觸發時,瀏覽器將參數 1 傳給 flip();而依據下列的 程式碼:
      function flip(pic)
      {
        if(pic == 1)
          document.mypic.src=img_on.src;
        else
          document.mypic.src=img_off.src;
      }
    
    我們會把 img_on.src 的值(也就是 redball.gif)指定給 document.mypic.src。 document.mypic.src 的用法就像之前介紹的 DOM 或者 Javascript 的物件架構圖 一樣,一個 HTML 的網頁由 document 物件代表;而 document.mypic 指的是 在網頁內名為 mypic 的物件;請再次看一下 <img> 標籤,我們為它 定義了一個 name 屬性,而它的值為 mypic。name 屬性的 目的在於為其相對應的標籤定義一個名稱,有了名稱我們就可以利用 document.名稱 的方式取得該物件。最後,document.mypic.src 即代表 <img> 標籤的 src。經由這樣的指定,<img> 標籤的 src 值 就從 blueball.gif 變成 redball.gif;而在網頁上的效果就是從藍色的球 變成紅色的球。利用 Javascript 我們可以"動態"的改變 HTML 的內容,所以這一類 的作法又稱為 DHTML(Dynamic HTML;動態 HTML)。





Javascript: window 物件


window 物件



本例題使用的語法為:
<body onLoad="newwin=window.open('http://web.nchu.edu.tw/~jlu',
        'targetname','height=300,width=400,toolbar=no,status=yes')">

說明:
  • window 物件的方法:
    • window.open(): open() 函數會傳回新視窗的物件 (如 newwin)。
      1. 第一個參數為 URL。
      2. 第二個參數為新開啟的視窗名稱。 此名稱可用於 <a> 的 target 參數。
      3. 第三個參數為視窗特色的描述。 描述的方式有:
        • height: 高度
        • width: 寬度
        • toolbar: 是否顯示工具列
        • status: 是否顯示狀態列
    • window.close(): 如要關閉剛剛開啟的視窗, 可使用 newwin.close();
  • window 物件的事件程序:
    • onLoad
    • onUnLoad
    • onFocus
    • onBlur

Javascript 事件處理


事件處理



範例:事件的處理(例如使用者在按鈕上點一下,即產生一個事件)是 Javascript 的一個強項。在這個範例中,我們試圖說明常用的 Javascript 的事件,以及其對應 的物件。 javascript:function: JavaScript Pseudo Protocol.


說明:這個範例由兩個部分所組成:一個是 HTML 的元件,而另一個是與其配合的 Javascript 程式碼。我們依序說明 HTML 元件並解釋與其配合的程式碼;至於,整個 網頁的內容應該如何設定,請讀者自行檢視本網頁的原始碼。
  • 範例中第一個部分的原始碼如下:
    <a href="javascript:open_window('javascript.html')">javascript:function</a>: JavaScript Pseudo Protocol.
    
    請注意 href 的內容 javascript:open_window('javascript.html')。一般 來說,href 的內容是 URL,但是我們卻設定了一串頗為奇怪的字串。讓我們解釋一下: Javascript 提供了一個特別的用法,稱之為 JavaScript Pseudo-Protocol,而其 specifier 為 javascript:(請特別注意最後的冒號);一般來說,javascript: 之後會加上一個函數名稱,函數可以是 Javascript 內建的函數,也可以是自行定義 的函數。例如,你可以直接在瀏覽器輸入 javascript:alert(document.links.length) 來得知目前這個網頁總共有多少超連結(links)。當然,你也可以將這段 程式碼用來取代這個範例的 href 值。 從原始碼中,我們知道如果使用者點選該連結,則瀏覽器會執行一個名為 open_window() 的函數,而該函數的程式碼如下:
    <script language="javascript">
    <!--
      function open_window(url)
      {
        window.open(url);
      }
    // -->
    </script>
    
    由之前說明函數的用法,我們知道字串 javascript.html 會被傳送到 open_window() 函數中,然後該函數利用 Javascript 內見的 window.open() 函數將該 URL 在另一個新視窗中開啟。
  • 範例中第二個部分的原始碼如下:
    <form>
    <input type="button" name="Button1" value="請按我" onClick="sayhi()">
    </form>
    
    這是一個 form 元件,該元件內包含一個按鈕元件,該元件名稱為 Button1。 在按鈕元件中,我們也定義了一個事件處理的函數:onClick="sayhi()"; 該定義說明了:如果使用者在按鈕上點一下(click),則執行 sayhi() 函數, 而 sayhi() 的程式碼如下:
    <script language="javascript">
    <!--
      function sayhi()
      {
        alert("What's up, duck?");
      }
    // -->
    </script>
    
    也就是說,如果使用者在"請按我"的按鈕上點一下,螢幕上會出現包含 "What's up, duck?" 訊息的 alert 視窗。 除了 onClick 事件可以定義在按鈕元件(button)之外,Javascript 還提供 哪些事件?而這些事件又可以用在哪些網頁物件呢?我們把相關資訊整理如下表:
    事件對應之物件
    onLoadDocuments and Framesets
    onUnloadDocuments and Framesets
    onSubmitForms
    onClickRadio buttons, push buttons, checkboxes, and links
    onFocusText fields, Password Fields
    onBlurText fields, Password Fields
    onChangeText Fields, Scrolling Lists, and Popup Menus
    onMouseOverHyperlinks
    onMouseOutHyperlinks
    onSelectText Fields

  • 最後,我們提供一個 onLoad 的範例:使用者如果依據 本例題 方式在 <body> 內加入 onLoad,除了該例題的網頁回出現之外,在載入該例題網頁時(onLoad)也會 另開一個視窗,該視窗是我的『首頁』。由於 onLoad 可以應用於 documents 或者 framesets,所以 onLoad 適合定義於 <body> 標籤,其簡單的寫法如下:
    <body onLoad="window.open('http://web.nchu.edu.tw/~jlu');">
    
    完整的 window.open 語法是 window.open("URL", "視窗名稱", "視窗的選項設定"),比較完整的解釋在該利提網頁中。由於新的瀏覽器大多會封鎖彈跳視窗,你可以 放心的允許它出現。 來開啟一個視窗。





Alert, Confirm, and Prompt


Alert, Confirm, and Prompt



範例:在本範例中,我們介紹三個最常用的對話視窗。其中,我們曾經介紹過 alert() 以及 prompt()。在操作這個範例時,如果出現"取消"按鈕,請記得也 點選它,並觀察結果有何不同。如果你想不斷重複操作,你只需要在瀏覽器上點選 "重載"即可。
原始碼:
<script language="javascript">


function getinput()
{
  var res;
  var conf;
  var res = prompt("Please enter your name.", "");

  if (res != null)
  {
    if (res != "")
    {
      conf = confirm("Your name is " + res);
      if (conf == true) 
        document.write("Welcome, " + res);
      else
        document.write("See you next time.");
    }
    else
      document.write("Hello whoever you are.");
  }
  else // if Cancel was clicked
  {
    document.write("See you next time.");
  }
}

getinput();

</script>

說明:
  • 就像前一個範例所說明的,alert("text") 會將參數內的字串顯示在對話視窗 中,而且該對話視窗只有一個"確定"按鈕。
  • 在 getinput() 中,首先執行 prompt() 來取得使用者輸入的姓名。之前,我們 已經介紹過 prompt() 的用法,我們在這裡只針對之前沒說明過的再進一步解釋。 在 prompt 對話視窗中,會出現兩個按鈕,一個是"確定",而另一個是"取消"。 不知道,你們是否想過:如果使用者不小心(或者故意)點選"取消"按鈕,程式 會不會產生一些莫名奇妙的結果?在之前的說明,我們知道如果使用者點選"確定", 那麼 prompt() 會把文字欄位中的資料回傳;反之,如果使用者點選"取消", 那麼 prompt() 會回傳 null(null 是保留字,代表什麼都沒有,不是 null 字串)。最後,還有一個問題:如果使用者什麼也沒有輸入就點選了"確定", 什麼物件會被回傳呢?這個問題的答案是"空字串";請注意,空字串和 null 是不 一樣的,空字串還是一個物件,而 null 卻沒有物件。
  • 經過了前一項的說明,我們來解釋 prompt() 之後的 if 敘述:如果使用者 在 prompt 視窗點選了"取消",由於 res 被設定成 null,因此 else 的區塊會被 執行,而出現 See you next time.。如果使用者什麼也沒有輸入就點選了"確定", 則 res 是空字串(""),則出現 Hello whoever you are.。最後一種情形就是 使用者輸入了一些資料並點選"確定",則程式執行 confirm() 函數。
  • confirm("text") 跟 alert("text") 類似,都會把參數內的字串顯示在 對話視窗中;但是跟 alert() 不同的地方在於 confirm 視窗會同時出現 "確定"和"取消"按鈕。在 confirm 視窗,如果使用者點選"確定"按鈕,則 confirm() 回傳 true;反之,confirm() 回傳 false
  • 接續之前的說明:假設使用者輸入 Eric 並點選"確定",則程式執行 confirm()。 首先,Your name is Eric 會顯示在 confirm 視窗;這時,如果使用者 點選"確定",則網頁出現 Welcome, Eric;否則,網頁出現 See you next time.。






Functions 的宣告與使用


Functions 的宣告與使用



範例:在這個範例中,我們說明函數(function)的宣告以及使用的方式。 範例中,我們定義了三個最常見的函數型態:world() 方法沒有傳入的參數, 也沒有回傳結果;hello(someone) 方法包含一個傳入的參數,但是沒有回傳結果; 還有,input() 方法,該方法沒有傳入的參數,卻有回傳結果。 定義完了方法之後,在程式碼的最後,我們分別呼叫這些方法。

原始碼:
<script language="javascript">

// 宣告函數
function world()
{
  document.write("<h2>Hello World.</h2>");
}

// 傳遞參數給函數
function hello(someone)
{
  document.write("<h2>Hello, " + someone + "</h2>");
}

// 回傳運算結果給呼叫程式
function input()
{
  return prompt("Please enter your name.", "");
}

var someone;

world();
hello("Class");
someone = input();
alert("Hello " + someone);


</script>

說明:
  • 函數的宣告由保留字 function 開頭,之後接上函數的名稱。 函數名稱後接上一對小括號,括號內可以置入參數。函數的範圍由一對大括號 組成,大括號內可以寫入敘述。
  • 一般來說, 函數(functions)宣告在 <head> 標籤之間。(請以檢視原始碼 的方式來看本例的寫法。)
  • 三個函數的寫法都還蠻簡單易懂的,除了 return 之外,我們就不多做 說明。在 input() 方法中,其目的在於取得使用者的資料,所以利用 prompt() 來 完成;prompt() 完成後,會回傳一個字串物件,而該物件會被 return 給傳回 呼叫端,也就是 someone = input();
  • 從宣告 someone 變數開始,所有的程式碼置放於 <body> 內,或者 更嚴謹的說,程式碼置放於應該出現的地方。由於 world() 和 hello("Class") 都沒有回傳任何資料,所以呼叫方法的方式就是直接把方法名稱寫出來,並加上 並要的參數即可。由於 input() 會回傳資料,所以我們寫成 someone = input();,由 someone 來接收 input() 回傳的資料。
  • 除了 prompt() 會呈現輸入的對話視窗之外,Javascript 也提供的輸出用的 對話視窗 alert();alert() 會將參數的內容以對話視窗的方式呈現出來。alert() 大概也是最常用來顯示除錯的訊息。







認識「註解」,「變數」等


認識「註解」,「變數」等



範例:從使用者取得三個分數(或者成績),將三個分數存放在陣列中, 最後利用迴圈計算平均分數。這個範例同時涵蓋了變數的宣告,註解的 使用,陣列的宣告/使用,以及 for 迴圈敘述。

JavaScript 的基本架構


JavaScript 的基本架構



範例:

原始碼:
<body>
<script language="javascript">
<!--
  document.write("<h2>Hello World.</h2>");
// -->
</script>
</body>

介紹所有程式語言的第一個程式,當然非 Hello World 莫屬。在範例下出現的 Hello World 是由 JavaScript 程式碼所產生的。 說明:
  • 從原始碼中可以清楚的看出:JavaScript 不是 Java。
  • JavaScript 的敘述應該放在 <script> 和 </script> 標籤之間,而 JavaScript 的程式碼在該網頁被載入的時候就開始執行。 <script> 標籤包含 language 屬性,該屬性值用來說明該網頁中使用的 script 語言為何。由於 我們寫的是 JavaScript 程式碼,所以屬性值是 javascript
  • 因為輸出的地方在於網頁的 <body> 標籤內,所以程式碼置放於 <body>;如果定義了一些共用的 JavaScript 程式碼,則一般的習慣會定義在 <head> 標籤內。
  • <!-- 和 // --> 標籤是為了避免某些不支援 JavaScript 的瀏覽器將 JavaScript 的程式碼顯示在網頁上。
  • JavaScript 的敘述最後都要加上分號(;)。範例中的敘述是將 <h2>Hello World.</h2> 寫入(write)網頁(document)。
  • JavaScript 將網頁內的所有元素(element)都視為一個個的物件,例如 document 這個物件就代表網頁。既然是物件,每個物件就有它自己的資料成員以及方法。例如,document 有一個方法 write(),而 write() 這個方法會將參數寫到網頁。
  • 還有沒有其他的物件?有,例如,每個瀏覽器的視窗是以 window 這個物件作代表。 如果我們要改變視窗的狀態列的訊息,我們可以利用 window.status="Test Status";Test Status 顯示在狀態列。試試看!(注意,status 是 window 物件 的資料成員)(FireFox 預設的狀態是不允許 script 修改狀態列,如要測試,你可以 工具->選項->內容->可使用 JavaScript->進階->把"改變狀態列文字"打上勾就好了。)
  • JavaScript 的物件關係類似樹狀的結構,例如一個瀏覽器內可以包含多個網頁,每一個網頁內可以包含圖、form 等。詳細的說明請參考 JavaScript Hierarchy in VOODOO's Introduction to JavaScript。