Java阶段一Day20

news2025/6/17 10:10:30

Java阶段一Day20

文章目录

  • Java阶段一Day20
    • 异常
      • throw
      • throws
      • 检测异常和非检测异常
      • 常见的RuntimeException
      • 常见的catch中的方法(ExceptionAPI)
      • 自定义异常
    • 网络
      • C/S 和 B/S
      • TCP 和 UDP
      • 三次握手
      • 四次挥⼿
  • 教师总结
    • 新单词
    • 异常处理
      • throw关键字
      • throws关键字
      • throws的重写规则
      • 异常分类
        • Java异常可以分为可检测异常,非检测异常:
        • 常见的RuntimeException子类
      • 异常API
      • 自定义异常
      • 总结:
          • java异常处理机制:
    • 网络编程
      • 网络应用模型
      • 可靠传输与不可靠传输
      • Socket与ServerSocket
          • 如果我们把Socket比喻为电话,那么ServerSocket相当于是某客服中心的总机。
      • 与服务端建立连接案例
      • 客户端与服务端完成第一次通讯(发送一行字符串)
          • Socket提供了两个重要的方法:
        • 原理图
      • 实现客户端循环发消息给服务端
        • 需要注意的几个点:
      • TCP进行连接时的三次握手
      • TCP断开连接时的四次挥手
      • 多客户端链接

异常

throw

当我们使用 throw 主动对外抛出一个异常时,除了RuntimeException以外的其他异常都需要在方法上 显示的声明 throws 来对外告知要抛出该异常

throws

当调用一个含有 throws 声明异常抛出的方法时,编译器要求必须处理该异常。处理方式有两种,遵循责任制原则,可以在这句代码所在方法继续throws向上抛出,也可以使用 try-catch主动捕获并处理该异常

永远不要在main方法上声明throws,这是种不负责的表现,虚拟机发现异常会直接kill进程

当子类重写超类含有throws声明异常抛出的方法时,对throws的重写规则:

  • 允许原样重写方法
  • 允许不再抛任何异常
  • 允许抛出一部分异常
  • 允许抛超类方法声明抛出异常的子类型异常
  • 不允许抛出超类方法抛出异常的超类型异常(不允许越抛越大)
  • 不允许抛出额外异常(超类方法没有声明且不存在继承关系)

检测异常和非检测异常

  • 可检测异常:可检测异常经编译器验证,对于声明抛出异常的任何方法,编译器将强制执行处理或声明规则,不捕捉这个异常,编译器就通不过,不允许编译
  • 非检测异常:非检测异常不遵循处理或者声明规则。在产生此类异常时,不一定非要采取任何适当操作,编译器不会检查是否已经解决了这样一个异常
  • RuntimeException类 属于非检测异常,因为普通JVM操作引起的运行时异常随时可能发生,此类异常一般是由特定操作引发。但这些操作在java应用程序中会频繁出现。因此它们不受编译器检查与处理或声明规则的限制

常见的RuntimeException

  • IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数
  • NullPointerException:当应用程序试图在需要对象的地方使用 null 时,抛出该异常
  • ArraylndexOutOfBoundsException:当使用的数组下标超出数组允许范围时,抛出该异常
  • ClassCastException:当试图将则象强制转换为不是实例的子类时,抛出该异常
  • NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常

常见的catch中的方法(ExceptionAPI)

  • printStackTrace:Throwable中定义了一个方法可以输出错误信息,用来跟踪异常事件发生时执行堆栈的内容。该方法定义为:void printStackTrace()
  • getMessage:获取字符消息,提示用户端,日志输出因为什么原因报错,一般在记录日志时使用

自定义异常

  • 通常用来声明那些在项目中满足语法但是不满足业务场景的问题
  • 自定义异常需要做一下几件事:
    1. 类名要做到见名知义
    2. 继承Exception(直接或间接继承都可以)
    3. 提供超类异常提供的所有构造器

网络

C/S 和 B/S

  • C/S
客户机
服务器
  • B/S
浏览器
服务器1
服务器2
服务器3

TCP 和 UDP

比较TCPUDP
是否面向链接
是否可靠
是否有状态
传输效率
首部开销20~608
传输形式字节流数据报文段
是否支持广播和多播

