第七章 异常处理
* 异常处理机制中的try-catch
* 语法:
* try{
* 代码片段
* }catch(XXXException e){
* 当try中的代码片段出现了XXXException后的处理代码
* }
* try中的代码片段报错行以下的代码都不会运行
* 应当有一个好习惯,在最后一个catch处捕获Exception
* 这样能避免因为一个未捕获的异常导致程序中断
* finally
* finally块是异常处理机制的最后一块,只能跟在最后一个catch之后或直接跟在try之后
* finally可以保证只要程序执行到try代码块中,无论try中的代码是否出抛出异常,finally中的代码都会执行
* 所以通常会将无关乎异常,必定要执行的代码放在finally中
*
* 例如:IO中的关闭流操作,就适合放在finally中
* JDK7之后推出了一个特性,自动关闭
* 可以将流这种需要最后调用close方法释放资源的操作从繁琐的try-catch-finally中简化
* 语法:
* try(创建最终需要关闭的对象){
* 正常代码块
* }catch(XXXException e){
* 异常处理代码
* }
* 凡是实现了AutoCloseable接口都可以被自动关闭
* 所有的流以及RandomAccessFile都实现了该接口
* 自动关闭是编译器认可,最终编译后的class文件中会被改为在finally中关闭
* 样子参考FinallyDemo2
throw 异常的抛出
* 当符合语法却不符合业务逻辑时,需要主动抛出异常
1 public void setAge(int age) throws IllegalAgeException {
2 if(age>0&&age<100) {
3 this.age = age;
4 }else {
5 throw new IllegalAgeException("年龄不合法");
6 }
7 }
* 当一个方法中使用throw抛出一个异常时,就要在当前方法上使用throws声明该异常的抛出
* 以便于通知调用方法者在调用方法时要处理异常
* 只有RuntimeException及其子类在抛出时编译器不要求必须写throws声明
* 其他异常则必须声明,否则编译不通过
* 当调用一个含有throws声明异常抛出的方法时编译器会提示必须处理该异常
* 处理的方式有两种:
* 1:使用try-catch捕获并处理异常
* 2:在当前方法上继续使用throws声明将该异常抛出
throws的重写规则:
* 子类在重写父类含有throws声明异常抛出的方法时:
* 1,重写父类方法可以原样抛出异常
* 2,重写父类方法可以什么都不抛
* 3,重写父类方法可以抛出父类的部分异常
* 4,重写父类方法可以抛出父类抛出异常的子类型异常
*
*不允许抛出额外异常(即抛出的异常与父类异常之间没有(继承)关系)
*不允许抛出父类抛出异常的父类型异常
自定义异常
*通常是用来说明业务逻辑错误,一定要把异常名称(类名)写明白
* 继承Exception||其他异常,以便编译器识别
* 重写父类构造方法
【案例】
public class IllegalAgeException extends Exception{
private static final long serialVersionUID = 1L;
public IllegalAgeException() {
super();
}
public IllegalAgeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public IllegalAgeException(String message, Throwable cause) {
super(message, cause);
}
public IllegalAgeException(String message) {
super(message);
}
public IllegalAgeException(Throwable cause) {
super(cause);
}
}
java异常API
RuntimeException
java异常可以分为可检测异常,非检测异常
可检测异常:对于声明抛出异常的任何方法,java编译器都会验证并强制执行声明规则或处理规则,不捕捉这个异常编译器就通不过(报红线)
非检测异常:不遵循处理规则或声明规则,产生异常时,编译器不会检查是否已经解决类这样一个异常。
RuntimeException类属于非检测异常,常见的有:
IllgalArgumentException:传递了一个不合法或不正确的参数
NullPointerException:空指针异常
ArrayIndexOutOfBoundsException:数组下标越界异常
ClassCastException:类强制转换异常
NumberFormatException:数字格式异常
void printStackTrace()
Throwable中定义的一个方法:输出错误信息,跟踪异常发生时执行堆栈的内容
第八章 TCP通讯
TCP 可靠性传输协议(java开发服务端常用)
UDP 不可靠传输协议
【聊天室项目】
一、客户端:
1,声明Socket变量socket;
2,创建构造方法,在构造方法中初始化socket,设置两个参数:1,服务端IP地址,2,访问端口;
(实例化的过程就是向服务端发起访问申请的过程,若服务端没有响应,实例化会抛出异常)
二、服务端:
1,声明ServerSocket变量server
2,创建构造方法,在构造方法中初始化server,server初始化时会向系统传递并申请端口号,若端口号已被占用,程序会抛出端口被占用的异常,这时我们需要更换端口号
3,在main方法中new一个实例对象,用对象调start方法
4,在start方法中,声明一个socket用于接收server.acccpt()方法监听服务端口的结果返回值;一旦有一个客户端通过该端口建立了连接,该方法会返回一个socket实例,通过该socket实际即可与该客户端建立连接;
三、客户端
1,在main中new一个实例对象,调用start方法
2,在start方法中,创建输出流,用于向服务端传递消息(可循环传递)
3,在start方法中,创建输入流,用于接收服务端传送过来的消息
四、服务端
1,在start方法中,创建输入流,用于接收客户端传递的消息(可循环接收)
2,在start方法中,创建输出流,用于回复客户端的消息(原样返回)(可循环发送)
3,这两个循环和main方法中循环监听端口并根据客户端连接抛出socket实例的两种循环会出现冲突,所以需要设计一条新线程,使之并发处理每一个客户连接后的通讯socket输入输出循环,在start方法中调用线程
五、客户端
1,循环读取服务端发回的消息时,会因为输入消息的IO阻塞,读取不及时,需要设计并发线程异步执行,创建内部类并实现runnable,重写run方法
2,将上一步创建的输入流语句剪切至run方法中,循环接收服务端发送过来的消息
3,在start方法中,实例化ServerHandler ,启动线程并调用
六、服务端
1,将原样返回给客户端的消息传递给所有客户端(即让所有客户端都可以看到其他客户端发送的消息,达到相互通讯的目的),根据内部类可以访问外部类成员变量的规则,我们在外部类创建成员数组变量allOut[]arr,将要发送给客户端的输出流存放在该数组中,遍历每一个元素.println(),则所有客户端都可接收到该输出流文件
2,处理当一个客户端断开连接后的情况(因为是必走的步骤,所以放在finally{}中执行):
2.1)将断开的客户端的pw从数组中删除
思路1:遍历数组,查询数组元素与断开的pw是否==,若true,将数组最后一个元素赋值到断开的pw处,再将最后一个元素缩容掉
思路2:遍历数组,查询数组元素与断开的pw是否!=,若true,则表明该元素未断开,将未断开的元素添加到另外的空数组中,最后将包含所有的未断开的数组复制到原数组
2.2)关闭socket释放资源:socket.close();
3,因为有多个线程可能同时操作allOut数组,如遍历输出,扩容,缩容等,这样就导致了线程之间不安全,所以要考虑用synchronized()来解决这个问题。互斥锁;
1 package socket;
2
3 import java.io.BufferedReader;
4 import java.io.BufferedWriter;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.io.InputStreamReader;
8 import java.io.OutputStream;
9 import java.io.OutputStreamWriter;
10 import java.io.PrintWriter;
11 import java.net.Socket;
12 import java.net.UnknownHostException;
13 import java.util.Scanner;
14 /**
15 * 聊天室客户端
16 * @author soft01
17 */
18 public class Client {
19 /*
20 * java.net.Socket
21 * Socket翻译为套接字
22 * 封装了TCP通讯协议的细节,使我们可以通过两条流与远端进行双向数据传输,达到通讯的目的
23 */
24 private Socket socket;
25 Scanner scan = new Scanner(System.in);
26 /**
27 * 客户端的构造方法
28 */
29 public Client() {
30 try {
31 /*
32 * 实例化 Socker时通常需要传入两个参数
33 * 参数1:服务端的IP地址
34 * 参数2:服务端的端口号
35 * 通过IP地址可以找到服务端计算机
36 * 通过端口可以找到运行在服务端计算机上的服务端应用程序
37 * 在这里实例化Scoket的过程就是连接服务端的过程
38 * 若服务端没有响应,则这里实例化会抛出异常
39 */
40 System.out.println("正在连接服务端……");
41 socket=new Socket("178.10.1.94",8088);//第一个参数是服务端网址,第二个参数是服务端的端口
42 System.out.println("与服务端建立连接!");
43 } catch (UnknownHostException e) {
44 e.printStackTrace();
45 } catch (IOException e) {
46 e.printStackTrace();
47 }
48 }
49 /**
50 * 客户端的启动方法
51 */
52 public void start() {
53 try {
54 //启动线程,读取服务端发送过来的消息
55 ServerHandler handler = new ServerHandler();
56 Thread t = new Thread(handler);
57 t.start();
58 /*
59 * Socket提供的方法:
60 * OutputStream getOutputStream()
61 * 该方法会返回一个字节输出流,通过这个流写出的字节都会通过网络发送给远端计算机
62 *
63 * 利用流连接,名可以很方便的按行写出一个字符串
64 */
65 OutputStream out = socket.getOutputStream();
66 OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8");
67 BufferedWriter bw = new BufferedWriter(osw);
68 PrintWriter pw = new PrintWriter(bw,true);
69 String str;
70 do {
71 System.out.println("请输入要发送的信息:");
72 str = scan.nextLine();
73 pw.println(str);
74 }while(!"exit".equals(str));//out.write(str.getBytes());
75 } catch (Exception e) {
76 e.printStackTrace();
77 }
78 scan.close();
79 }
80 public static void main(String[] args) {
81 Client client =new Client();
82 client.start();
83 }
84 /**
85 * 该线程用于读取服务端发送过来的消息并输出到控制台
86 * @author soft01
87 */
88 private class ServerHandler implements Runnable{
89 public void run() {
90 try {
91 InputStream is = socket.getInputStream();
92 InputStreamReader isr = new InputStreamReader(is,"utf-8");
93 BufferedReader br = new BufferedReader(isr);
94 String line;
95 while((line = br.readLine())!=null) {
96 System.out.println(line);
97 };
98 } catch (Exception e) {
99 e.printStackTrace();
100 }
101 }
102 }
103 }
104
105
106
107 【案例】服务端
108 package socket;
109
110 import java.io.BufferedReader;
111 import java.io.BufferedWriter;
112 import java.io.IOException;
113 import java.io.InputStream;
114 import java.io.InputStreamReader;
115 import java.io.OutputStream;
116 import java.io.OutputStreamWriter;
117 import java.io.PrintWriter;
118 import java.net.ServerSocket;
119 import java.net.Socket;
120 import java.util.Arrays;
121 /**
122 * 聊天室服务端
123 * @author soft01
124 */
125 public class Server {
126 /*
127 * 运行在服务端的ServerSocket主要有两个作用:
128 * 1:向系统申请服务端口,客户端就可以通过这个端口与服务端建立连接;
129 * 2:监听该服务端口,一旦客户端通过该端口请求建立连接,
130 * 那么马上就会实例化一个Socket,通过这个Socket就可以与该客户端进行数据通讯了
131 */
132 private ServerSocket server;
133 /*
134 * 由于ClientHandler时Server的内部类,那么所有的ClientHandler都可以访问到该属性,为此可以作为各ClientHandler的共享数据使用
135 */
136 private PrintWriter [] allOut = new PrintWriter[0];
137 /** 构造方法 */
138 public Server (){
139 try {
140 /*
141 * 实例化的过程需要传入向体同申请的端口号
142 * 若端口已经被系统其他程序占用则会抛出地址被占用的异常,这是我们需要更换其他端口;
143 */
144 System.out.println("正在启动服务端……");
145 server= new ServerSocket(8088);
146 System.out.println("服务端启动完毕");
147 } catch (IOException e) {
148 e.printStackTrace();
149 }
150 }
151 /** 启动方法 */
152 public void start() {
153 try {
154 /*
155 * ServerSocket提供两种方法:
156 * 1:Socket accept()
157 * 该方法是一个阻塞方法,作用是监听申请的服务端口,等待客户端的连接
158 * 一旦一个客户端通过该端口建立连接,那么该方法会返回一个Socket实例,
159 * 通过该Socket实例即可与该客户端进行通讯
160 */
161 while(true) {
162 System.out.println("等待客户端连接……");
163 Socket socket = server.accept();
164 System.out.println("一个客户端连接了!");
165 /**启动一个线程,将该socket传递给线程,使其处理该客户端交互 */
166 ClientHandler ch = new ClientHandler(socket);
167 Thread t1 = new Thread(ch);
168 t1.start();
169 }
170
171 }catch(Exception e) {
172 e.printStackTrace();
173 }
174 }
175
176 public static void main(String[] args) {
177 Server server = new Server();
178 server.start();
179 }
180
181 //内部类
182 private class ClientHandler implements Runnable{
183 private Socket socket;
184 /**构造方法,将socket通过传递参数形式赋值给socket */
185 ClientHandler(Socket socket){
186 this.socket=socket;
187 }
188 public void run() {
189 PrintWriter pw = null;
190 try {
191 /* Socket提供的: InputStream getIntputStream() 通过该方法返回的输入流可以读取到远端发送过来的数据 */
192 InputStream is = socket.getInputStream();
193 InputStreamReader isr= new InputStreamReader(is,"utf-8");
194 BufferedReader br = new BufferedReader(isr);
195 /*通过socket获取输出流,用于给客户端发消息 */
196 OutputStream os = socket.getOutputStream();
197 OutputStreamWriter osw = new OutputStreamWriter(os,"utf-8");
198 BufferedWriter bw = new BufferedWriter(osw);
199 pw = new PrintWriter(bw,true);
200 //将当前客户端对应的输出流存入共享数组
201 synchronized(allOut) {
202 //1,先扩容
203 allOut = Arrays.copyOf(allOut, allOut.length+1);
204 //2,将输出流放入数组最后一位
205 allOut[allOut.length-1] =pw;
206 }
207 Thread t = Thread.currentThread();
208 String line="";
209 do {
210 line = br.readLine();
211 // System.out.println("客户端说:"+line);
212 // //将内容发送给客户端
213 // pw.println("服务端说:"+line);
214 //3,遍历数组,输出
215 synchronized(allOut) {
216 for(int i=0;i<allOut.length;i++) {
217 allOut[i].println(t.getName()+"说:"+line);
218 }
219 }
220 if(line==null) {
221 System.out.println("一个客户端断开了连接。");
222 }
223 }while(line!=null);
224
225 } catch (Exception e) {
226 e.printStackTrace();
227 }finally {
228 //处理客户端断开连接后的操作:
229 //将当前客户端的输出流从allOut中删除
230 synchronized (allOut) {
231 for(int i=0;i<allOut.length;i++) {
232 if(allOut[i]==pw) {
233 allOut[i]=allOut[allOut.length-1];
234 allOut = Arrays.copyOf(allOut, allOut.length-1);
235 break;
236 }
237 }
238 }
239 //关闭流,释放资源
240 try {
241 socket.close();
242 } catch (IOException e) {
243 e.printStackTrace();
244 }
245 }
246 }
247 }
248 }