十九、网络编程

十九、🔴网络编程

19.1 网络编程

19.1.1 软件架构

  • C/S 结构 :全称为 Client/Server 结构,是指客户端和服务器结构。常见程序有 QQ 、迅雷等软件
  • B/S 结构 :全称为 Browser/Server 结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等
  • 两种架构各有优势,但是都离不开网络的支持。网络编程 , 就是在一定的协议下,实现两台计算机的通信的程序

19.1.2 什么是网络编程

  • 在网络通信协议下,不同计算机上运行的程序,可以进行数据传输

19.1.3 网络编程三要素

  • IP 地址 : 设备在网络中的地址,是唯一的标识。
  • 端口 : 设备在网络中的地址,是唯一的标识。
  • 数据在网络中传输的规则,常见的协议有 UDP 协议和 TCP 协议。

19.1.4 IP地址

  • IP :全称”互联网协议地址”,也称 IP 地址。是分配给上网设备的数字标签。常见的 IP 分类为: ipv4ipv6
    简单来说 : 就是设备在网络中的唯一标识 , 想要连接哪一台电脑 , 就找到此电脑在网络中的ip地址
  • IP 地址常见分类 :
  • ipv4
  • ipv6
  • 常用命令:
  • ipconfig :查看本机 IP 地址
  • Ping IP 地址:检查网络是否连通
  • 特殊IP地址:
  • 127.0.0.1:是回送地址也称本地回环地址,可以代表本机的 IP 地址,一般用来测试使用
  • 为了方便我们对 IP 地址的获取和操作, Java 提供了一个类 InetAddress 供我们使用
    InetAddress:此类表示 Internet 协议( IP)地址
    | static InetAddress getByName(String host) | 在给定主机名的情况下确定主机的 IP 地址 |
    | — | — |
    | String getHostName() | 获取此 IP 地址的主机名 |
    | String getHostAddress() | 返回 IP 地址字符串(以文本表现形式)。 |

19.1.5 端口

  • 端口:应用程序在设备中唯一的标识。
  • 端口号:应用程序的唯一标识方式 , 用两个字节表示的整数,它的取值范围是0~65535。
    其中0~1023之间的端口号用于一些知名的网络服务或者应用。
    我们自己使用1024以上的端口号就可以了。
  • 注意:一个端口号只能被一个应用程序使用。

19.1.6 通信协议

  • 协议:计算机网络中,连接和通信的规则被称为网络通信协议
  • UDP协议
  • 用户数据报协议(User Datagram Protocol)
  • UDP是面向无连接通信协议。
  • 速度快,有大小 限制一次最多发送64K,数据不安全, 易丢失数据。一般用于传输大型视频音频
  • TCP协议
  • 传输控制协议 (Transmission Control Protocol)
  • TCP协议是面向连接的通信协议。
  • 速度 没有大小限制,数据 *安全

19.2 TCP通信协议

19.2.1 TCP发送数据案例

package com.itheima.tcp_demo.demo1;

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

/*
    客户端 :

    发送数据的步骤
        1 创建客户端的Socket对象 : Socket(String host, int port) 与指定服务端连接
            参数说明:
            host 表示服务器端的主机名,也可以是服务器端的IP地址,只不过是String类型的
            port 表示服务器端的端口

        2 通获Socket对象取网络中的输出流,写数据
            OutputStream getOutputStream()

        3 释放资源
            void close()

 */
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 创建客户端的Socket对象(Socket) 与指定服务端连接
        // host:服务端的ip地址,哪台机器
        // port:端口,哪个程序
        Socket socket = new Socket("127.0.0.1", 10010);

        // 通获Socket对象取网络中的输出流,写数据
        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());

        // while(true){}

        // 释放资源
        os.close();
        socket.close();
    }
}

19.2.2 TCP接收数据案例

package com.itheima.tcp_demo.demo1;

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