三次握手

  • 刚开始时:客户端closed状态,服务端listen状态
  • 第一次握手:客户端给服务器发送SYN报⽂,并指明客户端的初始化序列ISN(C)此时客户端处于SYN_Send状态
  • 第⼆次握⼿:服务器收到客户端传送的SYN报⽂后,会以⾃⼰的SYN报⽂作为回应,并且指定⾃⼰的初始化序列ISN(S),同时会把客户端的ISN+1作为ACK的值,表示⾃⼰已经收到了客户端的SYN,此服务端处于SYN_REVD状态
  • 第三次握⼿:客户端收到了服务端的SYN后,同时把服务端的ISN+1作为ACK报⽂,表示已经收到了服务器的SYN,此时客户端处于establised服务器收到客户端的ACK后,也处于establised,双⽅建⽴连接确认双⽅的发送接受能⼒正常指定⾃⼰的初始化序列号,为后⾯的可靠传输做准备

四次挥⼿

  • 刚开始处在establised,假如客户端先发起关闭请求
  • 第⼀次挥手:客户端发送⼀个FIN报⽂,报⽂中指定⼀个序列号,此时客户端处于CLOSED_WAIT1
  • 第⼆次挥手:服务端收到FIN报⽂,将报⽂中的序列号+1,作为ACK报⽂发送给客户端,表明⾃⼰收到CLOSE_WAIT2
  • 第三次挥手:如果服务端也想断开链接,同样发送⼀个FIN报⽂给客户端,指定⼀个序列号,此时服务端LAST_ACK
  • 第四次挥手:客户端收到FIN,同样发送ACK报⽂,客户端TIME_WAIT,过⼀阵确保服务端收到ACK报⽂,才会进⼊CLOSED服务端收到ACK,处于关闭状态,CLOSED
  • TIME_WAIT确保服务端收到了ACK报⽂,如果没收到,服务端会重新发送FIN给客户端,客户端搜
  • 到两次FIN就知道之前的ACK报⽂丢失了,然后再次发送ACK等待时间⾄少⼀个报⽂的来回时间,设置⼀个时间等待计时器,过了计时器没收到FIN报⽂,代表成功接收ACK,则进⼊CLOSED

教师总结

新单词

  • Client-客户端
  • Server-服务端
  • Socket-计算机中翻译为套接字,日常为插座
  • illegal-非法的

异常处理

throw关键字

throw用来对外主动抛出一个异常,通常下面两种情况我们主动对外抛出异常:

  • 当程序遇到一个满足语法,但是不满足业务要求时,可以抛出一个异常告知调用者。
  • 程序执行遇到一个异常,但是该异常不应当在当前代码片段被解决时可以抛出给调用者。
package exception;

/**
 * 测试异常的抛出
 */
public class Person {
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) throws Exception {
        if(age<0||age>100){
            //使用throw对外抛出一个异常
            throw new RuntimeException("年龄不合法!");
        }
        this.age = age;
    }
}
package exception;

/**
 * throw关键字,用来对外主动抛出一个异常。
 * 通常下面两种情况我们主动对外抛出异常:
 * 1:当程序遇到一个满足语法,但是不满足业务要求时,可以抛出一个异常告知调用者。
 * 2:程序执行遇到一个异常,但是该异常不应当在当前代码片段被解决时可以抛出给调用者。
 */
public class ThrowDemo {
    public static void main(String[] args) {
        Person p = new Person();
        p.setAge(10000);//符合语法,但是不符合业务逻辑要求。
        System.out.println("此人年龄:"+p.getAge());
    }
}

throws关键字

throws用来在声明方法时同时声明该方法可能抛出的异常,用于通知调用方添加处理该异常的手段。

当一个方法中使用throw抛出一个非RuntimeException的异常时,就要在该方法上使用throws声明这个异常的抛出。此时调用该方法的代码就必须处理这个异常,否则编译不通过。

package exception;

/**
 * 测试异常的抛出
 */
public class Person {
    private int age;

    public int getAge() {
        return age;
    }

    /**
     * 当一个方法使用throws声明异常抛出时,调用此方法的代码片段就必须处理这个异常
     */
    public void setAge(int age) throws Exception {
        if(age<0||age>100){
            //使用throw对外抛出一个异常
//            throw new RuntimeException("年龄不合法!");
            //除了RuntimeException之外,抛出什么异常就要在方法上声明throws什么异常
            throw new Exception("年龄不合法!");
        }
        this.age = age;
    }
}

