day14 网络编程
第一章 基础概念
1.1 软件结构
C/S: 客户端-服务器端这种程序。
常见的软件
qq 魔兽世界 LOL 迅雷 百度云盘...
优点:
画面绚丽,安全性高。
缺点:
会占用硬盘空间
**B/S:**浏览器端-服务器端这种程序。
常见软件
tilas 系统,京东,淘宝,探花交友,黑马头条.....
优点:
不用下载,在页面就可以实现交互,方便快捷,只要有浏览器即可。
缺点:
效果没那么绚丽,效果依赖于网速。
JAVA 做 B/S 更多一些。
1.2 网络三要素
协议
- **协议:**计算机网络通信必须遵守的规则,已经介绍过了,不再赘述。
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地址/网站
特殊的 IP 地址
- 本机 IP 地址:
127.0.0.1
、localhost
。
端口号
网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
如果说IP 地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
- 端口号:用两个字节表示的整数,它的取值范围是 0~65535。其中,0~1023 之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用 1024 以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
利用协议
+IP地址
+端口号
三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
TCP 与 UDP 协议区别
TCP 协议(抓的比较细) 传输控制协议 (Transmission Control Protocol) TCP 协议是面向连接的通信协议,传输数据之前,先确定对方是否能够进行连接,然后再进行数据的传输。
(三次握手 四次挥手)
速度慢,没有大小限制,数据安全。 文件的上传下载。
UDP 协议
数据报协议 数据是打成包传输的。
UDP 协议是一个面向无连接的通信协议,不管对方是否存在,直接进行数据的传输,类似于 大喇叭。
速度快,有大小限制,每次传输最多 64KB,不可靠,容易丢失数据。
视频聊天 音频聊天。丢点数据无所谓
第二章 TCP 通信协议
2.1 概述
TCP 通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
两端通信时步骤:
- 服务端程序,需要事先启动,等待客户端的连接。
- 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
在 Java 中,提供了两个类用于实现 TCP 通信程序:
- 客户端:
java.net.Socket
类表示。创建Socket
对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。 - 服务端:
java.net.ServerSocket
类表示。创建ServerSocket
对象,相当于开启一个服务,并等待客户端的连接。
2.2 Socket 类
Socket
类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
构造方法
public Socket(String host, int port)
:创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的 host 是 null ,则相当于指定地址为回送地址。
Socket client = new Socket("127.0.0.1", 6666);
成员方法
public InputStream getInputStream()
: 返回此套接字的输入流。- 如果此 Scoket 具有相关联的通道,则生成的 InputStream 的所有操作也关联该通道。
- 关闭生成的 InputStream 也将关闭相关的 Socket。
public OutputStream getOutputStream()
: 返回此套接字的输出流。- 如果此 Scoket 具有相关联的通道,则生成的 OutputStream 的所有操作也关联该通道。
- 关闭生成的 OutputStream 也将关闭相关的 Socket。
public void close()
:关闭此套接字。- 一旦一个 socket 被关闭,它不可再使用。
- 关闭此 socket 也将关闭相关的 InputStream 和 OutputStream 。
public void shutdownOutput()
: 禁用此套接字的输出流。- 任何先前写出的数据将被发送,随后终止输出流。
2.3 ServerSocket 类
ServerSocket
类:这个类实现了服务器套接字,该对象等待通过网络的请求。
构造方法
public ServerSocket(int port)
:使用该构造方法在创建 ServerSocket 对象时,就可以将其绑定到一个指定的端口号上,参数 port 就是端口号。
ServerSocket server = new ServerSocket(6666);
成员方法
public Socket accept()
:侦听并接受连接,返回一个新的 Socket 对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。
2.4 简单的 TCP 网络程序
TCP 通信分析图解
- 【服务端】启动,创建 ServerSocket 对象,等待连接。
- 【客户端】启动,创建 Socket 对象,请求连接。
- 【服务端】接收连接,调用 accept 方法,并返回一个 Socket 对象。
- 【客户端】Socket 对象,获取 OutputStream,向服务端写出数据。
- 【服务端】Scoket 对象,获取 InputStream,读取客户端发送的数据。
- 【服务端】Socket 对象,获取 OutputStream,向客户端回写数据。
- 【客户端】Scoket 对象,获取 InputStream,解析回写数据。
- 【客户端】释放资源,断开连接。
2.5 简单的 TCP 通信案例
客户端向服务器发送数据--客户端程序
package com.itheima.tcp01;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
//客户端 1-3步 是完成连接服务器 并且向服务器发送一个数据 4-5读取服务器回写数据
public class Client {
public static void main(String[] args) throws IOException {
//1:创建客户端对象 确认服务器的存在,建立起连接
Socket socket = new Socket("192.168.21.36", 10086);
System.out.println("客户端在连接服务器端,连接成功。");
//2: 获取 输出流 把数据写到流中
OutputStream out = socket.getOutputStream();
System.out.println("客户端正在给服务器发数据");
//3:写一个数据过去
out.write("你好吗,今天天气不错,挺风和日丽的...".getBytes());//字符串调用getBytes()把字符串变成字节数组
out.close();
socket.close();
}
}
客户端向服务器发送数据--服务器端程序
package com.itheima.tcp01;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
//服务器端 1-4 开启服务器 等待客户端连接 处理客户端发送数据 5-6 服务器回写数据给客户端
public class Server {
public static void main(String[] args) throws IOException {
//1:创建服务器对象 并指定端口号
ServerSocket server = new ServerSocket(10086);
System.out.println("服务器对象已经启动...");
//2: 等待客户端连接
Socket socket = server.accept();//如果没有连接 就阻塞
//如果有连接就返回连接的哪个客户端 在服务器这里的 对象形式
String ip = socket.getInetAddress().getHostAddress();
System.out.println("客户端已经连接:ip:"+ip);
//3: 读取数据 获取字节输入流
InputStream in = socket.getInputStream();
System.out.println("服务器正在读取客户端发来的请求数据.");
//4:采用一个字节数组方式读取数据
byte[] buffer = new byte[1024];
// 读
int len = in.read(buffer);
//解析内容
System.out.println(ip+"客户端说:"+new String(buffer,0,len));
//关闭资源
in.close();
socket.close();
}
}
服务器向客户端回写数据--服务器端程序
package com.itheima.tcp01;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
//客户端 1-3步 是完成连接服务器 并且向服务器发送一个数据 4-5读取服务器回写数据
public class Client {
public static void main(String[] args) throws IOException {
//1:创建客户端对象 确认服务器的存在,建立起连接
Socket socket = new Socket("192.168.21.36", 10086);
System.out.println("客户端在连接服务器端,连接成功。");
//2: 获取 输出流 把数据写到流中
OutputStream out = socket.getOutputStream();
System.out.println("客户端正在给服务器发数据");
//3:写一个数据过去
out.write("你好吗,今天天气不错,挺风和日丽的...".getBytes());//字符串调用getBytes()把字符串变成字节数组
//4: 读数据 获取输入流
InputStream in = socket.getInputStream();
System.out.println("客户端正在 读取服务器发来的数据");
//5: 一个字节数组去读取
byte[] buffer = new byte[1024];
int len = in.read(buffer);
System.out.println("服务器给的响应是:"+new String(buffer,0,len));
in.close();
out.close();
socket.close();
}
}
服务器向客户端回写数据--客户端程序
package com.itheima.tcp01;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
//服务器端 1-4 开启服务器 等待客户端连接 处理客户端发送数据 5-6 服务器回写数据给客户端
public class Server {
public static void main(String[] args) throws IOException {
//1:创建服务器对象 并指定端口号
ServerSocket server = new ServerSocket(10086);
System.out.println("服务器对象已经启动...");
//2: 等待客户端连接
Socket socket = server.accept();//如果没有连接 就阻塞
//如果有连接就返回连接的哪个客户端 在服务器这里的 对象形式
String ip = socket.getInetAddress().getHostAddress();
System.out.println("客户端已经连接:ip:"+ip);
//3: 读取数据 获取字节输入流
InputStream in = socket.getInputStream();
System.out.println("服务器正在读取客户端发来的请求数据.");
//4:采用一个字节数组方式读取数据
byte[] buffer = new byte[1024];
// 读
int len = in.read(buffer);
//解析内容
System.out.println(ip+"客户端说:"+new String(buffer,0,len));
System.out.println("服务器给客户端正在写数据");
// 5: 回写数据 获取输出流 socket搞出来
OutputStream out = socket.getOutputStream();
//6:写
out.write("我一点都不好".getBytes());
//关闭资源
out.close();
in.close();
socket.close();
}
}
第三章 文件上传案例
3.1 文件上传案例
文件上传分析图解
- 【客户端】输入流,创建一个输入流关联源文件。(读取本地上传的文件数据)
- 【客户端】输出流,数据在客户端内存,将数据写到客户端和服务器的流中。
- 【服务器】输入流,获取一个输入流把流中数据读到服务器的内存中。
- 【服务器】输出流,创建一个输出流关联目的地(服务器硬盘),将数据写到目的地文件。
- 【服务器】输出流,获取一个输出流,回写数据。
- 【客户端】输入流,获取一个输入流,读取数据。
3.2 文件上传客户端实现
package com.itheima.upload;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
上传文件操作时 客户端
客户端需要先读取本地硬盘的资源
把资源数据写到 与服务器端之间流中
*/
public class Client {
public static void main(String[] args) throws IOException {
//1:创建Socket对象 关联上传服务器
Socket socket = new Socket("127.0.0.1", 9527);
//2: 创建 字节输入流 关联源文件
FileInputStream fis = new FileInputStream("E:\\385\\songxiaobao.jpg");
//3: 获取输出流 目的写数据到服务器端
OutputStream out = socket.getOutputStream();
//4: 读取源文件中的数据到 客户端内存
// 从客户端内存把数据 传递给服务器
byte[] buffer = new byte[8*1024];
int len;//用于接收每次读取的有效字节个数
while((len=fis.read(buffer))!=-1){
//读取到的真实字节数 len buffer 0 len
// 把有效数据写到 输出流中
out.write(buffer,0,len);
}
System.out.println("客户端已经把文件数据全部写到服务器流中了");
//告知服务器 客户端 不再写了 就相当于 写完了
// 流到末尾了
socket.shutdownOutput();// 关闭输出操作 客户端再也写不了
//接收数据进行解析
InputStream in = socket.getInputStream();
byte[] back = new byte[20];
int len1 = in.read(back);
System.out.println("服务器:"+new String(back,0,len1));
//释放资源
in.close();
out.close();
fis.close();
socket.close();
}
}
3.3 文件上传单线程服务器实现
package com.itheima.upload;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class UploadServer {
public static void main(String[] args) throws IOException {
//1:创建服务器对象 等待客户端连接
ServerSocket servert = new ServerSocket(9527);
System.out.println("服务器开启 等待客户端连接。。。");
Socket socket = servert.accept();//这个socket代表的是 连接的客户端。
String ip = socket.getInetAddress().getHostAddress();
System.out.println("上传文件的ip是:"+ip);
System.out.println("服务器开始接收 客户端传递来的数据.");
// 2:获取输入流 客户端把数据写到流中 从流中读
InputStream in = socket.getInputStream();
//3: 读取到数据要给服务器 硬盘上存储起来
// 创建字节输出流 关联服务器上硬盘位置
FileOutputStream fos = new FileOutputStream("F:\\upload\\"+ip+System.currentTimeMillis()+".jpg");
//4边读边写
//从流中读 写到服务器硬盘
byte[] buffer = new byte[8*1024];
int len;//接收每次读取的字节个数
while((len=in.read(buffer))!=-1){//判断是不是-1 -1代表读取完毕
//读到的字节数 在 buffer 0 len
// 写到目的地
fos.write(buffer,0,len);
}
System.out.println("文件上传成功!!把这个成功的喜悦 分享给客户端");
// ==========给客户端说 上传成功=====================
// 5: 获取输出流
OutputStream out = socket.getOutputStream();
out.write("文件上传成功".getBytes());
out.close();
fos.close();
in.close();
socket.close();
// servert.close();
}
}
3.4 文件上传多线程服务器实现
文件上传的案例中,服务器只能为客户端服务器一次,之后服务器端程序就会结束。而我们必须做到让服务器程序不能结束,时时刻刻都要为客户端服务。而且同时可以为多个客户端提供服务器,做到一个客户端就要开启一个信新的线程。
package com.itheima.upload2;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class UploadServer {
public static void main(String[] args) throws IOException {
//1:创建服务器对象 等待客户端连接
ServerSocket servert = new ServerSocket(9527);
System.out.println("服务器开启 等待客户端连接。。。");
while(true){//服务器一直开启
Socket socket = servert.accept();//这个socket代表的是 连接的客户端。
//是不是一个客户端 就是一个 执行路径!!一个执行路径 就是 一个线程
//连一个用户 开启一个线程
new Thread(()->{
//lambda 代表run方法 线程任务
try{
String ip = socket.getInetAddress().getHostAddress();
System.out.println("上传文件的ip是:"+ip);
System.out.println("服务器开始接收 客户端传递来的数据.");
// 2:获取输入流 客户端把数据写到流中 从流中读
InputStream in = socket.getInputStream();
//3: 读取到数据要给服务器 硬盘上存储起来
// 创建字节输出流 关联服务器上硬盘位置
FileOutputStream fos = new FileOutputStream("F:\\upload\\"+ip+System.currentTimeMillis()+".jpg");
//4边读边写
//从流中读 写到服务器硬盘
byte[] buffer = new byte[8*1024];
int len;//接收每次读取的字节个数
while((len=in.read(buffer))!=-1){//判断是不是-1 -1代表读取完毕
//读到的字节数 在 buffer 0 len
// 写到目的地
fos.write(buffer,0,len);
}
System.out.println("文件上传成功!!把这个成功的喜悦 分享给客户端");
// ==========给客户端说 上传成功=====================
// 5: 获取输出流
OutputStream out = socket.getOutputStream();
out.write("文件上传成功".getBytes());
out.close();
fos.close();
in.close();
socket.close();
}catch (Exception e){
e.printStackTrace();
}
}).start();
}
// servert.close();
}
}
3.5 文件上传服务器实现优化---使用线程池(了解一下)
频繁的创建线程会增加系统资源的开销,可以利用线程池进行再次优化。
public static void main(String[] args) throws IOException{
System.out.println("服务器 启动..... ");
ServerSocket serverSocket = new ServerSocket(6666);
//创建10个线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
while (true) {
Socket accept = serverSocket.accept();
//提交线程执行的任务
executorService.submit(()->{
try{
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
BufferedOutputStream bos = new BufferedOutputStream(fis);
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
}
System.out.println("back ........");
OutputStream out = accept.getOutputStream();
out.write("上传成功".getBytes());
out.close();
bos.close();
bis.close();
accept.close();
System.out.println("文件上传已保存");
} catch (IOException e) {
e.printStackTrace();
}
});
}
}