13-JavaSE【网络编程,Junit单元测试,单例模式,多例模式,工厂模式】

内容概述

  • 网络编程三要素
  • TCP通信
  • 文件上传
  • 模拟B/S
  • 多例设计模式
  • 工厂设计模式
- [ ] 能够描述网络编程的三要素及其对应作用
- [ ] 能够说出UDP协议的特点
- [ ] 能够说出TCP协议的特点
- [ ] 能够说出TCP协议构建服务端和客户端的类
- [ ] 能够使用TCP协议实现客户端和服务端字符串数据传输
- [ ] 能够理解TCP协议下文件上传案例
- [ ] 能够理解TCP协议下实现Web服务端案例
- [ ] 能够使用Junit进行程序测试
- [ ] 能够使用断言进行逻辑结果测试
- [ ] 能够说出单例设计模式的好处
- [ ] 能够说出多例模式的好处
- [ ] 能够使用简单工厂模式编写java程序

第一章 网络编程入门

1.1软件结构

  • C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。

在这里插入图片描述

B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。
在这里插入图片描述

两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。

1.2 网络通信协议

  • **网络通信协议:**通信协议是计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信。这就好比在道路中行驶的汽车一定要遵守交通规则一样,协议中对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守,最终完成数据交换。

  • TCP/IP协议: 传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
    在这里插入图片描述

1.3 协议分类

通信的协议还是比较复杂的,java.net 包中包含的类和接口,它们提供低层次的通信细节。我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节。

java.net 包中提供了两种常见的网络协议的支持:

  • TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
    • 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
      • 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。服务器你死了吗?
      • 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。我活着啊!!
      • 第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。我知道了!!

在这里插入图片描述

​ 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。

  • UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等。

1.4 网络编程三要素

协议

  • **协议:**计算机网络通信必须遵守的规则,已经介绍过了,不再赘述。

IP地址

  • IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。

**IP地址分类 **

  • IPv4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d 的形式,例如192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。

  • IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。有资料显示,全球IPv4地址在2011年2月分配完毕。

    为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。

常用命令

  • 查看本机IP地址,在控制台输入:
ipconfig
  • 检查网络是否连通,在控制台输入:
ping 空格 IP地址
ping 220.181.57.216
ping www.baidu.com

特殊的IP地址

  • 本机IP地址:127.0.0.1localhost

端口号

网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?

如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。

位于网络中一台计算机可以通过IP地址去访问另一台计算机,并通过端口号访问目标计算机中的某个进程。一个网络进程至少会有一个端口号。

  • **端口号:用整数表示,它的取值范围是065535**。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。

利用协议+IP地址+端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。

在Java中,可以使用java.net.InetAddress类来表示一个IP地址:

/**
    InetAddress类概述
        * 一个该类的对象就代表一个IP地址对象。

    InetAddress类成员方法
        * static InetAddress getLocalHost()
            * 获得本地主机IP地址对象
        * static InetAddress getByName(String host)
            * 根据IP地址字符串或主机名获得对应的IP地址对象

        * String getHostName();获得主机名
        * String getHostAddress();获得IP地址字符串
 */
public class InetAddressDemo01 {
    public static void main(String[] args) throws Exception {
        // 获得本地主机IP地址对象
        InetAddress inet01 = InetAddress.getLocalHost();
        // pkxingdeMacBook-Pro.local/10.211.55.2
        // 主机名/ip地址字符串
        System.out.println(inet01);
        // 根据IP地址字符串或主机名获得对应的IP地址对象
        // InetAddress inet02 = InetAddress.getByName("192.168.73.97");
        InetAddress inet02 = InetAddress.getByName("baidu.com");
        System.out.println(inet02);

        // 获得主机名
        String hostName = inet01.getHostName();
        System.out.println(hostName);
        // 获得IP地址字符串
        String hostAddress = inet01.getHostAddress();
        System.out.println(hostName);
        System.out.println(hostAddress);
    }
}

1.5 IP地址类InetAddress

InetAddress 此类表示Internet协议(IP)地址,是构建UDP和TCP协议的低级协议

InetAddress
  静态方法:
 		public static InetAddress getLocalHost() 
  			获得本地主机IP地址对象
            
 		public static InetAddress getByName(String host) 
  			根据IP地址字符串或主机名获得对应的IP地址对象,也可以通过网址例如:www.baidu.com 
      
	非静态方法:
 		String getHostName(): 获得主机名
 		String getHostAddress(): 获得IP地址字符串

【代码实践】

  1. 获取本机IP地址对象
  2. 根据IP地址字符串或主机名获得对应的IP地址对象
  3. 根据网址获取IP地址对象