当我们调用一个含有throws声明异常抛出的方法时,编译器要求我们必须处理这个异常,否则编译不通过。 处理手段有两种:

  • 使用try-catch捕获并处理这个异常
  • 在当前方法(本案例就是main方法)上继续使用throws声明该异常的抛出给调用者解决。 具体选取那种取决于异常处理的责任问题。
package exception;

/**
 * throw关键字,用于主动对外抛出一个异常
 */
public class ThrowDemo {
    public static void main(String[] args){
        System.out.println("程序开始了...");
        try {
            Person p = new Person();
            /*
                当我们调用一个含有throws声明异常抛出的方法时,编译器要求
                我们必须添加处理异常的手段,否则编译不通过.而处理手段有两种
                1:使用try-catch捕获并处理异常
                2:在当前方法上继续使用throws声明该异常的抛出
                具体用哪种取决于异常处理的责任问题
             */
            p.setAge(100000);//典型的符合语法,但是不符合业务逻辑要求
            System.out.println("此人年龄:"+p.getAge()+"岁");
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("程序结束了...");
    }
}

注意,永远不应当在main方法上使用throws!!

throws的重写规则

当一个子类重写超类含有throws声明异常抛出的方法时,针对throws的重写规则

  • 允许不再抛出任何异常
  • 允许仅抛出部分异常
  • 允许抛出超类方法声明抛出异常的子类型异常
  • 不允许抛出额外异常(超类方法没有声明的且不存在继承关系的)
  • 不允许抛出超类方法抛出异常的超类型异常

子类方法的几种重写分别解开注释就可以观察编译器是否允许该种重写方式

package exception;

import java.awt.*;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;

/**
 * 当子类重写超类含有throws声明异常抛出的方法时,对throws的重写规则
 */
public class ThrowsDemo {
    public void dosome()throws IOException, AWTException {
    }
}
class SubClass extends ThrowsDemo{
//    public void dosome()throws IOException, AWTException {
//    }

    //允许不再抛出任何异常
//    public void dosome(){
//    }

    //允许仅抛出部分异常
//    public void dosome()throws IOException{
//    }

    //允许抛出超类方法声明抛出异常的子类型异常
//    public void dosome()throws FileNotFoundException {
//    }

    //不允许抛出额外异常(超类方法没有声明的且不存在继承关系的)
//    public void dosome()throws SQLException {
//    }

    //不允许抛出超类方法抛出异常的超类型异常
//    public void dosome()throws Exception {
//    }
}

异常分类

Java异常可以分为可检测异常,非检测异常:

  • 可检测异常:可检测异常经编译器验证,对于声明抛出异常的任何方法,编译器将强制执行处理或声明规则,不捕捉这个异常,编译器就通不过,不允许编译
  • 非检测异常:非检测异常不遵循处理或者声明规则。在产生此类异常时,不一定非要采取任何适当操作,编译器不会检查是否已经解决了这样一个异常
  • RuntimeException 类属于非检测异常,因为普通JVM操作引起的运行时异常随时可能发生,此类异常一般是由特定操作引发。但这些操作在java应用程序中会频繁出现。因此它们不受编译器检查与处理或声明规则的限制。实际上RuntimeException及其子类型表达的都是因为程序漏洞(BUG),即:逻辑不严谨等原因导致的。这类异常都是通过修复代码可完全避免的异常,因此不应当由异常处理机制来处理

常见的RuntimeException子类

  • IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数
  • NullPointerException:当应用程序试图在需要对象的地方使用 null 时,抛出该异常
  • ArrayIndexOutOfBoundsException:当使用的数组下标超出数组允许范围时,抛出该异常
  • ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常
  • NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。

异常API

Exception上有一些常用的方法

  • void printStackTrace():用于输出当前异常的堆栈跟踪信息

    便于程序员找到错误根源,分析错误原因制定B计划

  • String getMessage():用于获取当前错误消息

