对几个Java的基础知识作一下补充。
一.异常 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(′ ′); } fos.close(); }
private static void readLine(byte line[])throws IOException{ int i=0,b=0; while((i<(lineLength-1))&&((b=System.in.read())!=′ ′)){ 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 + " "); 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 + " "); } 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(); } }
好了,Socket总算是入门了
|