JavaEE学习日志持续更新----> 必看!JavaEE学习路线(文章总汇)
Java学习日志(二十四)
文件上传
原理
文件上传原理:
- 客户端使用
本地
字节输入流,读取本地文件 - 客户端使用网络字节输出流,把读取到的图片上传到服务器
- 服务器使用网络字节输入流,读取客户端上传的文件
- 服务器使用
本地
字节输出流,把客户端上传的文件保存到服务器的硬盘上 - 服务器使用网络字节输出流,给客户端回写上传成功
- 客户端使用网络字节输入流,读取服务器回写的上传成功
注意:
- 客户端/服务器和本地的文件进行读写,必须使用自己创建的流
- 客户端和服务器之间进行交互,必须使用Socket提供的网络流
文件上传的原理:
文件复制,客户端本地-->复制-->服务器-->复制-->服务器硬盘
明确:
文件上传的客户端
文件上传的客户端:读取本地的文件,上传到服务器;读取服务器回写的数据
数据源:D:\1.jpg
目的地:服务器
实现步骤:
- 创建本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
- 创建客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
- 使用Socket对象中的方法getOutputStream,获取网络字节输出流OutputStream对象
- 使用本地字节输入流FileInputStream对象中的方法read,读取本地要上传的文件
- 使用网络字节输出流OutputStream对象中的方法write,把读取的文件写到服务器
- 使用Socket对象中的方法getInputStream,获取网络字节输入流InputStream对象
- 使用网络字节输入流InputStream对象的方法read,读取服务器回写的数据
- 释放资源
代码示例:客户端
public class TCPClient {
public static void main(String[] args) throws IOException {
//1.创建本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("D:\\1.jpg");
//2.创建客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1", 9999);
//3.使用Socket对象中的方法getOutputStream,获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
//4.使用本地字节输入流FileInputStream对象中的方法read,读取本地要上传的文件
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
//5.使用网络字节输出流OutputStream对象中的方法write,把读取的文件写到服务器
os.write(bytes, 0, len);
}
//6.使用Socket对象中的方法getInputStream,获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//7.使用网络字节输入流InputStream对象的方法read,读取服务器回写的数据
while ((len = is.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, len));
}
//8.释放资源
fis.close();
socket.close();
}
}
文件上传的服务器端
文件上传的服务器端:读取客户端上传的文件,保存服务器的硬盘,给客户端回写“上传成功”
数据源:客户端上传的文件
目的地:服务器的硬盘 D:\upload\1.jpg
实现步骤:
- 判断D盘是否有upload文件夹,没有则创建
- 创建服务器ServerSocket对象,构造方法和系统要指定的端口号
- 使用ServerSocket对象accept获取到请求的客户端Socket对象
- 使用Socket对象中的方法getInputStream,获取网络字节输入流InputStream对象
- 创建本地字节输出流FileOutputStream对象,构造方法绑定输出的目的地
- 使用网络字节输入流InputStream对象中的方法read读取客户端上传的文件
- 使用本地字节输出流FileOutputStream对象中的方法write,把客户端上传的文件保存到服务器的硬盘上
- 使用Socket对象中的方法getOutputStream,获取网络字节输出流OutputStream对象
- 使用write方法,给客户端回写“上传成功”
- 释放资源
代码示例:
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.判断D盘是否有upload文件夹,没有则创建
File file = new File("D:\\upload");
if(!file.exists()){
file.mkdirs();
}
//2.创建服务器ServerSocket对象,构造方法和系统要指定的端口号
ServerSocket server = new ServerSocket(9999);
//3.使用ServerSocket对象accept获取到请求的客户端Socket对象
Socket socket = server.accept();
//4.使用Socket对象中的方法getInputStream,获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//5.创建本地字节输出流FileOutputStream对象,构造方法绑定输出的目的地
FileOutputStream fos = new FileOutputStream(file+"\\1.jpg");
//6.使用网络字节输入流InputStream对象中的方法read读取客户端上传的文件
byte[] bytes = new byte[1024];
int len = 0;
while((len = is.read(bytes))!=-1){
//7.使用本地字节输出流FileOutputStream对象中的方法write,把客户端上传的文件保存到服务器的硬盘上
fos.write(bytes,0,len);
}
//8.使用Socket对象中的方法getOutputStream,获取网络字节输出流OutputStream对象
//9.使用write方法,给客户端回写“上传成功”
socket.getOutputStream().write("上传成功".getBytes());
//10.释放资源
fos.close();
socket.close();
server.close();
}
}
文件上传的阻塞问题
原因:
修改客户端程序:在文件上传完成之后,给服务器添加一个结束标记
使用Socket类中的方法:
void shutdownOutput()
禁用此套接字的输出流。对于TCP套接字,将发送任何先前写入的数据,然后发送TCP的正常连接终止序列。(结束标记)
代码示例:修改后的客户端程序
public class TCPClient {
public static void main(String[] args) throws IOException {
//1.创建本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("D:\\1.jpg");
//2.创建客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1", 9999);
//3.使用Socket对象中的方法getOutputStream,获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
//4.使用本地字节输入流FileInputStream对象中的方法read,读取本地要上传的文件
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
//5.使用网络字节输出流OutputStream对象中的方法write,把读取的文件写到服务器
os.write(bytes, 0, len);
}
/*
上传文件完成之后,给服务器写一个结束标记
void shutdownOutput() 禁用此套接字的输出流。
对于TCP套接字,将发送任何先前写入的数据,然后发送TCP的正常连接终止序列。(结束标记)
*/
socket.shutdownOutput();
//6.使用Socket对象中的方法getInputStream,获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//7.使用网络字节输入流InputStream对象的方法read,读取服务器回写的数据
while ((len = is.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, len));
}
//8.释放资源
fis.close();
socket.close();
}
}
文件上传优化–>自定义文件名
修改服务器端,让上传的文件按一定规则命名
代码示例:服务器端
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.判断D盘是否有upload文件夹,没有则创建
File file = new File("D:\\upload");
if(!file.exists()){
file.mkdirs();
}
//2.创建服务器ServerSocket对象,构造方法和系统要指定的端口号
ServerSocket server = new ServerSocket(9999);
//3.使用ServerSocket对象accept获取到请求的客户端Socket对象
Socket socket = server.accept();
//4.使用Socket对象中的方法getInputStream,获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
/*
自定义一个上传文件的名称
规则:域名+毫秒值+随机数
*/
String fileName = "itcast"+ System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
//5.创建本地字节输出流FileOutputStream对象,构造方法绑定输出的目的地
FileOutputStream fos = new FileOutputStream(file+"\\"+fileName);
//6.使用网络字节输入流InputStream对象中的方法read读取客户端上传的文件
byte[] bytes = new byte[1024];
int len = 0;
while((len = is.read(bytes))!=-1){
//7.使用本地字节输出流FileOutputStream对象中的方法write,把客户端上传的文件保存到服务器的硬盘上
fos.write(bytes,0,len);
}
//8.使用Socket对象中的方法getOutputStream,获取网络字节输出流OutputStream对象
//9.使用write方法,给客户端回写“上传成功”
socket.getOutputStream().write("上传成功".getBytes());
//10.释放资源
fos.close();
socket.close();
server.close();
}
}
文件上传的多线程优化
由于效率太慢,使用多线程优化
代码示例:客户端
public class TCPClient {
public static void main(String[] args) throws IOException {
//1.创建本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("D:\\1.jpg");
//2.创建客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1", 9999);
//3.使用Socket对象中的方法getOutputStream,获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
//4.使用本地字节输入流FileInputStream对象中的方法read,读取本地要上传的文件
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
//5.使用网络字节输出流OutputStream对象中的方法write,把读取的文件写到服务器
os.write(bytes, 0, len);
}
/*
上传文件完成之后,给服务器写一个结束标记
void shutdownOutput() 禁用此套接字的输出流。
对于TCP套接字,将发送任何先前写入的数据,然后发送TCP的正常连接终止序列。(结束标记)
*/
socket.shutdownOutput();
//6.使用Socket对象中的方法getInputStream,获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//7.使用网络字节输入流InputStream对象的方法read,读取服务器回写的数据
while ((len = is.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, len));
}
//8.释放资源
fis.close();
socket.close();
}
}
服务器端
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.判断D盘是否有upload文件夹,没有则创建
File file = new File("D:\\upload");
if(!file.exists()){
file.mkdirs();
}
//2.创建服务器ServerSocket对象,构造方法和系统要指定的端口号
ServerSocket server = new ServerSocket(9999);
/*
增加死循环,让accept方法一直监听客户端
有一个客户端上传,完成文件保存
*/
while(true){
//3.使用ServerSocket对象accept获取到请求的客户端Socket对象
Socket socket = server.accept();
/*
为了提高效率,增加多线程技术
获取一个客户端,开启一个线程,完成文件上传
*/
new Thread(new Runnable() {
@Override
public void run() {
try{
//4.使用Socket对象中的方法getInputStream,获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
/*
自定义一个上传文件的名称
规则:域名+毫秒值+随机数
*/
String fileName = "itcast"+ System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
//5.创建本地字节输出流FileOutputStream对象,构造方法绑定输出的目的地
FileOutputStream fos = new FileOutputStream(file+"\\"+fileName);
//6.使用网络字节输入流InputStream对象中的方法read读取客户端上传的文件
byte[] bytes = new byte[1024];
int len = 0;
while((len = is.read(bytes))!=-1){
//7.使用本地字节输出流FileOutputStream对象中的方法write,把客户端上传的文件保存到服务器的硬盘上
fos.write(bytes,0,len);
}
//8.使用Socket对象中的方法getOutputStream,获取网络字节输出流OutputStream对象
//9.使用write方法,给客户端回写“上传成功”
socket.getOutputStream().write("上传成功".getBytes());
//10.释放资源
fos.close();
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
}
//server.close();
}
}
BS版本TCP程序的代码实现(最终版本)
浏览器访问客户端的原理:
注意:浏览器页面的每张图片,都需要请求一个服务器端来获取图片,所以需要使用while循环来持续监听浏览器的请求信息,并使用多线程技术进行优化
代码示例:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
创建TCP程序BS版本的服务器
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//创建服务器对象ServerSocket,和系统要指定的端口号
ServerSocket server = new ServerSocket(8080);
/*
服务器给客户端回写的html页面中,如果包含了图片,
那么客户端就会获取到html页面中链接的图片地址
根据图片地址,再请求服务器,让服务器再读取图片,回写到客户端
所以需要服务器一直监听客户端,可以使用死循环
*/
while (true){
//使用ServerSocket对象中的方法accept,监听并获取请求的客户端对象socket(浏览器)
Socket socket = server.accept();
//为了提高浏览器显示图片的效率,可以使用多线程技术,浏览器请求一次,开启一个线程回写一个文件
new Thread(new Runnable() {
@Override
public void run() {
try {
//使用Socket对象中的方法getInputStream,获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//使用网络字节输入流InputStream对象中的方法read,读取客户端的请求信息
/*byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes, 0, len));*/
/*
服务器要做的事情:
1.获取到客户端请求html页面的地址
2.使用本地字节输入流读取这个html页面
3.把页面回写到客户端(浏览器)上显示
*/
//把网络字节输入流,转化为网络字符缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//读取客户端请求信息的第一行,包含了页面的地址
String line = br.readLine();
//System.out.println(line);//GET /day12/web/index.html HTTP/1.1
//切割字符串,只要中间部分
String[] arr = line.split(" ");
//System.out.println(arr[1]);///day12/web/index.html
//对字符串进行截取,不要第一个/
String path = arr[1].substring(1);
System.out.println(path);//day12/web/index.html,就是html页面的相对路径
//创建本地的字节输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
//获取网络字节输出流,可以转换为字节缓冲流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
/*
增加以下三行代码告知客户端,写的是html网页
客户端就会以网页的形式打开文件显示
*/
bos.write("HTTP/1.1 200 OK\r\n".getBytes());
bos.write("Content-Type:text/html\r\n".getBytes());
//必须写入空行,否则浏览器不解析
bos.write("\r\n".getBytes());
//使用BufferedOutputStream对象中的方法read,读取html文件
int len = 0;
while ((len = bis.read()) != -1) {
//使用BufferedOutputStream中的方法write,把读取到的html文件写到客户端显示
bos.write(len);
}
//释放资源
bos.close();
bis.close();
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
}
//server.close();
}
}
/*
day12/web/index.html
day12/web/img/header.jpg
day12/web/img/big01.jpg
day12/web/img/small03.jpg
day12/web/img/footer.jpg
day12/web/img/title2.jpg
day12/web/img/ad.jpg
day12/web/img/middle01.jpg
day12/web/img/1.jpg
day12/web/img/logo2.png
*/