异常和TCP通讯

时间:2023-03-09 17:32:59
异常和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 异常的抛出
* 当符合语法却不符合业务逻辑时,需要主动抛出异常

 public void setAge(int age) throws IllegalAgeException {
if(age>0&&age<100) {
this.age = age;
}else {
throw new IllegalAgeException("年龄不合法");
}
}

* 当一个方法中使用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()来解决这个问题。互斥锁;

 package socket;

 import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* 聊天室客户端
* @author soft01
*/
public class Client {
/*
* java.net.Socket
* Socket翻译为套接字
* 封装了TCP通讯协议的细节,使我们可以通过两条流与远端进行双向数据传输,达到通讯的目的
*/
private Socket socket;
Scanner scan = new Scanner(System.in);
/**
* 客户端的构造方法
*/
public Client() {
try {
/*
* 实例化 Socker时通常需要传入两个参数
* 参数1:服务端的IP地址
* 参数2:服务端的端口号
* 通过IP地址可以找到服务端计算机
* 通过端口可以找到运行在服务端计算机上的服务端应用程序
* 在这里实例化Scoket的过程就是连接服务端的过程
* 若服务端没有响应,则这里实例化会抛出异常
*/
System.out.println("正在连接服务端……");
socket=new Socket("178.10.1.94",8088);//第一个参数是服务端网址,第二个参数是服务端的端口
System.out.println("与服务端建立连接!");
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端的启动方法
*/
public void start() {
try {
//启动线程,读取服务端发送过来的消息
ServerHandler handler = new ServerHandler();
Thread t = new Thread(handler);
t.start();
/*
* Socket提供的方法:
* OutputStream getOutputStream()
* 该方法会返回一个字节输出流,通过这个流写出的字节都会通过网络发送给远端计算机
*
* 利用流连接,名可以很方便的按行写出一个字符串
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8");
BufferedWriter bw = new BufferedWriter(osw);
PrintWriter pw = new PrintWriter(bw,true);
String str;
do {
System.out.println("请输入要发送的信息:");
str = scan.nextLine();
pw.println(str);
}while(!"exit".equals(str));//out.write(str.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
scan.close();
}
public static void main(String[] args) {
Client client =new Client();
client.start();
}
/**
* 该线程用于读取服务端发送过来的消息并输出到控制台
* @author soft01
*/
private class ServerHandler implements Runnable{
public void run() {
try {
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is,"utf-8");
BufferedReader br = new BufferedReader(isr);
String line;
while((line = br.readLine())!=null) {
System.out.println(line);
};
} catch (Exception e) {
e.printStackTrace();
}
}
}
} 【案例】服务端
package socket; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
/**
* 聊天室服务端
* @author soft01
*/
public class Server {
/*
* 运行在服务端的ServerSocket主要有两个作用:
* 1:向系统申请服务端口,客户端就可以通过这个端口与服务端建立连接;
* 2:监听该服务端口,一旦客户端通过该端口请求建立连接,
* 那么马上就会实例化一个Socket,通过这个Socket就可以与该客户端进行数据通讯了
*/
private ServerSocket server;
/*
* 由于ClientHandler时Server的内部类,那么所有的ClientHandler都可以访问到该属性,为此可以作为各ClientHandler的共享数据使用
*/
private PrintWriter [] allOut = new PrintWriter[0];
/** 构造方法 */
public Server (){
try {
/*
* 实例化的过程需要传入向体同申请的端口号
* 若端口已经被系统其他程序占用则会抛出地址被占用的异常,这是我们需要更换其他端口;
*/
System.out.println("正在启动服务端……");
server= new ServerSocket(8088);
System.out.println("服务端启动完毕");
} catch (IOException e) {
e.printStackTrace();
}
}
/** 启动方法 */
public void start() {
try {
/*
* ServerSocket提供两种方法:
* 1:Socket accept()
* 该方法是一个阻塞方法,作用是监听申请的服务端口,等待客户端的连接
* 一旦一个客户端通过该端口建立连接,那么该方法会返回一个Socket实例,
* 通过该Socket实例即可与该客户端进行通讯
*/
while(true) {
System.out.println("等待客户端连接……");
Socket socket = server.accept();
System.out.println("一个客户端连接了!");
/**启动一个线程,将该socket传递给线程,使其处理该客户端交互 */
ClientHandler ch = new ClientHandler(socket);
Thread t1 = new Thread(ch);
t1.start();
} }catch(Exception e) {
e.printStackTrace();
}
} public static void main(String[] args) {
Server server = new Server();
server.start();
} //内部类
private class ClientHandler implements Runnable{
private Socket socket;
/**构造方法,将socket通过传递参数形式赋值给socket */
ClientHandler(Socket socket){
this.socket=socket;
}
public void run() {
PrintWriter pw = null;
try {
/* Socket提供的: InputStream getIntputStream() 通过该方法返回的输入流可以读取到远端发送过来的数据 */
InputStream is = socket.getInputStream();
InputStreamReader isr= new InputStreamReader(is,"utf-8");
BufferedReader br = new BufferedReader(isr);
/*通过socket获取输出流,用于给客户端发消息 */
OutputStream os = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os,"utf-8");
BufferedWriter bw = new BufferedWriter(osw);
pw = new PrintWriter(bw,true);
//将当前客户端对应的输出流存入共享数组
synchronized(allOut) {
//1,先扩容
allOut = Arrays.copyOf(allOut, allOut.length+1);
//2,将输出流放入数组最后一位
allOut[allOut.length-1] =pw;
}
Thread t = Thread.currentThread();
String line="";
do {
line = br.readLine();
// System.out.println("客户端说:"+line);
// //将内容发送给客户端
// pw.println("服务端说:"+line);
//3,遍历数组,输出
synchronized(allOut) {
for(int i=0;i<allOut.length;i++) {
allOut[i].println(t.getName()+"说:"+line);
}
}
if(line==null) {
System.out.println("一个客户端断开了连接。");
}
}while(line!=null); } catch (Exception e) {
e.printStackTrace();
}finally {
//处理客户端断开连接后的操作:
//将当前客户端的输出流从allOut中删除
synchronized (allOut) {
for(int i=0;i<allOut.length;i++) {
if(allOut[i]==pw) {
allOut[i]=allOut[allOut.length-1];
allOut = Arrays.copyOf(allOut, allOut.length-1);
break;
}
}
}
//关闭流,释放资源
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}