菜鳥初學Java的備忘錄(五) (轉)

worldblog發表於2008-01-31
菜鳥初學Java的備忘錄(五) (轉)[@more@]

年1月20日 星期一 陰

對幾個的基礎知識作一下補充。

一.異常
Java對異常的處理同一樣,不是刻意的去避免它的發生,而是等它發生後去補救.
Delphi的異常處理簡單來說就是一下語句

Try
Except//異常發生後就轉入此處
Finally//不管異常發不發生,都轉入此處執行
End

與此相類似,Java的異常處理的基本形式如下
try{
}catch(ExceptionType1 e){
異常情況1的處理
}catch(ExceptionType2 e){
異常情況2的處理
throw(e)//丟擲異常,和Delphi中的raise是一回事
}

要補充的是,對大多數的異常,假如你要在正常執行的中而不是捕捉異常的程式中明確的丟擲,Java的需要你事先對你要丟擲的異常作宣告,否則不允許編譯透過.這個任務是由throws來完成的.

二.Java的輸入輸出流
2.1 輸出
System.out.print  裡out是一個靜態方法哦
System.out.println
System.err.print  和out一樣也是標準輸出,至於有什麼不同,我目前還不清楚
System.err.println
2.2 輸入
System.in.read()
2.3 的操作
只需要幾個帶註釋的例子就可以了。
第一個是一個顯示檔案基本資訊的程式

import java.io.*;//調入和io相關的類
class fileinfo{
意,main一定是靜態方法

  public static void main(String args[])throws IOException{
  File fileToCheck;//使用檔案建立例項
  if (args.length>0){
  for (int i=0;i  fileToCheck=new File(args[i]);//為檔案物件分配空間
  info(fileToCheck);//這裡引用的info一定要是靜態方法成員
  }
  }
  else{
  System.out.println("no file given");
  }
  }

  public static void info(File f)throws IOException{
  System.out.println("Name:"+f.getName());
  System.out.println("Path:"+f.getPath());
  if (f.exists()){
  System.out.println("File exists.");
  System.out.print((f.canRead()?" and is Readable":""));//判斷函式,如果滿足條件,輸出前者,否則輸出後者
  System.out.print((f.canWrite()?"and is Writable":""));
  System.out.print(".");
  System.out.println("File is"+f.length()+"bytes.");
  }
  else{
  System.out.println("File does not exist.");
  }
  }
}

第二個例子是一個電話資訊的小程式,輸入姓名和電話號碼,程式將其存入phone.numbers檔案中,透過FileOutputStream來實現

import java.io.*;

class phones{
  static FileOutputStream fos;
  public static final int lineLength=81;
  public static void main(String args[])throws IOException{
  byte[] phone=new byte[lineLength];
  byte[] name=new byte[lineLength];
  int i;
  fos=new FileOutputStream("phone.numbers");
  while(true){
  System.err.println("Enter a name(enter 'done' to quit)");
  readLine(name);
  if ("done".equalsIgnoreCase(new String(name,0,0,4))){
  break;
  }
  System.err.println("Enter the phone number");
  readLine(phone);
  for (i=0;phone[i]!=0;i++){
  fos.write(phone[i]);
  }
  fos.write(',');
  for (i=0;name[i]!=0;i++){
  fos.write(name[i]);
  }
  fos.write('n');
  }
  fos.close();
  }