public class Demo01 {
    public static void main(String[] args) throws UnknownHostException {
        //InetAddress

        //1. 获取本机IP地址对象
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println("localHost = " + localHost);//lyc-surfacebook/192.168.147.72
        //获取主机名
        String hostName = localHost.getHostName();
        System.out.println("hostName = " + hostName);//lyc-surfacebook
        //获取IP地址
        String hostAddress = localHost.getHostAddress();
        System.out.println("hostAddress = " + hostAddress);//192.168.147.72


        //2. 根据IP地址字符串或主机名获得对应的IP地址对象
        InetAddress ip2 = InetAddress.getByName("lyc-surfacebook");
        System.out.println("ip2 = " + ip2);//lyc-surfacebook/192.168.147.72

        InetAddress ip3 = InetAddress.getByName("192.168.147.72");
        System.out.println("ip3 = " + ip3);

        //3. 根据网址获取IP地址对象
        InetAddress ip4 = InetAddress.getByName("www.baidu.com");
        System.out.println("ip4 = " + ip4);//www.baidu.com/112.80.248.76
    }
}

第二章 TCP通信程序

1 概述

TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接。

在JDK中提供了两个类用于实现TCP程序,一个是ServerSocket类,用于表示服务器端,一个是Socket类,用于表示客户端。

通信时,首先创建代表服务器端的ServerSocket对象,该对象相当于开启一个服务,并等待客户端的连接,然后创建代表客户端的Socket对象向服务器端发出连接请求,服务器端响应请求,两者建立连接开始通信。

2 Socket类

Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。

构造方法
Socket(String host, int port) 创建一个流套接字并将其连接到指定主机上的指定端口号
参数说明:
    host 表示服务器端的主机名,也可以是服务器端的IP地址,只不过是String类型的
    port 表示服务器端的端口

注意:客户端套接字,一旦创建,就会尝试与目标IP和端口的服务端建立连接通道。

【代码实践】

建立客户端,连接本电脑的服务端程序,服务端程序端口为:8888

public class Demo01 {
    public static void main(String[] args) throws IOException {
        //建立客户端,连接本电脑的服务端程序,服务端程序端口为:8888

        Socket socket = new Socket("127.0.0.1", 8888);
        System.out.println("socket = " + socket);
    }
}

因为服务端还没有构建,此时不存在,连接会出错:
在这里插入图片描述

常用方法
InputStream getInputStream();获得字节输入流对象,用来获取服务端发送的数据
OutputStream getOutputStream(); 获得字节输出流对象,用来给服务端发送数据
    
void shutdownOutput():禁用此套接字的输出流,任何先前写出的数据将被发送,随后终止输出流。 
    
void close():关闭此套接字。

3 ServerSocket类

说明:ServerSocket类用来描述服务器端的套接字,创建完这个类的对象之后,那么就会等待客户端来访问,客户端有请求之后,服务器端有可能会有返回结果,有可能也会没有,具体由我们书写的代码来决定。

构造方法
ServerSocket(int port) 创建服务端对象,绑定一个端口号
参数说明:
    port就是端口号

说明:创建服务器端套接字对象时必须指定端口,只有这样,客户端访问服务器端的时候才能根据指定的端口来访问。

常用方法
Socket accept() 侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。 

【代码实践】

构建服务端,绑定端口8888,并监控客户端的连接。

public class Demo01 {
    public static void main(String[] args) throws IOException {
        //ServerSocket 服务端
        //1.创建服务端对象,绑定端口
        ServerSocket serverSocket = new ServerSocket(8888);
        while (true) {

            //2.调用accept方法等待客户端的连接
            System.out.println("等待客户端连接:");
            Socket socket = serverSocket.accept();//阻塞,等待客户端连接
            System.out.println("客户端已连接:" + socket);

        }

    }
}

在这里插入图片描述

4 TCP通信案例

代码实现客户端发送一条信息给服务端,服务端回发一条信息给客户端

通讯流程

在这里插入图片描述

  1. 创建服务器端并启动
  2. 创建客户端并启动【指定服务端的地址和端口】
  3. 服务端accept 等待接受客户端的连接
  4. 客户端获取网络的输出流,将数据写到网络中发送给服务端
  5. 服务端获取网络的输入流,将客户端发送的数据读取
  6. 服务端获取网络的输出流,将数据发送给客户端的
  7. 客户端获取网络的输入流,将服务端发送的数据读取
  8. 释放资源

当实现了客户端和服务端后,要先开启服务端程序。

客户端开发

客户端向服务器发送数据,并接收服务端回发的数据

开发步骤

  1. 创建Socket对象并指定服务端的IP和端口
  2. 获取网络输出流:getOutputStream
  3. 使用网络输出流给服务端发送一个字符串
  4. 当发送完数据后,告诉服务端,发送完毕:shutdownOutput()
  5. 获取网络输入流,读取服务端发送的数据
  6. 释放资源
/*
客户端: 给服务端发送一个字符串,并接收服务端回发的字符串
 */
public class Client {
    public static void main(String[] args) throws IOException {

        //1. 创建Socket对象并指定服务端的IP和端口
        Socket socket = new Socket("127.0.0.1", 8888);
        //2. 获取网络输出流:getOutputStream
        OutputStream netOut = socket.getOutputStream();
        //3. 使用网络输出流给服务端发送一个字符串
        netOut.write("在吗,约不约!".getBytes());
        //4. 当发送完数据后,告诉服务端,发送完毕:shutdownOutput()
        socket.shutdownOutput();

        //5. 获取网络输入流,读取服务端发送的数据:getInputStream()
        InputStream netIn = socket.getInputStream();
        int len;
        byte[] buf = new byte[1024];
        while ((len = netIn.read(buf)) != -1) {
            System.out.println(new String(buf, 0, len));
        }

        //6. 释放资源
        socket.close();

    }
}