/*
     服务端接收数据 :

    1 创建服务器端的Socket对象 : ServerSocket类
        ServerSocket(int port)  : 构造方法需要绑定一个端口号 , port就是端口号

    2 监听客户端连接,并接受连接,返回一个Socket对象
        Socket accept() : 该方法会一直阻塞直到建立连接

    3 获取网络中的输入流,用来读取客户端发送过来的数据
        InputStream getInputStream()

    4 释放资源 : 服务端一般不会关闭
        void close()
 */
public class ServerDemo {
    public static void main(String[] args) throws IOException {
//        1 创建服务器端的Socket对象 : ServerSocket类
//        ServerSocket(int port)  : 构造方法需要绑定一个端口号 , port就是端口号
        ServerSocket serverSocket = new ServerSocket(10010);

//        2 监听客户端连接,并接受连接,返回一个Socket对象
//        Socket accept() : 该方法会一直阻塞直到建立连接
        Socket socket = serverSocket.accept();
//
//        3 获取网络中的输入流,用来读取客户端发送过来的数据
//        InputStream getInputStream()
        InputStream is = socket.getInputStream();
        int by;
        System.out.println("read方法执行前");
        while ((by = is.read()) != -1) {
            System.out.print((char) by);
        }
        System.out.println("read方法执行后");
    }
}

19.2.3 TCP通信原理分析

十九、网络编程

19.2.4 TCP三次握手

十九、网络编程

19.2.5 TCP练习1:客户端与服务端数据交互

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

/**
 * @author: Carl Zhang
 * @create: 2022-01-06 13:58
 * 客户端:发送数据,接收服务器反馈数据
 */
public class ClientDemo02 {
    public static void main(String[] args) throws IOException {
        //发送数据,
        //1. 创建客户端Socket对象
        Socket socket = new Socket("127.0.0.1", 2008);
        //2. 获取socket对象对应的字节输出流
        OutputStream outputStream = socket.getOutputStream();
        //3. 通过字节输出流向服务器传输数据
        outputStream.write("hello".getBytes());
        outputStream.flush();
        //4. 写完向服务端发送结束命令
        socket.shutdownOutput();

        //接收服务器反馈数据
        //5. 通过socket对象获取字节输入流
        InputStream inputStream = socket.getInputStream();
        //通过转换输入流将字节输入流封装成字符流,再获取对应字符缓冲输入流
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(inputStream));

        //6. 从流中打印出服务器反馈的数据
        String s;
        while ((s = bufferedReader.readLine()) != null) {
            System.out.println(s);
        }

        //7. 释放资源,关闭了socket对象,通过socket对象创建的流也会自动关闭
        socket.close();
    }
}
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author: Carl Zhang
 * @create: 2022-01-06 14:28
 * 服务器:接收数据,给出反馈
 */
public class ServerDemo02 {
    public static void main(String[] args) throws IOException {
        //接收数据
        //1. 获取服务端的ServerSocket对象
        ServerSocket serverSocket = new ServerSocket(2008);
        //2. 监听连接情况,获取连接的Socket对象
        Socket accept = serverSocket.accept();
        //3. 获取Socket对象的字节输入流
        InputStream inputStream = accept.getInputStream();
        //4. 将流中的信息打印出来
        int b;
        while ((b = inputStream.read()) != -1) {
            System.out.print((char) b);
        }

        //给出反馈
        //5. 获取Socket对象的字节输出流
        //6. 通过转换流将字节流封装成字符流,再获取字符缓冲流
        BufferedWriter bufferedWriter = new BufferedWriter(
                new OutputStreamWriter(accept.getOutputStream()));
        //7. 通过字符流写入中文反馈
        bufferedWriter.write("你谁啊");
        // 必须有换行 , 因为readLine读到换行结束
        bufferedWriter.newLine();
        //   刷新
        bufferedWriter.flush();
        //8. 写完了就发送结束命令
        accept.shutdownOutput();

        //9. 释放资源 -- 可以省略
        accept.close();
        serverSocket.close();
    }
}

19.2.6 TCP练习2 :图片上传与下载

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

