第七章 异常处理
* 异常处理机制中的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 }
原文地址:https://www.cnblogs.com/shijinglu2018/p/9153292.html