服务端开发

服务端接收客户端的连接,并获取客户端的数据,然后回发一条数据给客户端。

开发步骤

  1. 创建服务端ServerSocket对象,绑定端口号
  2. 调用accept方法,等待客户端连接。
  3. 当accept解阻塞,意味着有客户端连接,得到一个Socket对象
  4. 获取网络输入流,读取客户端发送的数据
  5. 获取网络输出流,写数据给客户端
  6. 告诉客户端,写数据完毕
  7. 释放资源
/*
服务端:接收客户端的信息,并回发信息给客户端
 */
public class Server {
    public static void main(String[] args) throws IOException {
        //1. 创建服务端ServerSocket对象,绑定端口号
        ServerSocket serverSocket = new ServerSocket(8888);
        while (true) {
            //2. 调用accept方法,等待客户端连接。
            System.out.println("等待客户端连接~~");
            Socket socket = serverSocket.accept();
            //3. 当accept解阻塞,意味着有客户端连接,得到一个Socket对象
            System.out.println("客户端连接成功:" + socket);

            //4. 获取网络输入流,读取客户端发送的数据
            InputStream netIn = socket.getInputStream();
            int len;
            byte[] buf = new byte[1024];
            while ((len = netIn.read(buf)) != -1) {
                System.out.println(new String(buf, 0, len));
            }

            //5. 获取网络输出流,写数据给客户端
            OutputStream netOut = socket.getOutputStream();
            netOut.write("不约,没空,我要学习Java,等我毕业找到工作,再约!!".getBytes());
            //6. 告诉客户端,写数据完毕
            socket.shutdownOutput();

            //7. 释放资源
            socket.close();
            //serverSocket.close();  //如果循环服务,服务器是不能关的
        }
    }
}

注意:

socket.shutdownOutput();
//该方法的执行可以让对方任务数据发送完毕,如果是循环读取会得到-1

第三章 综合案例

3.1 文件上传案例

件上传分析图解

在这里插入图片描述

需求:实现客户端将一个图片文件,上传到文件服务端。服务端接收到图片数据保存到本地。

客户端开发

读取本地文件,发送给服务端

开发步骤:

  1. 创建Socket对象指定服务端的IP和端口号
  2. 创建本地的输入流,关联要上传的文件
  3. 获取网络的输出流,用来发送文件字节数据到网络中
  4. 边读(读取本地文件)边写(使用网络输出流发送数据)
  5. 调用shutdownOutput方法告诉服务端,数据传输完毕
  6. 获取网络输入流,读取服务端发送的数据
  7. 释放客户端资源和本地流资源
public class Client {
    public static void main(String[] args) throws IOException {
        //1. 创建Socket对象指定服务端的IP和端口号
        Socket socket = new Socket("127.0.0.1", 8888);

        //2. 创建本地的输入流,关联要上传的文件
        FileInputStream localIn = new FileInputStream("day12/client/美女.jpg");

        //3. 获取网络的输出流,用来发送文件字节数据到网络中
        OutputStream netOut = socket.getOutputStream();

        //4. 边读(读取本地文件)边写(使用网络输出流发送数据)
        int len;
        byte[] buf = new byte[1024];
        while ((len = localIn.read(buf)) != -1) {//本地流读取文件字节
            netOut.write(buf, 0, len);//将读取的字节,写到网络中
        }

        //5. 调用shutdownOutput方法告诉服务端,数据传输完毕
        socket.shutdownOutput();// 对方while循环读取的时候,就会得到-1标记

        //6. 获取网络输入流,读取服务端发送的数据
        InputStream netIn = socket.getInputStream();
        while ((len = netIn.read(buf)) != -1) {
            //System.out.println((char)len);
            //System.out.println(buf,0,len);
            String msg = new String(buf, 0, len);//解码
            System.out.println("收到服务端的回信:" + msg);
        }

        //7. 释放客户端资源和本地流资源
        socket.close();
        localIn.close();

    }
}

服务端开发

接收客户端的连接,并接收客户端上传的数据。完成之后回发一个信息

开发步骤:

  1. 创建服务端对象,绑定端口
  2. 接受客户端连接:accept方法
  3. 如果连接成功得到一个Socket对象,可以与客户端进行通讯
  4. 先网络的输入流,用来读取客户端上传的文件信息
  5. 创建一个本地的输出流,用来保存客户端上传的文件数据到服务端的文件
  6. 边读(读取网络中数据),边写(保存到本地硬盘)
  7. 获取网络的输出流,用来发送信息给客户端
  8. 告诉对方发完:shutdownOutput
  9. 释放资源
/*
图片上传服务端:
接收客户端的连接,并接收客户端上传的数据。完成之后回发一个信息
 */