/**
 * @author: Carl Zhang
 * @create: 2022-01-06 14:54
 * 客户端:将本地文件上传到服务器。接收服务器的反馈。
 */
public class ClientExercise02 {
    public static void main(String[] args) throws IOException {
        //通过字节缓冲输出流,将文件发送到服务器
        // 创建关联服务器的Socket对象
        Socket socket = new Socket("127.0.0.1", 10086);

        // 获取网络中的字节输出流,并转换成字节缓冲流
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
                socket.getOutputStream());

        // 创建文件的字节缓冲流输入流
        BufferedInputStream bufferedInputStream = new BufferedInputStream(
                new FileInputStream("Client\\战狼02.jpg"));

        // 循环读取文件的信息,通过字节缓冲流发送到服务器
        int r;
        while ((r = bufferedInputStream.read()) != -1) {
            bufferedOutputStream.write(r);
            bufferedOutputStream.flush();
        }

        // 写完发送结束命令
        socket.shutdownOutput();

        // 打印服务器的反馈
        // 获取网络中的字节输入流转换并包装成字符缓冲输入流
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));

        String s;
        while ((s = bufferedReader.readLine()) != null) {
            System.out.println(s);
        }

        //释放资源
        socket.close();
    }
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author: Carl Zhang
 * @create: 2022-01-06 14:55
 * 服务器:接收客户端上传的文件,上传完毕之后给出反馈。
 */
public class ServerExercise02 {
    public static void main(String[] args) throws IOException {
        //接收客户端上传的文件

        //监听并获取连接了服务器的Socket对象
        ServerSocket serverSocket = new ServerSocket(10086);
        Socket accept = serverSocket.accept();

        //获取网络中的字节输入流,然后封装成字节缓冲输入流
        BufferedInputStream bufferedInputStream = new BufferedInputStream(
                accept.getInputStream());

        //创建文件要存放位置的字节缓冲输出流
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
                new FileOutputStream("Server\\战狼_副本.jpg"));

        //循环将文件拷贝到指定位置
        int r;
        while ((r = bufferedInputStream.read()) != -1) {
            bufferedOutputStream.write(r);
            bufferedOutputStream.flush();
        }

        //给出反馈
        //获取socket对象的字节输出流,转换封装成字符缓冲输出流
        BufferedWriter bufferedWriter = new BufferedWriter(
                new OutputStreamWriter(accept.getOutputStream()));

        //在流中写如反馈信息
        bufferedWriter.write("收到了");
        bufferedWriter.newLine();
        bufferedWriter.flush();、

        //写完反馈发送结束命令
        accept.shutdownOutput();

        //释放资源
        accept.close();
        serverSocket.close();
    }
}

19.2.7 TCP练习3 图片上传与下载优化

  • 弊端1:服务器只能处理一个客户端请求,接收完一个图片之后,服务器就关闭了。
  • 改进方式:循环
  • 弊端2:第二次上传文件的时候,会把第一次的文件给覆盖。
  • 改进方式: UUID. randomUUID() 方法生成随机的文件名
package com.heima.tcp;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;

/**
 * @author: Carl Zhang
 * @create: 2022-01-06 14:55
 * 服务器:接收客户端上传的文件,上传完毕之后给出反馈。
 * 弊端1:服务器只能处理一个客户端请求,接收完一个图片之后,服务器就关闭了。
 *
 * * 改进方式:循环
 *
 * 弊端2:第二次上传文件的时候,会把第一次的文件给覆盖。
 *
 * * 改进方式:UUID. randomUUID() 方法生成随机的文件名
 */
public class ServerExercise02 {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(10086);

        while (true) {
            //监听并获取连接了服务器的Socket对象
            Socket accept = serverSocket.accept();

            //获取网络中的字节流和文件的字节流
            BufferedInputStream bufferedInputStream = new BufferedInputStream(
                    accept.getInputStream());
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
                    new FileOutputStream("Server\\"+ UUID.randomUUID() +".jpg"));