    多用于提示给用户或记录日志使用

package exception;

/**
 * 异常常用方法
 */
public class ExceptionAPIDemo {
    public static void main(String[] args) {
        System.out.println("程序开始了");
        try {
            String str = "abc";
            System.out.println(Integer.parseInt(str));
        } catch (NumberFormatException e) {
            //输出错误堆栈跟踪信息便于程序员找到错误根源,分析错误原因制定B计划
            e.printStackTrace();//输出错误信息
            //错误消息多用于提示给用户或记录日志使用
            String message = e.getMessage();//获取错误消息
            System.out.println(message);
        }
        System.out.println("程序结束了");
    }
}

自定义异常

自定义异常通常用来定义那些业务上的异常问题。

定义自定义异常需要注意以下问题:

  • 异常的类名要做到见名知义
  • 需要是Exception的子类
  • 提供超类异常提供的所有种类构造器

package exception;

/**
 * 非法的年龄异常
 *
 * 自定义异常通常用来说明业务上的错误.
 * 自定义异常要注意以下问题:
 * 1:定义的类名要做到见名知义
 * 2:必须是Exception的子类
 * 3:提供Exception所定义的所有构造方法
 */
public class IllegalAgeException extends Exception{
    public IllegalAgeException() {
    }

    public IllegalAgeException(String message) {
        super(message);
    }

    public IllegalAgeException(String message, Throwable cause) {
        super(message, cause);
    }

    public IllegalAgeException(Throwable cause) {
        super(cause);
    }

    public IllegalAgeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
package exception;

/**
 * 测试异常的抛出
 */
public class Person {
    private int age;

    public int getAge() {
        return age;
    }

    /**
     * 当一个方法使用throws声明异常抛出时,调用此方法的代码片段就必须处理这个异常
     */
    public void setAge(int age) throws IllegalAgeException {
        if(age<0||age>100){
            //使用throw对外抛出一个异常
//            throw new RuntimeException("年龄不合法!");
            //除了RuntimeException之外,抛出什么异常就要在方法上声明throws什么异常
//            throw new Exception("年龄不合法!");

            //抛出自定义异常
            throw new IllegalAgeException("年龄超范围:"+age);

        }
        this.age = age;
    }
}
package exception;

/**
 * throw关键字,用于主动对外抛出一个异常
 */
public class ThrowDemo {
    public static void main(String[] args){
        System.out.println("程序开始了...");
        try {
            Person p = new Person();
            /*
                当我们调用一个含有throws声明异常抛出的方法时,编译器要求
                我们必须添加处理异常的手段,否则编译不通过.而处理手段有两种
                1:使用try-catch捕获并处理异常
                2:在当前方法上继续使用throws声明该异常的抛出
                具体用哪种取决于异常处理的责任问题
             */
            p.setAge(100000);//典型的符合语法,但是不符合业务逻辑要求
            System.out.println("此人年龄:"+p.getAge()+"岁");
        } catch (IllegalAgeException e) {
            e.printStackTrace();
        }

        System.out.println("程序结束了...");
    }
}

总结:

java异常处理机制:
  • 异常处理机制是用来处理那些可能存在的异常,但是无法通过修改逻辑完全规避的场景。
  • 而如果通过修改逻辑可以规避的异常是bug,不应当用异常处理机制在运行期间解决!应当在编码时及时修正

try语句块用来包含可能出错的代码片段

catch用来捕获并处理对应的异常,可以定义多个,也可以合并多个异常在一个catch中。

finally是异常的最后一块,只要程序执行到try中则必走。一般用于释放资源这类操作。

throw用于主动对外抛出异常。要么是满足语法不满足业务主动抛出异常,要么就是实际发生了异常但是不应当在当前代码片段被解决是抛出。具体情况要结合实际业务分析。

throws用于在方法声明时声明该异常的抛出,使得调用者必须处理该异常。

网络编程

网络应用模型

  • C/S:这里的C是指Client,即客户端。而S是指Server,即服务端。网络上的的应用本质上就是两台计算机上的软件进行交互。而客户端和服务端就是对应的两个应用程序,即客户端应用程序和服务端应用程序

  • B/S:这里的B是Browser,即浏览器,而S是指Server。浏览器是一个通用的客户端,可以与不同的服务端进行交互。但是本质上B/S还是C/S结构,只不过浏览器是一个通用的客户端而已。

可靠传输与不可靠传输

TCP协议与UDP协议都是传输协议,客户端程序与服务端程序基于这些协议完成网络间的数据交互。