public class Server {
    public static void main(String[] args) throws IOException {
        //1. 创建服务端对象,绑定端口
        ServerSocket serverSocket = new ServerSocket(8888);

        //2. 接受客户端连接:accept方法
        System.out.println("等待客户端的连接~~");
        Socket socket = serverSocket.accept();

        //3. 如果连接成功得到一个Socket对象,可以与客户端进行通讯
        System.out.println("客户端连接成功:" + socket);

        //4. 先获取网络的输入流,用来读取客户端上传的文件信息
        InputStream netIn = socket.getInputStream();

        //5. 创建一个本地的输出流,用来保存客户端上传的文件数据到服务端的文件
        FileOutputStream localOut = new FileOutputStream("day12/server/美女.jpg");

        //6. 边读(读取网络中数据),边写(保存到本地硬盘)
        int len;
        byte[] buf = new byte[1024];
        while ((len = netIn.read(buf)) != -1) {
            localOut.write(buf, 0, len);
        }

        //7. 获取网络的输出流,用来发送信息给客户端
        OutputStream netOut = socket.getOutputStream();
        netOut.write("恭喜,文件上传成功!!".getBytes());

        //8. 告诉对方发完:shutdownOutput
        socket.shutdownOutput();

        //9. 释放资源
        socket.close();
        localOut.close();
        //服务端
        serverSocket.close();
    }
}

案例优化

以上案例存在的问题:

  1. 不能持续服务【采用线程池的方式优化】
  2. 文件会被覆盖
/*
服务端的优化:
1.循环服务
2.让文件不会覆盖
 */
public class ServerBetter2 {
    public static void main(String[] args) throws IOException {
        //线程池处理上传任务
        //1.先创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        //2.将文件上传的业务,封装成为一个任务,提交给线程池


        //1. 创建服务端对象,绑定端口
        ServerSocket serverSocket = new ServerSocket(8888);
        while (true) {
            //2. 接受客户端连接:accept方法
            System.out.println("等待客户端的连接~~");
            Socket socket = serverSocket.accept();

            //创建上传任务,将任务提交给线程池
            threadPool.submit(new UploadTask(socket));


            //服务端
            //serverSocket.close();
        }
    }
}

class UploadTask implements Runnable {
    private Socket socket;