  private static void readLine(byte line[])throws IOException{
  int i=0,b=0;
  while((i  line[i++]=(byte)b;
  }
  line[i]=(byte)(0);
  }
}

2.4 流
無非是兩種
輸出流,讓我們來寫的
輸入流,給我們來讀的
java.io包中有很多種類的輸入輸出流
1.FileInputStream和FileOutputStream 節點流
2.BufferedInputStream和BufferedOutputStream 過濾流
3.DataInputStream和DataOutputStream 增強的過濾流
4.PipedInputStream和PipledOutputStream 用於執行緒的流


掌握了流的概念,就可以開始Sockets的學習了.關於Socket的作用,昨天我已經講了.

現在,我們將建立一個簡單的通訊程式,以獲得對Socket的實質性的認識.該程式包括兩個部分,客戶機(RemoteFileClient)和(RemoteFileServer).客戶機向伺服器發出請求,要求讀取伺服器上的檔案資訊.伺服器將響應請求,將相應的檔案資訊傳給客戶機,將相應的檔案資訊傳給客戶機.

首先我們建立RemoteFileClient類:
import java.io.*;//java.io 包提供對流進行讀寫的工具,也是與 TCP 套接字通訊的唯一途徑
import java.*;//java.net 包提供套接字工具。

public class RemoteFileClient {
  protected String hostIp;
  protected int hostPort;
  protected BufferedReader socketReader;//負責讀資料的物件
  protected PrintWriter socketWriter;//負責寫資料的物件

的構造器有兩個引數:主機的 地址(hostIp)和埠號(hostPort)各一個.構造器將它們賦給例項變數
  public RemoteFileClient(String aHostIp, int aHostPort) {
  hostIp = aHostIp;
  hostPort = aHostPort;
  }
  public static void main(String[] args) {
  }
接到遠端伺服器
  public void setUpConnection() {
  }
遠端伺服器請求檔案資訊
  public String getFile(String fileNameToGet) {
  }
遠端伺服器上斷開
  public void tearDownConnection() {
  }
}

首先來實現main()
public static void main(String[] args) {
  RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1", 3000);//為了方便,我們把本地伺服器當作遠端伺服器
  remoteFileClient.setUpConnection();//連線。不能直接使用setUpConnection,因為它是非靜態變數,需要建立例項後,對例項進行引用,可以看我第一天的日記,上面寫的非常詳細
  String fileContents =
  remoteFileClient.getFile("RemoteFile.txt");//讀取

  remoteFileClient.tearDownConnection();//斷開

  System.out.println(fileContents);//輸出讀取內容
}

步驟非常清楚.那麼我們分別看連線,讀取,斷開是怎麼實現的
1.連線
public void setUpConnection() {
  try {
  Socket client = new Socket(hostIp, hostPort);//建立Socket物件


  OutputStream outToServerStream=client.getOutputStream();
  InputStream  inFromServerStream=client.getInputStream();
  socketReader = new BufferedReader(new InputStreamReader(inFromServerStream));
Socket的InputStream包裝進BufferedReader 以使我們能夠讀取流的行

  socketWriter = new PrintWriter(outToServerStream);
Socket的OutputStream包裝進PrintWriter 以使我們能夠傳送檔案請求到伺服器

  } catch (UnknownHostException e) {
  System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);
Socket物件建立錯誤的異常處理
  } catch (IOException e) {
  System.out.println("Error setting up socket connection: " + e);
IO錯誤的異常處理
  }
}

2.讀取
public String getFile(String fileNameToGet) {
  StringBuffer fileLines = new StringBuffer();//StringBuffer物件也是String物件,但是比它更靈活,這裡是用來存放讀取內容的

  try {
  socketWriter.println(fileNameToGet);
  socketWriter.flush();//檔案存放地址輸出到socketWriter中,然後清空緩衝區,讓這個地址送到伺服器中去


  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();//別忘了把buffer中的內容轉成String再返回
}

3.斷開
public void tearDownConnection() {
  try {
  socketWriter.close();
  socketReader.close();
  } catch (IOException e) {
  System.out.println("Error tearing down socket connection: " + e);
  }
}

tearDownConnection() 方法只別關閉我們在 Socket 的 InputStream 和 OutputStream 上建立的 BufferedReader 和 PrintWriter。這樣做會關閉我們從 Socket 獲取的底層流,所以我們必須捕捉可能的 IOException


好,現在可以總結一下客戶機程式的建立步驟了
1.用要連線的機器的IP埠號例項化Socket(如有問題則丟擲 Exception)。
2.獲取 Socket 上的流以進行讀寫.
3.把流包裝進 BufferedReader/PrintWriter 的例項.
4.對 Socket 進行讀寫.具體說來,就是在Writer送檔案地址資訊給伺服器,在Reader上讀取伺服器傳來的檔案資訊
5.關閉開啟的流。

下面是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 aHostIp, int aHostPort) {
  hostIp = aHostIp;
  hostPort = aHostPort;
  }
  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 static void main(String[] args) {
  RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1", 3000);
  remoteFileClient.setUpConnection();
  String fileContents = remoteFileClient.getFile("RemoteFile.txt");
  remoteFileClient.tearDownConnection();

  System.out.println(fileContents);
  }
  public void setUpConnection() {
  try {
  Socket client = new Socket(hostIp, hostPort);

  OutputStream outToServerStream=client.getOutputStream();
  InputStream  inFromServerStream=client.getInputStream();
  socketReader = new BufferedReader(new InputStreamReader(inFromServerStream));
  socketWriter = new PrintWriter(outToServerStream);

  } catch (UnknownHostException e) {
  System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);
  } catch (IOException e) {
  System.out.println("Error 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);
  }
  }
}

好了,現在來看伺服器端的程式怎麼寫.
建立RemoteClientServer類:
import java.io.*;
import java.net.*;

public class RemoteFileServer {
  protected int listenPort = 3000;
  public static void main(String[] args) {
  }
  public void acceptConnections() {
  }
  public void handleConnection(Socket incomingConnection) {
  }
}

跟客戶機中一樣,首先匯入 java.net 的 java.io。接著,給我們的類一個例項變數以儲存埠,我們從該埠偵聽進入的連線。預設情況下,埠是 3000。
acceptConnections()將允許客戶機連線到伺服器
handleConnection()負責與客戶機 Socket 互動以將您所請求的檔案的內容傳送到客戶機。

首先看main()

public static void main(String[] args) {
  RemoteFileServer server = new RemoteFileServer();
  server.acceptConnections();
}
非常簡單,因為主函式無非是讓伺服器進入狀態,所以直接acceptConnection().需要注意的是,必須先建立RemoteFileServer()的例項,而不是直接呼叫.