  • TCP是可靠传输协议,是面向连接的协议,保证数据传输中的可靠性和完整性。

    TCP保证可靠传输,但是传输效率低,占用带宽高。

  • UDP是不可靠传输协议,不保证数据传输的完整性。

    UDP不保证可靠传输,但是传输速度块,占用带宽小。

Socket与ServerSocket

  • java.net.Socket

    Socket(套接字)封装了TCP协议的通讯细节,是的我们使用它可以与服务端建立网络链接,并通过 它获取两个流(一个输入一个输出),然后使用这两个流的读写操作完成与服务端的数据交互

  • java.net.ServerSocket

  • ServerSocket运行在服务端,作用有两个:

    • 向系统申请服务端口,客户端的Socket就是通过这个端口与服务端建立连接的。
    • 监听服务端口,一旦一个客户端通过该端口建立连接则会自动创建一个Socket,并通过该Socket与客户端进行数据交互。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qHeUToIY-1681212694558)(C:\Users\TEACHER\IdeaProjects\JSD2303_SE\笔记\image-20230411140752599.png)]

  • 如果我们把Socket比喻为电话,那么ServerSocket相当于是某客服中心的总机。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hKT7dCUa-1681212694560)(C:\Users\TEACHER\IdeaProjects\JSD2303_SE\笔记\image-20230411115701346.png)]

与服务端建立连接案例

package socket;

import java.io.IOException;
import java.net.Socket;

/**
 * 聊天室客户端
 */
public class Client {
    /*
        java.net.Socket 套接字
        Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
        并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
        与远端计算机的数据交互工作。
        我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
        风(输出流),通过它们就可以与对方交流了。
     */
    private Socket socket;