    public UploadTask(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        FileOutputStream localOut = null;
        try {
            //3. 如果连接成功得到一个Socket对象,可以与客户端进行通讯
            System.out.println("客户端连接成功:" + socket);

            //4. 先获取网络的输入流,用来读取客户端上传的文件信息
            InputStream netIn = socket.getInputStream();

            //5. 创建一个本地的输出流,用来保存客户端上传的文件数据到服务端的文件
            long t = System.currentTimeMillis();
            localOut = new FileOutputStream("day12/server/美女" + t + ".jpg");

            //6. 边读(读取网络中数据),边写(保存到本地硬盘)
            int len;
            byte[] buf = new byte[1024];
            while ((len = netIn.read(buf)) != -1) {
                localOut.write(buf, 0, len);
            }

            //7. 获取网络的输出流,用来发送信息给客户端
            OutputStream netOut = socket.getOutputStream();
            netOut.write("恭喜,文件上传成功!!".getBytes());

            //8. 告诉对方发完:shutdownOutput
            socket.shutdownOutput();

            //9. 释放资源
            // socket.close();
            //localOut.close();
        } catch (Exception e) {
            System.out.println("上传出现问题:" + e.getMessage());
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (localOut != null)
                    localOut.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

3.2 模拟服务器(扩展)

模拟网站服务器,使用浏览器访问自己编写的服务端程序,查看网页效果。

案例分析

在这里插入图片描述

我们要完成的任务:构建服务端程序,监听浏览器的请求。浏览器需要什么资源,我们服务端就给什么资源(文件)。

  1. 准备页面数据,web文件夹。

    将今天下发的资料文件夹中的web整个文件夹复制到今天的模块中去

  2. 我们模拟服务器端,ServerSocket类监听端口,使用浏览器访问。浏览器在访问服务端的时候,会发送http协议数据给服务端。我们可以在里面解析出浏览器想要的文件。

    服务端获取浏览器的请求数据:
    
    GET /day12/web/index.html HTTP/1.1
    Host: 127.0.0.1:8888
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1
    

    以上是协议数据,我们现在所要关心的是第一行

    GET /day12/web/index.html HTTP/1.1
    第一行有三部分组成,使用空格分隔
    1.GET : 表示HTTP协议的一种请求方式,POST
    2./day12/web/index.html  :表示浏览器需要的资源【我们需要解析的】
    3.HTTP/1.1:是协议的版本
    

    我们要在程序中实现,把协议的第一行中的中间表示资源的路径解析出来。

    解析如下:

    //创建服务端对象,绑定端口8888
    ServerSocket serverSocket = new ServerSocket(8888);
    //等待浏览器的连接
    System.out.println("等待浏览器的访问:");
    Socket socket = serverSocket.accept();
    
    //获取浏览器传输的数据
    //需要第一行:GET /day12/web/index.html HTTP/1.1的 中间路径字符串
    InputStream netIn = socket.getInputStream();
    //1.先把字节输入流转换为字符流
    InputStreamReader isr = new InputStreamReader(netIn);
    //2.将字符流包装成为高效流
    BufferedReader br = new BufferedReader(isr);
    //3.读取第一行,解析中间的资源路径
    String firstLine = br.readLine();
    //浏览器需要的文件路径
    String path = firstLine.split(" ")[1].substring(1);
    
    System.out.println("浏览器要:" + path);
    
  3. 将浏览器所需的资源,通过网络输出流输出到网络中,写出数据时,先写协议,浏览器才能够认识。

    netOut.write("HTTP/1.1 200 OK\r\n".getBytes());
    netOut.write("Content-Type:text/html\r\n".getBytes());
    netOut.write("\r\n".getBytes()); //第三行空行
    
案例实现
普通版
public class BsServer {
    public static void main(String[] args) throws IOException {
        //创建服务端对象,绑定端口8888
        ServerSocket serverSocket = new ServerSocket(8888);

        while (true) {
            //等待浏览器的连接
            System.out.println("等待浏览器的访问:");
            Socket socket = serverSocket.accept();

            //获取浏览器传输的数据
            //需要第一行:GET /day12/web/index.html HTTP/1.1的 中间路径字符串
            InputStream netIn = socket.getInputStream();

            //1.先把字节输入流转换为字符流
            InputStreamReader isr = new InputStreamReader(netIn);
            //2.将字符流包装成为高效流
            BufferedReader br = new BufferedReader(isr);
            //3.读取第一行,解析中间的资源路径
            String firstLine = br.readLine();
            //浏览器需要的文件路径
            String path = firstLine.split(" ")[1].substring(1);

            System.out.println("浏览器要:" + path);

            //将文件发送给浏览器
            //1.获取网络输出流,用来发送数据给浏览器
            OutputStream netOut = socket.getOutputStream();

            //2.创建一个本地输入流,关联浏览器需要的文件
            FileInputStream localIn = new FileInputStream(path);

            //3.边读,边写
            //给浏览器发送内容数据之前,需要给浏览器发送协议数据
            netOut.write("HTTP/1.1 200 OK\r\n".getBytes());
            netOut.write("Content-Type:text/html\r\n".getBytes());
            netOut.write("\r\n".getBytes()); //第三行空行


            int len;
            byte[] buf = new byte[1024];
            while ((len = localIn.read(buf)) != -1) {
                netOut.write(buf, 0, len);
            }

            //4.告诉浏览器,写完了
            socket.shutdownOutput();

            //5.释放资源
            socket.close();
            //serverSocket.close(); //服务端不能关,需要重复服务
        }

    }
}

优化版

使用了线程池技术

public class BsServerBetter {
    public static void main(String[] args) throws IOException {
        //线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        //创建服务端对象,绑定端口8888
        ServerSocket serverSocket = new ServerSocket(8888);

        while (true) {
            //等待浏览器的连接
            System.out.println("等待浏览器的访问:");
            Socket socket = serverSocket.accept();

            threadPool.submit(new Task(socket));

            //serverSocket.close(); //服务端不能关,需要重复服务
        }

    }
}


class Task implements Runnable {
    private Socket socket;

    public Task(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        FileInputStream localIn = null;
        try {
            //获取浏览器传输的数据
            //需要第一行:GET /day12/web/index.html HTTP/1.1的 中间路径字符串
            InputStream netIn = socket.getInputStream();

            //1.先把字节输入流转换为字符流
            InputStreamReader isr = new InputStreamReader(netIn);
            //2.将字符流包装成为高效流
            BufferedReader br = new BufferedReader(isr);
            //3.读取第一行,解析中间的资源路径
            String firstLine = br.readLine();
            //浏览器需要的文件路径
            String path = firstLine.split(" ")[1].substring(1);

            System.out.println("浏览器要:" + path);

            //将文件发送给浏览器
            //1.获取网络输出流,用来发送数据给浏览器
            OutputStream netOut = socket.getOutputStream();

            //2.创建一个本地输入流,关联浏览器需要的文件
            localIn = new FileInputStream(path);

            //3.边读,边写
            //给浏览器发送内容数据之前,需要给浏览器发送协议数据
            netOut.write("HTTP/1.1 200 OK\r\n".getBytes());
            netOut.write("Content-Type:text/html\r\n".getBytes());
            netOut.write("\r\n".getBytes()); //第三行空行


            int len;
            byte[] buf = new byte[1024];
            while ((len = localIn.read(buf)) != -1) {
                netOut.write(buf, 0, len);
            }

            //4.告诉浏览器,写完了
            socket.shutdownOutput();


        } catch (Exception e) {
            System.out.println("请求出错:" + e.getMessage());
        }finally {
            //5.释放资源
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (localIn!=null) {
                    localIn.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

通过上述代码我们可以书写代码模拟服务端,其实在企业真实开发过程中我们是很少自己书写代码模拟服务器的,我们使用的是第三方服务器公司给我们提供好的服务器,

我们现在企业开发中比较主流的服务器有Tomcat、JBoss、Oracle WebLogic 等。其中最为流行的服务器就是Apache 公司的Tomcat服务器。

总结

案例的总体思想,开发一个ServerSocket服务端,用来接收浏览器的请求。我们要解析浏览器的请求资源,浏览器要什么就给什么。

  1. 浏览器发送请求,发送的是协议数据。而浏览器所需的数据就在协议中,所以先要把协议中的资源解析出来
  2. 把浏览器所需的文件发送出去,发送之前需要发送三行协议

第四章 Junit单元测试

1 单元测试概述

  1. Junit是什么?

    Junit是Java语言编写的第三方单元测试框架(工具类),加载到我们自己的工程或者模块后,可以直接使用这些框架中定义好的类型。

  2. 单元测试概念

    单元:单元就是人为规定的最小的被测功能模块,在Java中,一个类就是一个单元。

    单元测试:对某个类中的定义的方法进行功能测试或业务逻辑测试。

  3. Junit单元测试框架的作用
    用来对类中的方法功能进行有目的的测试,以保证程序的正确性和稳定性。

    能够让方法独立运行起来。

    在工作中完成代码开发后就可以进行单元测试,以达到使程序稳定,健壮的效果。

2 Junit使用步骤

  1. 编写业务类,在业务类中编写业务方法。比如增删改查的方法【被测试的代码】

  2. 编写测试类,在测试类中编写测试方法,在测试方法中编写测试代码来测试。【测试操作代码】

    测试类

    • 命名规范:以Test开头,以业务类类名结尾,使用驼峰命名法每一个单词首字母大写。

      比如业务类类名:ProductDao,那么测试类类名就应该叫:TestProductDao
      

    测试方法

    • 命名规范:以test开头,以业务方法名结尾

      比如业务方法名为:save,那么测试方法名就应该叫:testSave
      
    • 方法格式

      • 必须是public修饰的,没有返回值,没有参数(如果不按规则写,测试方法执行不起来)

      • 必须使注解@Test修饰(这个注解来自Junit测试框架)

      【固定格式】

      @Test
      public void testXxx(){
          写测试的代码
      }
      

      初次使用时,需要将将Junit导入工程==【Alt+Enter快捷键】==,会有两个版本一个Junit4和Junit5。我们常用使用Junit4版本进行测试,选择Junit4版本。
      }

  3. 如何运行测试方法
    选中方法名 --> 右键 --> Run ‘测试方法名’ 运行选中的测试方法
    选中测试类类名 --> 右键 --> Run ‘测试类类名’ 运行测试类中所有测试方法
    选中模块名 --> 右键 --> Run ‘All Tests’ 运行模块中的所有测试类的所有测试方法

  4. 如何查看测试结果
    绿色:表示测试通过
    红色:表示测试失败,有问题

【代码实践】

写一个Calculator类,实现加减乘除的功能。然后使用测试类对功能进行测试。

public class Calculator {

    public int add(int a, int b) {
        System.out.println(1/0);
        return a + b;
    }


}



public class TestCalculator {

    @Test
    public void testAdd() {
        // System.out.println("Hello Test~~");
        Calculator c = new Calculator();
        int add = c.add(10, 10);
        System.out.println("add = " + add);



    }


    @Test
    public void test1() {
        System.out.println("test1~~~");
    }

}

  1. 把Junit4导入到模块
    1. IDEA导入,在写入方法后直接Alt+Enter
    2. 直接导入jar包
  2. 写测试类
  3. 写测试方法
    1. 测试方法必须是无参,无返回值,的公共方法。
    2. 使用@Test进行注解

3 Junit中其他注解使用

//Junit4常用注解(Junit4.xxxx版本)
	@Before:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
	@After:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
        //以上两个注解,修饰非静态方法,每一个test方法执行,都会导致执行
        
	@BeforeClass:用来静态修饰方法,该方法会在所有测试方法之前执行一次。
	@AfterClass:用来静态修饰方法,该方法会在所有测试方法之后执行一次。
        //以上两个注解,修饰静态方法,不管有多少个测试方法,只会执行一次


//Junit5常用注解(Junit5.xxxx版本)
 	@BeforeEach:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
 	@AfterEach:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
 	@BeforeAll:用来静态修饰方法,该方法会在所有测试方法之前执行一次。
 	@AfterAll:用来静态修饰方法,该方法会在所有测试方法之后执行一次。

【示例代码】

public class TestCalculator2 {
    @BeforeClass
    public static void beforeClassTest() {
        System.out.println("beforeClass方法执行");
    }
    @AfterClass
    public static void afterClassTest() {
        System.out.println("afterClass方法执行");
    }

    Calculator c;
    @After
    public void afterTest() {
        System.out.println("after方法执行");
        c = null;
    }

    @Before
    public void beforeTest() {
        System.out.println("before方法执行");
        c = new Calculator();
    }



    @Test
    public void testAdd() {
        // System.out.println("Hello Test~~");

        int add = c.add(10, 10);
        System.out.println("add = " + add);



    }


    @Test
    public void testSub() {

        System.out.println(c.sub(10, 10));
    }

}

最常用的最核心的注解:@Test

4 断言

断言作用

预先判断某个条件一定成立,如果条件不成立,则直接报错。

断言代码

//第一个参数表示期望值
//第二个参数表示实际值
//如果实际值和期望值相同,说明结果正确就测试通过,如果不相同,说明结果是错误的,就会报错
Assert.assertEquals( 期望值, 实际值);

//例如:
int result = add(100,200);
Assert.assertEquals(300, result);  

代码实践

    @Test
    public void testMultiply() {
        Calculator cal = new Calculator();

        int multiply = cal.multiply(2, 2);
        System.out.println("multiply = " + multiply);
        //断言
        Assert.assertEquals(4,multiply);

        int multiply1 = cal.multiply(2, 3);
        System.out.println("multiply1 = " + multiply1);
        //断言
        Assert.assertEquals(6,multiply1);


    }

第五章 设计模式

一、 单例设计模式

正常情况下一个类可以创建多个对象

public static void main(String[] args) {
	// 正常情况下一个类可以创建多个对象
	Person p1 = new Person();
	Person p2 = new Person();
	Person p3 = new Person();
}

定义一个皇帝类,该类就应该只能存在一个对象。

1 单例设计模式的作用

单例模式,是一种常用的软件设计模式。通过单例模式可以保证系统中,应用该模式的这个类只有一个实例。即一个类只有一个对象实例。

2 单例设计模式实现步骤

  1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
  2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
  3. 定义一个静态方法返回这个唯一对象。

3 单例设计模式的类型

根据实例化对象的时机单例设计模式又分为以下两种:

  1. 饿汉单例设计模式
  2. 懒汉单例设计模式

4 饿汉单例设计模式

饿汉单例设计模式就是使用类的时候已经将对象创建完毕,不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故被称为“饿汉模式”。

代码如下:

public class Singleton {
    // 1.将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
    private Singleton() {}

    // 2.在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
    private static final Singleton instance = new Singleton();
    
    // 3.定义一个静态方法返回这个唯一对象。
    public static Singleton getInstance() {
        return instance;
    }
}

需求:定义一个皇帝类,要求对象只能存在一个。

public class Demo01 {
    public static void main(String[] args) {
        //new King("亚历山大二世");
        //获取皇帝对象
        King k1 = King.getInstance();
        System.out.println("k1 = " + k1);

        for (int i = 0; i < 10; i++) {
            System.out.println("King.getInstance() = " + King.getInstance());
        }

    }
}

//定义一个皇帝类,要求对象只能存在一个。
class King {
    String name;
    //1. 私有化构造方法
    private King(String name) {
        this.name = name;
    }

    //2.提供一个静态字段,用来保存一个对象
    private static King king = new King("亚历山大二世");


    //3.提供一个公共的静态方法,用来获取该字段
    public static King getInstance() {
        return king;
    }
}

5 懒汉单例设计模式

懒汉单例设计模式就是调用getInstance()方法时实例才被创建,先不急着实例化出对象,等要用的时候才实例化出对象。不着急,故称为“懒汉模式”。

代码如下:

public class Singleton {

    // 2.在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
    private static Singleton instance;
    
    // 1.将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
    private Singleton() {}
    
    // 3.定义一个静态方法返回这个唯一对象。要用的时候才例化出对象
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

注意:懒汉单例设计模式在多线程环境下可能会实例化出多个对象,不能保证单例的状态,所以加上关键字:synchronized,保证其同步安全。

需求:使用懒汉单例,实现皇帝类的单例模式

public class Demo02 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            HuangDi hd = HuangDi.getInstance();
            System.out.println("hd = " + hd);
        }

    }
}

//懒汉单例
class HuangDi {
    String name;

    //  1. 私有化构造方法
    private HuangDi(String name) {
        this.name = name;
    }


    //2. 静态字段定义,不赋值【不着急】
    private static HuangDi huangDi;

    //3.定义方法获取单例
    public static synchronized HuangDi getInstance() {
        if (huangDi == null) {
            huangDi = new HuangDi("秦始皇");
        }

        return huangDi;
    }

}

6 小结

单例模式可以保证系统中一个类只有一个对象实例。

实现单例模式的步骤:

  1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
  2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
  3. 定义一个静态方法返回这个唯一对象。

二、多例设计模式

1.多例设计模式的作用

多例模式,是一种常用的软件设计模式。通过多例模式可以保证系统中,应用该模式的类有固定数量的实例。多例类要自我创建并管理自己的实例,还要向外界提供获取本类实例的方法。

使用场景:线程池

线程池 = Executors.newFixedThreadPool(3);

2.实现步骤

​ 1.创建一个类, 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。

​ 2.在类中定义该类被创建对象的总数量

​ 3.在类中定义存放类实例的list集合

​ 4.在类中提供静态代码块,在静态代码块中创建类的实例

​ 5.提供获取类实例的静态方法

3.实现代码

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Multition {
    // 定义该类被创建的总数量
    private static final int maxCount = 3;
    // 定义存放类实例的list集合
    private static List instanceList = new ArrayList();
    // 构造方法私有化,不允许外界创建本类对象
    private Multition() {
    }
    static {
        // 创建本类的多个实例,并存放到list集合中
        for (int i = 0; i < maxCount; i++) {
            Multition multition = new Multition();
            instanceList.add(multition);
        }
    }
    // 给外界提供一个获取类对象的方法
    public static Multition getMultition(){
        Random random = new Random();
        // 生成一个随机数
        int i = random.nextInt(maxCount);
        // 从list集合中随机取出一个进行使用
        return (Multition)instanceList.get(i);
    }
}

4.测试结果

public static void main(String[] args) {
    // 编写一个循环从中获取类对象
    for (int i = 0; i < 10; i++) {
        Multition multition = Multition.getMultition();
        System.out.println(multition);
    }
}

需求:

定义一个类,用来描述公司的公用车。固定只能有3辆车。使用多例模式进行设计该类。

  1. 定义静态字段,用来设定实例个数
  2. 定义静态的集合,用来保存多例
  3. 私有化构造方法
  4. 静态代码块中进行对多例初始化
  5. 提供静态方法将某个实例返回
public class Demo01 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Car car = Car.getInstance();
            System.out.println("car = " + car);
        }

    }
}

//定义一个类,用来描述公司的车。固定只能有3辆车。使用多例模式进行设计该类。
class Car {
    //1. 定义静态字段,用来设定实例个数
    //2. 定义静态的集合,用来保存多例
    //3. 私有化构造方法
    private static int count = 3;
    private static ArrayList<Car> carList = new ArrayList<>();

