一.異常
Java對異常的處理同Delphi一樣,不是刻意的去避免它的發生,而是等它發生後去補救.
Delphi的異常處理簡單來說就是一下語句
Try
Except//異常發生後就轉入此處執行
Finally//不管異常發不發生,都轉入此處運行
End
與此相類似,Java的異常處理的基本形式如下
try{
}catch(ExceptionType1 e){
file&://對/異常情況1的處理
}catch(ExceptionType2 e){
file&://對/異常情況2的處理
throw(e)//拋出異常,和Delphi中的raise是一回事
}
要補充的是,對大多數的異常,假如你要在正常運行的程序中而不是捕捉異常的程序中明確的拋出,Java的編譯器需要你事先對你要拋出的異常作聲明,否則不允許編譯通過.這個任務是由throws來完成的.
二.Java的輸入輸出流
2.1 輸出
System.out.print file&://這/裡out是一個靜態方法哦
System.out.println
System.err.print file&://err/和out一樣也是標准輸出,至於有什麼不同,我目前還不清楚
System.err.println
2.2 輸入
System.in.read()
2.3 文件的操作
只需要幾個帶注釋的例子就可以了。
第一個是一個顯示文件基本信息的程序
import java.io.*;//調入和io相關的類
class fileinfo{
file&://注/意,main函數一定是靜態方法
public static void main(String args[])throws IOException{
File fileToCheck;//使用文件對象創建實例
if (args.length>0){
for (int i=0;i<args.length;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<(lineLength-1))&&((b=System.in.read())!='\n')){
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.net.*;//java.net 包提供套接字工具。
public class RemoteFileClient {
protected String hostIp;
protected int hostPort;
protected BufferedReader socketReader;//負責讀數據的對象
protected PrintWriter socketWriter;//負責寫數據的對象
file&://類/的構造器有兩個參數:遠程主機的 IP 地址(hostIp)和端口號(hostPort)各一個.構造器將它們賦給實例變量
public RemoteFileClient(String aHostIp, int aHostPort) {
hostIp = aHostIp;
hostPort = aHostPort;
}
public static void main(String[] args) {
}
file&://連/接到遠程服務器
public void setUpConnection() {
}
file&://向/遠程服務器請求文件信息
public String getFile(String fileNameToGet) {
}
file&://從/遠程服務器上斷開
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));
file&://把/Socket的InputStream包裝進BufferedReader 以使我們能夠讀取流的行
socketWriter = new PrintWriter(outToServerStream);
file&://把/Socket的OutputStream包裝進PrintWriter 以使我們能夠發送文件請求到服務器
} catch (UnknownHostException e) {
System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);
file&://對/Socket對象創建錯誤的異常處理
} catch (IOException e) {
System.out.println("Error setting up socket connection: " + e);
file&://對/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");
file&://既/然已經發送到服務器去了,那我們都要等待響應,這裡的程序就是等待服務器把我們所需要的文件內容傳過來
} 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);
}
file&://不/斷監聽直到來了一個連接請求,然後交由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();
file&://首/先獲取同Socket相關聯的流outputToSocket和InputStream
file&://其/中outputToSocket是要返回給客戶端Socket的流
file&://InputStream/是客戶端發來的請求,在這裡就是文件路徑,即"RemoteFile.txt"
BufferedReader streamReader =
new BufferedReader(new InputStreamReader(inputFromSocket));
file&://首/先要將InputStream轉換到BufferedReader中
FileReader fileReader = new FileReader(new File(streamReader.readLine()));
file&://從/BufferedReader中讀出文件路徑,建立新對象FileReader
BufferedReader bufferedFileReader = new BufferedReader(fileReader);
file&://再/次建立BufferedReader對象,這一次它讀取得是文件裡面的內容
PrintWriter streamWriter =
new PrintWriter(OutputStream);
file&://把/Socket的outputToSocket流包裝進PrintWriter 以使我們能夠發送文件信息到客戶端
String line = null;
while ((line = bufferedFileReader.readLine()) != null) {
streamWriter.println(line);
}
file&://從/bufferedFileReader中讀出文件信息,再經由streamWriter輸出到客戶端
fileReader.close();
streamWriter.close();//注意Socket的兩個流關閉的順序
streamReader.close();
file&://完/成之後關閉所有流
} 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();
}
}