            //循环将文件拷贝到指定位置
            int r;
            while ((r = bufferedInputStream.read()) != -1) {
                bufferedOutputStream.write(r);
                bufferedOutputStream.flush();
            }

            //使用完本地流后要关闭
            bufferedOutputStream.close();

            //给出反馈
            //获取socket对象的字节输出流,转换封装成字符缓冲输出流
            BufferedWriter bufferedWriter = new BufferedWriter(
                    new OutputStreamWriter(accept.getOutputStream()));
            //在流中写如反馈信息
            bufferedWriter.write("Server:收到了");
            bufferedWriter.newLine();
            bufferedWriter.flush();
            //写完反馈发送结束命令
            accept.shutdownOutput();
        }

        //释放资源 -- 可以忽略
        //accept.close();
        //serverSocket.close();
    }
}
  • 弊端3:使用循环虽然可以让服务器处理多个客户端请求。但是还是无法同时跟多个客户端进行通信。
  • 改进方式:开启多线程处理
  • 弊端4:使用多线程虽然可以让服务器同时处理多个客户端请求。但是资源消耗太大。
  • 改进方式:加入线程池
package com.heima.tcp;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
import java.util.concurrent.*;

/**
 * @author: Carl Zhang
 * @create: 2022-01-06 14:55
 * 服务器:接收客户端上传的文件,上传完毕之后给出反馈。
 *
 * 弊端3:使用循环虽然可以让服务器处理多个客户端请求。但是还是无法同时跟多个客户端进行通信。
 * 改进方式:开启多线程处理
 *
 * 弊端4:使用多线程虽然可以让服务器同时处理多个客户端请求。但是资源消耗太大。
 * 改进方式:加入线程池
 */
public class ServerExercise04 {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(10086);

        while (true) {
            Socket accept = serverSocket.accept(); //唯一的

            //创建线程并启动
            //手动创建线程资源消耗太大,通过线程池解决
            //new Thread(new ServerThread(accept)).start();

            //使用线程池 核心线程小于临时线程会报错IllegalArgumentException
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,
                    5, 60, TimeUnit.MINUTES, new ArrayBlockingQueue(2), new ThreadPoolExecutor.AbortPolicy());
            threadPoolExecutor.submit(new ServerThread(accept));
        }

        //释放资源 -- 可以忽略
        //accept.close();
        //serverSocket.close();
    }
}
package com.heima.tcp;

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

/**
 * @author: Carl Zhang
 * @create: 2022-01-06 16:59
 *
 */
public class ServerThread implements Runnable {
    BufferedOutputStream bufferedOutputStream = null;
    private final Socket accept ;

    public ServerThread(Socket socket) {
        this.accept = socket;
    }