    private Car() {
    }

    //静态代码块
    //4. 静态代码块中进行对多例初始化
    static {
        for (int i = 0; i < count; i++) {
            carList.add(new Car());
        }
    }

    //5. 提供静态方法将某个实例返回
    public static Car getInstance() {
        int index = new Random().nextInt(count);
        Car car = carList.get(index);
        return car;
    }

}


car = _12多例设计模式.Car@14ae5a5
car = _12多例设计模式.Car@7f31245a
car = _12多例设计模式.Car@14ae5a5
car = _12多例设计模式.Car@14ae5a5
car = _12多例设计模式.Car@7f31245a
car = _12多例设计模式.Car@14ae5a5
car = _12多例设计模式.Car@14ae5a5
car = _12多例设计模式.Car@6d6f6e28
car = _12多例设计模式.Car@14ae5a5
car = _12多例设计模式.Car@6d6f6e28

5.小结

多例模式可以保证系统中一个类有固定个数的实例, 在实现需求的基础上, 能够提高实例的复用性.

实现多例模式的步骤:

  1. 创建一个类, 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
  2. 在类中定义该类被创建的总数量
  3. 在类中定义存放类实例的list集合
  4. 在类中提供静态代码块,在静态代码块中创建类的实例
  5. 提供获取类实例的静态方法

枚举,也是一种多例模式。

三、 工厂设计模式

1概述

​ 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。之前我们创建类对象时, 都是使用new 对象的形式创建, 除new 对象方式以外, 工厂模式也可以创建对象.

2作用

​ 解决类与类之间的耦合问题

3实现步骤

