JavaEE學習日誌持續更新----> 必看!JavaEE學習路線(文章總彙)
文件上傳
原理
文件上傳原理:
- 客戶端使用
本地
字節輸入流,讀取本地文件 - 客戶端使用網絡字節輸出流,把讀取到的圖片上傳到服務器
- 服務器使用網絡字節輸入流,讀取客戶端上傳的文件
- 服務器使用
本地
字節輸出流,把客戶端上傳的文件保存到服務器的硬盤上 - 服務器使用網絡字節輸出流,給客戶端回寫上傳成功
- 客戶端使用網絡字節輸入流,讀取服務器回寫的上傳成功
注意:
- 客戶端/服務器和本地的文件進行讀寫,必須使用自己創建的流
- 客戶端和服務器之間進行交互,必須使用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
*/