    /**
     * 构造方法,用来初始化客户端
     */
    public Client(){
        try {
            System.out.println("正在链接服务端...");
            /*
                实例化Socket时要传入两个参数
                参数1:服务端的地址信息
                     可以是IP地址,如果链接本机可以写"localhost"
                参数2:服务端开启的服务端口
                我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
                的服务端应用程序。
                实例化的过程就是链接的过程,如果链接失败会抛出异常:
                java.net.ConnectException: Connection refused: connect
             */
            socket = new Socket("localhost",8088);
            System.out.println("与服务端建立链接!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法
     */
    public void start(){

    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }
}
package socket;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 聊天室服务端
 */
public class Server {
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
        try {
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
        try {
            System.out.println("等待客户端链接...");
            /*
                ServerSocket提供了接受客户端链接的方法:
                Socket accept()
                这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                通过这个Socket就可以与客户端进行交互了。
                
                可以理解为此操作是接电话,电话没响时就一直等。
             */
            Socket socket = serverSocket.accept();
            System.out.println("一个客户端链接了!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
}

客户端与服务端完成第一次通讯(发送一行字符串)

Socket提供了两个重要的方法:
  • OutputStream getOutputStream()

    该方法会获取一个字节输出流,通过这个输出流写出的字节数据会通过网络发送给对方。

  • InputStream getInputStream()

    通过该方法获取的字节输入流读取的是远端计算机发送过来的数据。

原理图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aEGF0hrK-1681212694561)(C:\Users\TEACHER\IdeaProjects\JSD2303_SE\笔记\image-20230411163427777.png)]

package socket;

import java.io.*;
import java.net.Socket;

/**
 * 聊天室客户端
 */
public class Client {
    /*
        java.net.Socket 套接字
        Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
        并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
        与远端计算机的数据交互工作。
        我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
        风(输出流),通过它们就可以与对方交流了。
     */
    private Socket socket;

    /**
     * 构造方法,用来初始化客户端
     */
    public Client(){
        try {
            System.out.println("正在链接服务端...");
            /*
                实例化Socket时要传入两个参数
                参数1:服务端的地址信息
                     可以是IP地址,如果链接本机可以写"localhost"
                参数2:服务端开启的服务端口
                我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
                的服务端应用程序。
                实例化的过程就是链接的过程,如果链接失败会抛出异常:
                java.net.ConnectException: Connection refused: connect
             */
            socket = new Socket("localhost",8088);
            System.out.println("与服务端建立链接!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法
     */
    public void start(){
        try {
            /*
                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);

            pw.println("你好服务端!");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }
}
package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 聊天室服务端
 */
public class Server {
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
        try {
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
        try {
            System.out.println("等待客户端链接...");
            /*
                ServerSocket提供了接受客户端链接的方法:
                Socket accept()
                这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                通过这个Socket就可以与客户端进行交互了。

                可以理解为此操作是接电话,电话没响时就一直等。
             */
            Socket socket = serverSocket.accept();
            System.out.println("一个客户端链接了!");

            /*
                Socket提供的方法:
                InputStream getInputStream()
                获取的字节输入流读取的是对方计算机发送过来的字节
             */
            InputStream in = socket.getInputStream();
            InputStreamReader isr = new InputStreamReader(in,"UTF-8");
            BufferedReader br = new BufferedReader(isr);

            String message = br.readLine();
            System.out.println("客户端说:"+message);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
}

实现客户端循环发消息给服务端

package socket;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * 聊天室客户端
 */
public class Client {
    /*
        java.net.Socket 套接字
        Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
        并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
        与远端计算机的数据交互工作。
        我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
        风(输出流),通过它们就可以与对方交流了。
     */
    private Socket socket;

    /**
     * 构造方法,用来初始化客户端
     */
    public Client(){
        try {
            System.out.println("正在链接服务端...");
            /*
                实例化Socket时要传入两个参数
                参数1:服务端的地址信息
                     可以是IP地址,如果链接本机可以写"localhost"
                参数2:服务端开启的服务端口
                我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
                的服务端应用程序。
                实例化的过程就是链接的过程,如果链接失败会抛出异常:
                java.net.ConnectException: Connection refused: connect
             */
            socket = new Socket("localhost",8088);
            System.out.println("与服务端建立链接!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法
     */
    public void start(){
        try {
            /*
                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);

            Scanner scanner = new Scanner(System.in);
            while(true) {
                String line = scanner.nextLine();
                if("exit".equalsIgnoreCase(line)){
                    break;
                }
                pw.println(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                /*
                    通讯完毕后调用socket的close方法。
                    该方法会给对方发送断开信号。
                 */
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }
}
package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 聊天室服务端
 */
public class Server {
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
        try {
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
        try {
            System.out.println("等待客户端链接...");
            /*
                ServerSocket提供了接受客户端链接的方法:
                Socket accept()
                这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                通过这个Socket就可以与客户端进行交互了。

                可以理解为此操作是接电话,电话没响时就一直等。
             */
            Socket socket = serverSocket.accept();
            System.out.println("一个客户端链接了!");

            /*
                Socket提供的方法:
                InputStream getInputStream()
                获取的字节输入流读取的是对方计算机发送过来的字节
             */
            InputStream in = socket.getInputStream();
            InputStreamReader isr = new InputStreamReader(in,"UTF-8");
            BufferedReader br = new BufferedReader(isr);

            String message = null;
            while((message = br.readLine())!=null) {
                System.out.println("客户端说:" + message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
}

需要注意的几个点:

  • 当客户端不再与服务端通讯时,需要调用socket.close()断开链接,此时会发送断开链接的信号给服务端。这时服务端的br.readLine()方法会返回null,表示客户端断开了链接。
  • 当客户端链接后不输入信息发送给服务端时,服务端的br.readLine()方法是出于阻塞状态的,直到读取了一行来自客户端发送的字符串。

TCP进行连接时的三次握手

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-koKy8etO-1681212694562)(C:\Users\TEACHER\IdeaProjects\JSD2303_SE\笔记\image-20230411161055080.png)]

TCP断开连接时的四次挥手

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nKEmD74g-1681212694563)(C:\Users\TEACHER\IdeaProjects\JSD2303_SE\笔记\image-20230411162032603.png)]

多客户端链接

只有第一个连接的客户端可以与服务端说话。

原因:

服务端只调用过一次accept方法,因此只有第一个客户端链接时服务端接受了链接并返回了Socket,此时可以与其交互。

而第二个客户端建立链接时,由于服务端没有再次调用accept,因此无法与其交互。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wRnY5pEd-1681212694564)(C:\Users\TEACHER\IdeaProjects\JSD2303_SE\笔记\image-20230411172359125.png)]

package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 聊天室服务端
 */
public class Server {
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
        try {
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
        try {
            while(true) {
                System.out.println("等待客户端链接...");
                /*
                    ServerSocket提供了接受客户端链接的方法:
                    Socket accept()
                    这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                    的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                    通过这个Socket就可以与客户端进行交互了。

                    可以理解为此操作是接电话,电话没响时就一直等。
                 */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                /*
                    Socket提供的方法:
                    InputStream getInputStream()
                    获取的字节输入流读取的是对方计算机发送过来的字节
                 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);

                String message = null;
                while ((message = br.readLine()) != null) {
                    System.out.println("客户端说:" + message);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
}

添加循环操作后,发现依然无法实现。

原因在于:

外层的while循环里面嵌套了一个内层循环(循环读取客户端发送消息),而循环执行机制决定了里层循环不结束,外层循环则无法进入第二次操作。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/411944.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python函数详解_INDEX函数

一. 函数的作用 函数就是将一段具有独立功能的代码块 整合到一个整体并命名&#xff0c;在需要的位置调用这个名称即可完成对应的需求。 函数在开发过程中&#xff0c;可以更高效的实现代码重用。 二. 函数的使用步骤 1. 定义函数 def 函数名(参数):代码1代码2...... 复制 …

一些算法知识整理

最近找工作&#xff0c;整理了一些算法知识&#xff0c;希望能帮助到同样在找工作的同学。 1、LR&#xff1a; 逻辑回归&#xff0c;用于分类问题&#xff0c;模型可以理解为 线性模型sigmoid函数&#xff1a;y11e−(wxb)y \frac{1}{1e^{-(wxb)}} y1e−(wxb)1​ 其优化目标是…

【Linux系统编程】10.open/close函数

目录 open 参数pathname 参数flags 主类 副类 参数mode 返回值 close 参数fp 测试代码 测试结果 错误处理函数 open 查看open函数 man 2 open 其中 #include <unistd.h> 包含 #include <sys/types.h> #include <sys/stat.h> 参数pathname 打开…

NodeRED学习(五)http websoket

HTTP定义响应内容&#xff1a; 注&#xff1a;可含请求参数 // msg.payload{ // "demo":"你好" // } msg.payload msg.req.query.content; return msg;支持浏览器请求测试&#xff1a;IP:1880/(URL) 组件验证请求&#xff1a; websocket消息: 工具参考…

Python学习笔记--循环语句 for 和 while

目前更新中.... &#xff08;一&#xff09; while循环的基本语法 1. 基本格式 while 条件&#xff1a; 条件满足时&#xff0c;做的事情1 条件满足时&#xff0c;做的事情2 …&#xff08;省略&#xff09;… 只要条件满足&#xff0c;会无限循环执行条件需要布尔类型结果…

excel表格的导入和导出(Java+element)

本项目是前端vue3&#xff0c;后端springboot开发 需求为&#xff1a;前端导入表格&#xff0c;后端处理表格存储数据&#xff0c;点击按钮可以导出表格。 上传效果&#xff1a;前端点击上传按钮&#xff0c;会跳出选择文件框&#xff0c;选择文件&#xff0c;点击上传。 导出效…

C语言CRC-16 IBM格式校验函数

C语言CRC-16 IBM格式校验函数 CRC-16校验产生2个字节长度的数据校验码&#xff0c;通过计算得到的校验码和获得的校验码比较&#xff0c;用于验证获得的数据的正确性。基本的CRC-16校验算法实现&#xff0c;参考&#xff1a; C语言标准CRC-16校验函数。 不同厂家通过对输入数…

鸿鹄工程项目管理系统源码 Spring Cloud+Spring Boot+Mybatis+Vue+ElementUI+前后端分离构建工程项目管理系统

鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工程管…

这家年销售额309亿的Tier 1,要谈一场千亿新生意

跨入2023年&#xff0c;智能汽车软件赛道更热闹了。 相较于传统汽车开发模式&#xff0c;软件属于分布式ECU工程开发的一部分&#xff0c;由一级供应商作为黑盒提供&#xff0c;软件开发成本等被认为是硬件系统成本的一部分&#xff0c;没有实现单独定价。 如今&#xff0c;“…

Redis 如何使用 RedisCluster 构建高可用集群架构?

文章目录Redis 如何使用 RedisCluster 构建高可用集群架构&#xff1f;什么是 Redis Cluster&#xff1f;哈希槽&#xff08;hash slot&#xff09;一致性保证&#xff08;consistency guarantees&#xff09;如何构建 Redis Cluster&#xff1f;配置环境构建 A&#xff0c;A1 …

int(1) 和 int(10)区别

有个表的要加个user_id字段&#xff0c;user_id字段可能很大&#xff0c; alter table xxx ADD user_id int(1)。 int(1)怕是不够用吧&#xff0c;接下来是一通解释。 我们知道在mysql中 int占4个字节&#xff0c;那么对于无符号的int&#xff0c;最大值是2^32-1 4294967295&a…

一文弄懂Python中的sort和sorted函数

1. 引言 Python中的 sort()和sorted()函数主要用于按升序或降序对数据进行排序。在本文中比较用于列表时&#xff0c;两个函数在编程和语法上的差异。 闲话少说&#xff0c;我们直接开始吧&#xff01; 2. Sort()函数基本用法 用于列表排序的sort函数的语法如下&#xff1a…

java equals和==的区别

目录一、equals1.前言2.重写equals方法二、三、equals和的区别一、equals 1.前言 **当用equals来比较两个引用数据类型时默认比较的是它们的地址值&#xff0c;比如创建两个成员变量完全相同对象A和对象B两个进行比较&#xff0c;比较的是两个对象的地址值是否相等&#xff0c…

从spring boot泄露到接管云服务器平台

0x1前言 在打野的时候意外发现了一个站点存在spring boot信息泄露&#xff0c;之前就有看到一些文章可以直接rce啥的&#xff0c;今天刚好试试。通过敏感信息发现存在accesskey泄露&#xff0c;就想直接通过解密&#xff0c;获取敏感信息&#xff0c;接管云平台。 首先说下这个…

Linux服务器如何清除dns缓存

Linux服务器如何清除dns缓存 DNS缓存是一个临时数据库&#xff0c;用于存储已解释的DNS查询信息。换句话说&#xff0c;每当你访问网站时&#xff0c;你的操作系统和网络浏览器都会保留域名和相应IP地址的记录。 这消除对远程DNS服务器重复查询&#xff0c;并允许你的操作系统…

【实验报告】实验三、图像复原

1. 实验目的 (1) 理解退化模型。 (2) 掌握常用的图像复原方法。 2. 实验内容 (1) 模拟噪声的行为和影响的能力是图像复原的核心。 (2) 空域滤波 实验一 1. 1 产生至少 2 种不同类型的噪声&#xff0c;并绘制原图像、加噪后图像及对应直方图于 一个图形窗口中[subplot(m…

用GPT-4写代码不用翻墙了?Cursor告诉你:可以~~

目录 一、介绍 二、使用方法 三、其他实例 1.正则表达式 2.自动化测试脚本 3.聊聊技术 一、介绍 Cursor主要功能是根据用户的描述写代码或者进行对话&#xff0c;对话的范围仅限技术方面。优点是不用翻墙、不需要账号。Cursor基于GPT模型&#xff0c;具体什么版本不祥&#…

ChatGPT文本框再次升级,打造出新型操作系统

在ChatGPT到来之前&#xff0c;没有谁能够预见。但是&#xff0c;它最终还是来了&#xff0c;并引起了不小的轰动&#xff0c;甚至有可能颠覆整个行业。 从某种程度上说&#xff0c;ChatGPT可能是历史上增长最快的应用程序&#xff0c;仅在两个多月就拥有了1亿多活跃用户&…

2023年4月企业内部定制课程简章

2023年4月企业内部定制课程简章 》》数据治理内训 数据管理基础 数据处理伦理 数据治理 数据架构 数据建模和设计 数据安全 数据集成和互操作 文件和内容管理 参考数据和主数据 数据仓库和商务智能 元数据管理 数据质量 大数据和数据科学 数据管理成熟度评估 数据管理组织与…

js基础之Promise(全面+手写实现)

1. 是什么 Promise是一种异步编程的解决方案&#xff0c;用于处理异步操作并返回结果。 主要作用是解决回调函数嵌套&#xff08;回调地狱&#xff09;的问题&#xff0c;使异步操作更加清晰、易于理解和维护。 2. 怎么用 Promise有三种状态&#xff1a;pending&#xff08;…