  1. 编写一个Car接口, 提供run方法
  2. 编写一个Falali类实现Car接口,重写run方法
  3. 编写一个Benchi类实现Car接口
  4. 提供一个CarFactory(汽车工厂),用于生产汽车对象
  5. 定义CarFactoryTest测试汽车工厂

4实现代码

1.编写一个Car接口, 提供run方法

public interface Car {
    public void run();
}

2.编写一个Falali类实现Car接口,重写run方法

public class Falali implements Car {
    @Override
    public void run() {
        System.out.println("法拉利以每小时500公里的速度在奔跑.....");
    }
}

3.编写一个Benchi类实现Car接口

public class Benchi implements Car {
    @Override
    public void run() {
        System.out.println("奔驰汽车以每秒1米的速度在挪动.....");
    }
}

4.提供一个CarFactory(汽车工厂),用于生产汽车对象

public class CarFactory {
    /**
     * @param id : 车的标识
     *           benchi : 代表需要创建Benchi类对象
     *           falali : 代表需要创建Falali类对象
     *           如果传入的车标识不正确,代表当前工厂生成不了当前车对象,则返回null
     * @return
     */
    public Car createCar(String id){
        if("falali".equals(id)){
            return new Falali();
        }else if("benchi".equals(id)){
            return new Benchi();
        }
        return null;
    }
}

5.定义CarFactoryTest测试汽车工厂

public class CarFactoryTest {
    public static void main(String[] args) {
        CarFactory carFactory = new CarFactory();
        Car benchi = carFactory.createCar("benchi");
        benchi.run();
        Car falali = carFactory.createCar("falali");
        falali.run();
    }
}

5小结

工厂模式的存在可以改变创建类的方式,解决类与类之间的耦合.

实现步骤:

  1. 编写一个Car接口, 提供run方法
  2. 编写一个Falali类实现Car接口,重写run方法
  3. 编写一个Benchi类实现Car接口
  4. 提供一个CarFactory(汽车工厂),用于生产汽车对象
    )是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。之前我们创建类对象时, 都是使用new 对象的形式创建, 除new 对象方式以外, 工厂模式也可以创建对象.
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值