• 异常和TCP通讯


    第七章 异常处理


    * 异常处理机制中的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 }
    成年人的世界没有那么多的童话,也没有那么多的逆袭。
  • 相关阅读:
    MKMapViewDelegate
    MKMapView
    正则表达式随手篇
    c#多线程
    sql模糊查询效率
    c#多线程,进度条,实时给前台发送数据
    如何使用CocoaPods
    TabBar自定义方式(一)
    Spring MVC 学习资料
    优化Myeclipse10 Building Workspace速度慢等问题
  • 原文地址:https://www.cnblogs.com/shijinglu2018/p/9153292.html
Copyright © 2020-2023  润新知