十九、网络编程
十九、🔴网络编程
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
分类为:ipv4
和ipv6
简单来说 : 就是设备在网络中的唯一标识 , 想要连接哪一台电脑 , 就找到此电脑在网络中的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:服务器只能处理一个客户端请求,接收完一个图片之后,服务器就关闭了。
* <p>
* * 改进方式:循环
*
* 弊端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
* 服务器:接收客户端上传的文件,上传完毕之后给出反馈。
* <p>
* 弊端3:使用循环虽然可以让服务器处理多个客户端请求。但是还是无法同时跟多个客户端进行通信。
* 改进方式:开启多线程处理
* <p>
* 弊端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.实现步骤
- 创建一个类, 将构造方法私有化,使其不能在类的外部通过
new
关键字实例化该类对象。 - 在类中定义该类被创建对象的总数量
- 在类中定义存放类实例的
list
集合 - 在类中提供静态代码块,在静态代码块中创建类的实例
- 提供获取类实例的静态方法
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<Teacher> 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 使用场景
- 工厂模式的存在可以改变创建对象的方式,降低类与类之间的耦合问题.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南