    @Override
    public void run() {
        try {
            //获取网络中的字节流和文件的字节流
            BufferedInputStream bufferedInputStream = new BufferedInputStream(
                    accept.getInputStream());
            bufferedOutputStream = new BufferedOutputStream(
                    new FileOutputStream("Server\\"+ UUID.randomUUID() +".jpg"));

            //循环将文件拷贝到指定位置
            int r;
            while ((r = bufferedInputStream.read()) != -1) {
                bufferedOutputStream.write(r);
                bufferedOutputStream.flush();
            }

            //给出反馈
            //获取socket对象的字节输出流,转换封装成字符缓冲输出流
            BufferedWriter bufferedWriter = new BufferedWriter(
                    new OutputStreamWriter(accept.getOutputStream()));
            //在流中写如反馈信息
            bufferedWriter.write("Server:收到了");
            bufferedWriter.newLine();
            bufferedWriter.flush();
            //写完反馈发送结束命令
            accept.shutdownOutput();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedOutputStream != null) {
                //关闭本地流
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

19.3 多例设计模式

19.3.1 多例设计模式的作用

  • 多例模式,是一种常用的软件设计模式。通过多例模式可以保证系统中,应用该模式的类有 固定数量的实例
    多例类要自我创建并管理自己的实例,还要向外界提供获取本类实例的方法。
  • 使用场景:线程池
线程池 = Executors.newFixedThreadPool(3);

19.3.2.实现步骤

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

19.3.3 实现代码

  • 某一个学科有固定3位老师,年级中上该课程的老师就是这三位老师其中一位
    要求使用多例模式 ,每次获取的都是这三位老师其中一位
package com.itheima.moreinstance_demo;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/*
    需求  : 某一个学科有固定3位老师,年级中上该课程的老师就是这三位老师其中一位
            要求使用多例模式 ,每次获取的都是这三位老师其中一位

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

    // 在类中定义该类被创建对象的总数量
    private final int maxCount = 3;

    // 在类中定义存放类实例的list集合
    private static List list = new ArrayList<>();

    // 在类中提供静态代码块,在静态代码块中创建类的实例
    static {
        for (int i = 0; i < 3; i++) {
            list.add(new Teacher());
        }
    }

    // 提供获取类实例的静态方法
    public static Teacher getInstance() {
        Random r = new Random();
        return list.get(r.nextInt(list.size()));
    }
}
package com.itheima.moreinstance_demo;

import org.junit.Test;

import static org.junit.Assert.*;

public class TeacherTest {

    @Test
    public void getInstance() {
        // Teacher teacher = new Teacher();

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

19.3.4 注意事项和使用细节

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

  • 实现多例模式的步骤 :

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

19.4 工厂设计模式

19.4.1 概述

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

19.4.2 作用

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

19.4.3 案例实践

  • 需求:定义汽车工厂类,生产各种品牌的车
  • *实现代码
package com.itheima.factorydesign_demo;

/*
      - 需求:定义汽车工厂类,生产各种品牌的车

      - 实现步骤
      - 编写一个Car接口, 提供run方法
      - 编写一个Falali类实现Car接口,重写run方法
      - 编写一个Benchi类实现Car接口
      - 提供一个CarFactory(汽车工厂),用于生产汽车对象
      - 定义CarFactoryTest测试汽车工厂
      此时如果更改了Falali类构造,可能出现问题的只有工厂类,其他使用了Falali类的地方都不会出现问题,达到了解耦的目的
 */
public class CarTest {
    public static void main(String[] args) {
        // Falali falali = new Falali();
//        Car car = CarFactory.getInstance("奔驰");
//        car.run();

        CarFactory.getInstance(CarType.AOTI);
    }
}

// 编写一个Car接口, 提供run方法
interface Car {
    public abstract void run();
}

// 编写一个Falali类实现Car接口,重写run方法
class Falali implements Car {
//    public Falali(int a) {
//    }

    @Override
    public void run() {
        System.out.println("法拉利破百只需要 3秒!");
    }
}

// 编写一个Benchi类实现Car接口
class Benchi implements Car {
    @Override
    public void run() {
        System.out.println("奔驰破百只需要 5秒!");
    }
}

// 枚举项都是车的品牌
enum CarType {
    FALALI, BENCHI, AOTI, DAZHONG, HONGQI
}

// 提供一个CarFactory(汽车工厂),用于生产汽车对象
class CarFactory {
    public static Car getInstance(CarType carType) {

        switch (carType) {
            case FALALI:
                return new Falali   ();
            case BENCHI:
                return new Benchi();
            default:
                return null;
        }
    }
}

//// 提供一个CarFactory(汽车工厂),用于生产汽车对象
//class CarFactory {
//    public static Car getInstance(String carName) {
//        if (carName.equals("奔驰")) {
//            return new Benchi();
//        } else if (carName.equals("法拉利")) {
//            return new Falali(100);
//        }
//        return null;
//    }
//}

19.4.4 使用场景

  • 工厂模式的存在可以改变创建对象的方式,降低类与类之间的 耦合问题.

Original: https://www.cnblogs.com/Carl-Zhang/p/15782487.html
Author: Carl-Zhang
Title: 十九、网络编程

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/685889/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球