今日目标
- 网络编程
- TCP通信
- 单例设计模式
- 多例设计模式
-
1 网络编程
1.1 软件架构
C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件
- B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等
两种架构各有优势,但是都离不开网络的支持。网络编程 , 就是在一定的协议下,实现两台计算机的通信的程序
1.2 什么是网络编程
-
1.3 网络编程三要素
IP地址 : 设备在网络中的地址,是唯一的标识。
- 端口 : 是设备中程序的是唯一的标识。
协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。
1.4 IP地址
IP:全称”互联网协议地址”,也称IP地址。是分配给上网设备的数字标签。常见的IP分类为:ipv4和ipv6
简单来说 : 就是设备在网络中的唯一标识 , 想要连接哪一台电脑 , 就找到此电脑在网络中的ip地址- IP地址常见分类 : ipv4和ipv6
- 常用命令:
- ipconfig:查看本机IP地址 (mac linux系统 :
ifconfig
) - ping 地址:检查网络是否连通
- ipconfig:查看本机IP地址 (mac linux系统 :
- 特殊IP地址:
- 127.0.0.1:是回送地址也称本地回环地址,可以代表本机的IP地址,一般用来测试使用
- 为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress 供我们使用InetAddress:此类表示Internet协议(IP)地址 | static InetAddress getByName(String host) | 在给定主机名的情况下确定主机的 IP 地址 | | —- | —- | | String getHostName() | 获取此 IP 地址的主机名 | | String getHostAddress() | 返回 IP 地址字符串(以文本表现形式)。 |
1.5 端口
- 端口:应用程序在设备中唯一的标识。
- 端口号:应用程序的唯一标识方式 , 用两个字节表示的整数,它的取值范围是0~65535。
其中0~1023之间的端口号用于一些知名的网络服务或者应用。
我们自己使用1024以上的端口号就可以了。 -
1.6 通信协议
协议:计算机网络中,连接和通信的规则被称为网络通信协议
- UDP协议
- 用户数据报协议(User Datagram Protocol)
- UDP是面向无连接通信协议。
- 速度快,有大小限制一次最多发送64K,数据不安全,易丢失数据。
TCP协议
- 传输控制协议 (Transmission Control Protocol)
- TCP协议是面向连接的通信协议。
- 速度慢,没有大小限制,数据安全
2 TCP通信
2.1 TCP发送数据
package com.itheima.tcp_demo.demo1;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
/*
TCP协议 : 面向连接
客户端 : 发送数据
发送数据的步骤
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 {
//1 创建客户端的Socket对象
Socket socket = new Socket("127.0.0.1",8888);
//启动客户端前,要先启动服务端,这样才能建立连接
//对象能创建成功,意味着三次握手就成立!!
// 2 通获Socket对象取网络中的输出流,写数据
OutputStream netOut = socket.getOutputStream();
//发送信息给服务端
netOut.write("Hello 在么!晚上一起去撸串!".getBytes());
//3 释放资源
socket.close();
}
}
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;
import java.net.SocketAddress;
/*
基于TCP协议
服务端接收数据 :
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 serverSocket = new ServerSocket(8888);
while (true) { //让服务端持续服务
// 2 监听客户端连接,并接受连接,返回一个Socket对象
System.out.println("等待客户端连接中.......");
Socket socket = serverSocket.accept();
//socket 用来和客户端的socket对象进行交互
// 客户端发送数据,服务端收数据
// 服务端发数据,客户端收数据
//获取客户端的IP信息
SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();
System.out.println("remoteSocketAddress = " + remoteSocketAddress);
//3 获取网络中的输入流,用来读取客户端发送过来的数据
InputStream netIn = socket.getInputStream();
//读取数据
byte[] buf = new byte[1024];
int len = netIn.read(buf);
System.out.println("收到客户端发送的数据:" + new String(buf, 0, len));
// 4 释放资源 : 服务端一般不会关闭
socket.close();
//serverSocket.close();//不用关,保证服务端7x24小时不间断执行
}
}
}
2.3 TCP通信原理分析
2.4 TCP三次握手
2.5 TCP练习1
实现客户端和服务端的字符串数据交互package com.itheima.tcp_demo.Demo2;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
TCP协议 : 面向连接
客户端 : 发送数据
发送数据的步骤
1 创建客户端的Socket对象 : Socket(String host, int port) 与指定服务端连接
参数说明:
host 表示服务器端的主机名,也可以是服务器端的IP地址,只不过是String类型的
port 表示服务器端的端口
2 通获Socket对象取网络中的输出流,写数据
OutputStream getOutputStream()
3 通过Socket获取输入流,读取服务端传过来的数据
InputStream getInputStream()
4 释放资源
void close()
*/
public class ClientDemo {
public static void main(String[] args) throws IOException {
//1 创建客户端的Socket对象
Socket socket = new Socket("127.0.0.1", 8888);
//启动客户端前,要先启动服务端,这样才能建立连接
//对象能创建成功,意味着三次握手就成立!!
// 2 通获Socket对象取网络中的输出流,写数据
OutputStream netOut = socket.getOutputStream();
//发送信息给服务端
netOut.write("Hello 在么!晚上一起去撸串!".getBytes());
//3 通过Socket获取输入流,读取服务端传过来的数据
InputStream netIn = socket.getInputStream();
byte[] buf = new byte[1024];
int len = netIn.read(buf);
System.out.println("收到服务端的数据:" + new String(buf, 0, len));
//4 释放资源
socket.close();
}
}
package com.itheima.tcp_demo.Demo2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; /* 基于TCP协议 服务端接收数据 : 1 创建服务器端的Socket对象 : ServerSocket类 ServerSocket(int port) : 构造方法需要绑定一个端口号 , port就是端口号 2 监听客户端连接,并接受连接,返回一个Socket对象 Socket accept() : 该方法会一直阻塞直到建立连接 3 获取网络中的输入流,用来读取客户端发送过来的数据 InputStream getInputStream() 4 获取网络中的输出流,用来发送数据给客户端 OutputStream getOutputStream() 5 释放资源 : 服务端一般不会关闭 void close() */ public class ServerDemo { public static void main(String[] args) throws IOException { // 1 创建服务器端的Socket对象 : ServerSocket类 ServerSocket serverSocket = new ServerSocket(8888); while (true) { //让服务端持续服务 // 2 监听客户端连接,并接受连接,返回一个Socket对象 System.out.println("等待客户端连接中......."); Socket socket = serverSocket.accept(); //socket 用来和客户端的socket对象进行交互 // 客户端发送数据,服务端收数据 // 服务端发数据,客户端收数据 //获取客户端的IP信息 SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); System.out.println("remoteSocketAddress = " + remoteSocketAddress); //3 获取网络中的输入流,用来读取客户端发送过来的数据 InputStream netIn = socket.getInputStream(); //读取数据 byte[] buf = new byte[1024]; int len = netIn.read(buf); System.out.println("收到客户端发送的数据:" + new String(buf, 0, len)); // 4 获取网络中的输出流,用来发送数据给客户端 OutputStream netOut = socket.getOutputStream(); netOut.write("可以呀!时间,地址,人物!!".getBytes()); // 5 释放资源 : 服务端一般不会关闭 socket.close(); //serverSocket.close();//不用关,保证服务端7x24小时不间断执行 } } }
2.6 TCP练习2
实现文件从客户端上传到服务端package com.itheima.tcp_test1; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; /* 需求 : 文件上传和下载 客户端 */ public class ClientDemo { public static void main(String[] args) throws IOException { //1.创建客户端对象 Socket socket = new Socket("127.0.0.1", 6666); //2.获取网络输出流 OutputStream netOut = socket.getOutputStream(); //3.获取本地文件的输入流 FileInputStream localIn = new FileInputStream("day12_demo/client/caocao.jpg"); //4.边读,边写 //把文件的信息发送给服务端 int len; byte[] buf = new byte[1024]; while ((len = localIn.read(buf)) != -1) { netOut.write(buf, 0, len); } //5. 告诉对方,数据发送完毕。关闭输出流 socket.shutdownOutput();// 对方read就会得到-1 // netOut.close(); //6. 获取网络输入流,读取服务端返回的数据 InputStream netIn = socket.getInputStream(); len = netIn.read(buf); System.out.println(new String(buf, 0, len)); //7. 释放资源 localIn.close(); socket.close(); } }
package com.itheima.tcp_test1; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /* 需求 : 文件上传和下载 服务端 1 创建服务器端的Socket对象 : ServerSocket类 ServerSocket(int port) : 构造方法需要绑定一个端口号 , port就是端口号 2 监听客户端连接,并接受连接,返回一个Socket对象 Socket accept() : 该方法会一直阻塞直到建立连接 3 获取网络中的输入流,用来读取客户端发送过来的数据 InputStream getInputStream() 获取本地输出流,用来保存客户端发送的数据到文件中 4 获取网络中的输出流,用来发送数据给客户端 OutputStream getOutputStream() 5 释放资源 : 服务端一般不会关闭 void close() */ public class ServerDemo { public static void main(String[] args) throws IOException { // 1 创建服务器端的Socket对象 : ServerSocket类 ServerSocket serverSocket = new ServerSocket(6666); while (true) { // 2 监听客户端连接,并接受连接,返回一个Socket对象 System.out.println("等待客户端连接!!"); Socket socket = serverSocket.accept(); //3 获取网络中的输入流,用来读取客户端发送过来的数据 InputStream netIn = socket.getInputStream(); //获取本地输出流,用来保存客户端发送的数据到文件中 FileOutputStream localOut = new FileOutputStream("day12_demo/server/caocao.jpg"); System.out.println("文件上传开始!!"); byte[] buf = new byte[1024]; int len; while ((len = netIn.read(buf)) != -1) { localOut.write(buf, 0, len); } System.out.println("文件上传完毕!!"); // 4 获取网络中的输出流,用来发送数据给客户端 OutputStream netOut = socket.getOutputStream(); netOut.write("哥们,你的文件已上传车成功!!".getBytes()); //5 释放资源 : 服务端一般不会关闭 localOut.close();//本地流 socket.close(); } } }
2.7 TCP练习3
文件名覆盖优化,线程优化
线程池优化package com.itheima.tcp_test1; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.text.SimpleDateFormat; /* 优化:文件被覆盖的问题 解决方案:让每次的名字都发生变化 */ public class ServerBetter2 { public static void main(String[] args) throws IOException { // 1 创建服务器端的Socket对象 : ServerSocket类 ServerSocket serverSocket = new ServerSocket(6666); while (true) { // 2 监听客户端连接,并接受连接,返回一个Socket对象 System.out.println("等待客户端连接!!"); Socket socket = serverSocket.accept(); new Thread(new UploadTask(socket)).start(); } } } /* 上传任务 */ class UploadTask implements Runnable { static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); private final Socket socket; public UploadTask(Socket socket) { this.socket = socket; } @Override public void run() { FileOutputStream localOut = null; try { //3 获取网络中的输入流,用来读取客户端发送过来的数据 InputStream netIn = socket.getInputStream(); //获取本地输出流,用来保存客户端发送的数据到文件中 /*FileOutputStream localOut = new FileOutputStream("day12_demo/server/"+ UUID.randomUUID()+".jpg"); //UUID.randomUUID() 生成不同的随机的字符串,让文件名不会重复 */ localOut = new FileOutputStream("day12_demo/server/" + sdf.format(System.currentTimeMillis()) + ".jpg"); System.out.println("文件上传开始!!"); byte[] buf = new byte[1024]; int len; while ((len = netIn.read(buf)) != -1) { localOut.write(buf, 0, len); } System.out.println("文件上传完毕!!"); // 4 获取网络中的输出流,用来发送数据给客户端 OutputStream netOut = socket.getOutputStream(); netOut.write("哥们,你的文件已上传车成功!!".getBytes()); } catch (Exception e) { System.out.println("上传有误:" + socket.getRemoteSocketAddress() + e); } finally { //5 释放资源 : 服务端一般不会关闭 try { if (localOut != null) localOut.close();//本地流 } catch (IOException e) { e.printStackTrace(); } try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
package com.itheima.tcp_test1; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 线程池优化服务端 */ public class ServerBetter3 { public static void main(String[] args) throws IOException { //先定义一个线程池 ExecutorService pool = Executors.newFixedThreadPool(50); // 1 创建服务器端的Socket对象 : ServerSocket类 ServerSocket serverSocket = new ServerSocket(6666); while (true) { // 2 监听客户端连接,并接受连接,返回一个Socket对象 System.out.println("等待客户端连接!!"); Socket socket = serverSocket.accept(); //创建任务给线程 //new Thread(new UploadTask(socket)).start(); //创建任务给线程池 pool.submit(new UploadTask(socket)); } } }
3 单例设计模式
学习目标
-
内容讲解
正常情况下一个类可以创建多个对象
public static void main(String[] args) { // 正常情况下一个类可以创建多个对象 Person p1 = new Person(); Person p2 = new Person(); Person p3 = new Person(); }
如果说有时一个对象就能搞定的事情 , 非要创建多个对象 , 浪费内存!!!
3.1 单例设计模式的作用
单例模式,是一种常用的软件设计模式。通过单例模式可以保证项目中,应用该模式的这个类只有一个实例(对象)。
即一个类只有一个对象实例。- 好处 :可以节省内存,共享数据
3.2 单例设计模式实现步骤
- 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
- 在该类内部产生一个唯一的实例化对象,并且将其封装为private static 类型的成员变量。
-
3.3 单例设计模式的类型
根据创建对象的时机单例设计模式又分为以下两种:
饿汉单例设计模式
- 懒汉单例设计模式
3.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; } }
需求:定义一个皇帝类,要求对象只能存在一个。
package com.itheima.singledesign; /* 需求 : 使用单例模式(饿汉式) , 要求此类只能有一个对象 步骤 : 1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。 2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static 类型的成员变量。 3. 定义一个静态方法返回这个唯一对象。 */ public class King { private String name; //1. 将构造方法私有化, private King(String name) {//当前类之外不能创建对象 this.name = name; } //2. 在该类内部产生一个唯一的实例化对象, private static final King king = new King("秦始皇"); //3. 定义一个静态方法返回这个唯一对象。 public static King getKing() { return king; } @Override public String toString() { return "King{" + "name='" + name + '\'' + '}'; } }
=============================================================================================
3.5 懒汉单例设计模式
懒汉单例设计模式就是调用getInstance()方法时对象才被创建
也就是说先不急着实例化出对象,等要用的时候才实例化出对象。不着急,故称为“懒汉模式”。
代码如下:
public class Singleton {
// 1.将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
private Singleton() {}
// 2.在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
private static Singleton instance;
// 3.定义一个静态方法返回这个唯一对象。要用的时候才例化出对象
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
- ##### 注意 :
- 懒汉单例设计模式在多线程环境下可能会实例化出多个对象,不能保证单例的状态,所以加上关键字:synchronized,保证其同步安全。
- 需求:使用懒汉单例 ,改写皇帝类的单例模式
```java
package com.itheima.singledesign;
/*
需求 : 使用单例模式(懒汉式) , 要求此类只能有一个对象
步骤 :
1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
2. 在该类内部定义一个private static修饰的成员变量 . 此变量不需要赋值
3. 定义一个静态方法返回这个唯一对象。 此方法需要加上synchronized关键字保证在多线程中也只有一个实例对象
*/
public class King2 {
// 1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
private King2() {
}
// 2. 在该类内部定义一个private static修饰的成员变量 . 此变量不需要赋值
private static King2 king;// null
// 3. 定义一个静态方法返回这个唯一对象。 此方法需要加上synchronized关键字保证在多线程中也只有一个实例对象
public static synchronized King2 getInstance() {
if (king == null) {
king = new King2();
}
return king;
}
}
知识小结
- 单例模式可以保证系统中一个类只有一个对象实例。
实现单例模式的步骤:
-
内容讲解
4.1 多例设计模式的作用
多例模式,是一种常用的设计模式之一。通过多例模式可以保证项目中,应用该模式的类有固定数量的实例(对象)。
多例类要自我创建并管理自己的实例,还要向外界提供获取本类实例的方法。使用场景:线程池
线程池 = Executors.newFixedThreadPool(3);
4.2.实现步骤
1.创建一个类, 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
2.在类中定义该类被创建对象的总数量
3.在类中定义存放类实例的list集合
4.在类中提供静态代码块,在静态代码块中创建类的实例
5.提供获取类实例的静态方法4.3.实现代码
某一个学科有固定3位老师,年级中上该课程的老师就是这三位老师其中一位
要求使用多例模式 ,每次获取的都是这三位老师其中一位package com.itheima.moreinstance_demo; import java.util.ArrayList; import java.util.Random; /* 需求 : 某一个学科有固定3位老师,年级中上该课程的老师就是这三位老师其中一位 要求使用多例模式 ,每次获取的都是这三位老师其中一位 实现步骤 : 1.创建一个类, 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。 2.在类中定义该类被创建对象的总数量 3.在类中定义存放类实例的list集合 4.在类中提供静态代码块,在静态代码块中创建类的实例 5.提供获取类实例的静态方法 枚举类型就是多例模式的一种实现方式 */ public class Teacher { private String name; //1 将构造方法私有化, private Teacher(String name) { this.name = name; } //2 创建对象的总数 private static final int count = 3; //3 定义集合存储对象 private static ArrayList<Teacher> teachers = new ArrayList<>(); //4 在类中提供静态代码块中初始化 static { teachers.add(new Teacher("小花"));//0 teachers.add(new Teacher("小红"));//1 teachers.add(new Teacher("昌老师"));//2 /*for (int i = 0; i < count; i++) { teachers.add(new Teacher("老师" + i)); }*/ } //5 提供获取类实例的静态方法 public static Teacher getInstance(){ //new Random().nextInt(teachers.size()) int i = new Random().nextInt(count); return teachers.get(i); } @Override public String toString() { return "Teacher{" + "name='" + name + '\'' + '}'; } } class Demo{ public static void main(String[] args) { for (int i = 0; i < 10; i++) { Teacher t = Teacher.getInstance(); System.out.println("t = " + t); } } }
4.4 小结
多例模式作用 : 可以保证项目中一个类有固定个数的实例, 在实现需求的基础上, 能够提高实例的复用性.实现多例模式的步骤 :
-
内容讲解
5.1 概述
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。之前我们创建类对象时, 都是使用 new 对象的形式创建, 除new 对象方式以外, 工厂模式也可以创建对象.
5.2 作用
-
5.3案例实践
需求:定义汽车工厂类,生产各种品牌的车
- 实现步骤
- 编写一个Car接口, 提供run方法
- 编写一个Falali类实现Car接口,重写run方法
- 编写一个Benchi类实现Car接口,重写run方法
- 提供一个CarFactory(汽车工厂),用于生产汽车对象
- 定义CarFactoryTest测试汽车工厂
实现代码
package com.itheima.factorydesign_demo;
/*
- 需求:定义汽车工厂类,生产各种品牌的车
- 实现步骤
- 编写一个Car接口, 提供run方法
- 编写一个Falali类实现Car接口,重写run方法
- 编写一个Benchi类实现Car接口
- 提供一个CarFactory(汽车工厂),用于生产汽车对象
- 定义CarFactoryTest测试汽车工厂
*/
public class CarTest {
public static void main(String[] args) {
//Falali f = new Falali();//耦合
//使用工厂创建对象进行解耦
Car benchi = CarFactory.makeCar(CarType.BENCHI);
benchi.run();
Car falali = CarFactory.makeCar(CarType.FALALI);
falali.run();
}
}
interface Car {
void run();
}
class Falali implements Car {
@Override
public void run() {
System.out.println("法拉利跑车,3秒破百!");
}
}
class Benchi implements Car {
@Override
public void run() {
System.out.println("奔驰车,5秒破百!");
}
}
enum CarType {
FALALI, BENCHI
}
//设计工厂类,生产对象
class CarFactory {
public static Car makeCar(CarType carType) {
switch (carType) {
case BENCHI:
return new Benchi();
case FALALI:
return new Falali();
default:
return null;
}
}
}
知识小结
- 工厂模式的存在可以改变创建对象的方式,降低类与类之间的耦合问题.