那麼伺服器是怎樣透過acceptConnection()來監聽客戶機的連線呢?並且如果兼聽到了,又怎樣處理呢?我們來看
public void acceptConnections() {
  try {
  ServerSocket server = new ServerSocket(listenPort);//同客戶機的Socket對應,在伺服器端,我們需要ServerSocket物件,引數是兼聽的埠號
  Socket incomingConnection = null;//建立一個客戶端的Socket變數,以接收從客戶端監聽到的Socket
  while (true) {
  incomingConnection = server.accept();//呼叫該 ServerSocket 的 accept() 來告訴它開始偵聽,
  handleConnection(incomingConnection);
  }
斷監聽直到來了一個連線請求,然後交由handleConnection處理
  } 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);
  }
}

無論何時如果建立了一個無法繫結到指定埠(可能是因為別的什麼控制了該埠)的 ServerSocket,Java 程式碼都將丟擲一個錯誤。所以這裡我們必須捕捉可能的 BindException。同時,與在客戶機端上時一樣,我們必須捕捉 IOException,當我們試圖在 ServerSocket 上接受連線時,它就會被丟擲。可以透過用毫秒數呼叫 setSoTimeout() 來為 accept() 呼叫設定超時,以避免實際長時間的等待。呼叫 setSoTimeout() 將使 accept() 經過指定佔用時間後丟擲 IOException

最關鍵的處理在handleConnection()中,這時已經連線到了客戶端的Socket,要從該Socket中讀取客戶端的請求並且響應。

public void handleConnection(Socket incomingConnection) {
  try {
  OutputStream outputToSocket = incomingConnection.getOutputStream();
  InputStream inputFromSocket = incomingConnection.getInputStream();

先獲取同Socket相關聯的流outputToSocket和InputStream
中outputToSocket是要返回給客戶端Socket的流
是客戶端發來的請求,在這裡就是檔案路徑,即"RemoteFile.txt"

  BufferedReader streamReader =
  new BufferedReader(new InputStreamReader(inputFromSocket));

先要將InputStream轉換到BufferedReader中

  FileReader fileReader = new FileReader(new File(streamReader.readLine()));
BufferedReader中讀出檔案路徑,建立新物件FileReader

  BufferedReader bufferedFileReader = new BufferedReader(fileReader);

次建立BufferedReader物件,這一次它讀取得是檔案裡面的內容

  PrintWriter streamWriter =
  new PrintWriter(OutputStream);

Socket的outputToSocket流包裝進PrintWriter 以使我們能夠傳送檔案資訊到客戶端

  String line = null;
  while ((line = bufferedFileReader.readLine()) != null) {
  streamWriter.println(line);
  }
bufferedFileReader中讀出檔案資訊,再經由streamWriter輸出到客戶端

  fileReader.close();
  streamWriter.close();//注意Socket的兩個流關閉的順序
  streamReader.close();
成之後關閉所有流

  } catch (Exception e) {
  System.out.println("Error handling a client: " + e);
  }
}


請注意完成所有操作之後關閉流的順序,streamWriter的關閉在streamReader的關閉之前。這不是偶然的,假如將關閉次序顛倒過來,客戶端將不會獲取到任何檔案資訊,你可以除錯一下看看.這是為什麼呢?原因是如果你在關閉 streamWriter 之前關閉 streamReader,則你可以以往 streamWriter中寫任何東西,但沒有任何資料可以透過通道(通道被關閉了).但奇怪的是,我不是已經在之前的streamWriter.println()中輸出了嗎?難道非要等到所有的流關閉之後輸出到客戶端的資訊的東西才能到達?我試著將
streamWriter.close();
streamReader.close();
遮蔽掉,看是否依然能夠實現正常的通訊,結果發現不行,程式當機.可能是因為通道沒有閉合導致的.那麼至少可以說明,只有將通道按某種順序正常關閉,才能完成通訊資料的傳輸,否則客戶端收不到資訊.

最後依然是總結一下建立伺服器端程式的步驟
1.用一個你想讓它偵聽傳入客戶機連線的埠(比如程式中的3000)來例項化一個 ServerSocket(如有問題則丟擲 Exception)。
2.迴圈呼叫ServerSocket的accept()以監聽連線
3.獲取客戶端的Socket流以進行讀寫操作
4.包裝流
5.對客戶端的Socket進行讀寫
6.關閉開啟的流(切記,永遠不要在關閉 Writer 之前關閉 Reader),完成通訊

下面是
RemoteFileServer 的程式碼清單

import java.io.*;
import java.net.*;

public class RemoteFileServer {
  int listenPort;
  public RemoteFileServer(int aListenPort) {
  listenPort = aListenPort;
  }
  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);
  }
  }
  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(outputToSocket);
  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);
  }
  }
  public static void main(String[] args) {
  RemoteFileServer server = new RemoteFileServer(3000);
  server.acceptConnections();
  }
}

好了,Socket總算是了


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-998849/,如需轉載,請註明出處,否則將追究法律責任。

相關文章