作業1
概述:
如何使用JAVA創建HTTP報文,在本回答中沒有體現。
本回答的報文均由一個自定義類而實現。
代碼邏輯:
1.建立連接
2.客戶端發送請求的文件路徑到服務端
3.服務端尋找文件。如果找到,發送文件,並且發送一個確定報文。如果沒有找到,發送一個沒有找到的報文。
服務端
總所周知,服務端可能需要同時為多個客戶端提供服務,所以很自然的,博主使用了多線程技術。具體的,博主使用了線程池技術
同時,在服務端,對於同一類服務,線程池只需要一個就夠了,所以博主使用了單例模式來設計代碼。
在編寫代碼的過程中,我遇到了兩個問題:
第一個問題,TCP是面向字節流的運輸層協議,在發送一個文件后,再發送一個Object,如果之前不指定文件的長度,那么接收端根本無法確定,接收到的數據屬於文件,還是屬於后來發送的Object
第二個問題,發送Object的時候,一開始我使用了ObjectOutputStream 發送,但是很顯然地,接收端不能使用ObjectInputStream接收,因為在接收文件時,該Object的一部分字節可能已經被讀入到byte[]中了。所以客戶端必須使用socket的InputStream接收數據,再通過相關方法(詳見代碼)將byte轉化為Object。問題就出在這里,我嘗試了很久,但是一直在類型轉換的時候出現異常,直到我將發送的Object在服務端轉化為byte[],才發現得到的byte[]長度和客戶端收到的不一樣。為什么會這樣,我找了很久的答案,但是一無所獲,只能假設,在ObjectOutputStream基於Socket.OutputStream的時候,會做相關優化,減少字節長度。通過比對byte[]的內容大致可以猜測,基於Socket.OutputStream的ObjectOutputStream發送Object時,會忽略一部分Object的頭部信息。最后的解決方法是,不使用ObjectOutputStream直接發送,而是將其轉化為byte[]之后,再通過Socket.OutputStream發送。
客戶端
客戶端不用考慮多線程的問題
實現比服務端簡單得多,只需要根據服務端代碼接收相關信息即可
Datagram類
自定義類,同時存在於客戶端和服務端,用於客戶端和服務端接收信息,上文提到的Object實際上就是指Datagram的某個實例
Datagram類
package DatagramBean;
import java.io.Serializable;
import java.util.Objects;
/**
-
@Author : ZGQ
-
@Date : 2020/2/16 18:10
-
@Version : 1.0
*/
public class Datagram implements Serializable {
static final long serialVersionUID = 952752194657456L;
int code;
String message;
public Datagram() {
}
public Datagram(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Datagram datagram = (Datagram) o;
return code == datagram.code &&
Objects.equals(message, datagram.message);
}
@Override
public int hashCode() {
return Objects.hash(code, message);
}
@Override
public String toString() {
return "Datagram{" +
"code=" + code +
", message='" + message + ''' +
'}';
}
}
服務端代碼
package TCP; import DatagramBean.Datagram;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/**
- @Author : ZGQ
- @Date : 2020/2/16 18:03
- @Version : 1.0
*///多線程
class ServerUtil implements Runnable{private static final int MAX_BUFF_SIZE = 1024; private Socket socket = null; private InputStream is = null; private FileInputStream fis = null; private OutputStream os = null; private ObjectOutputStream oos=null; public ServerUtil(Socket socket){ this.socket=socket; } //接收客戶端的請求的文件路徑 private String receURL() throws IOException { byte[] data = new byte[MAX_BUFF_SIZE]; int len = is.read(data); String s1 = new String(data,0,len); return s1; } //發送文件 private void sendFile(String s1) throws IOException { fis = new FileInputStream(s1); byte[] data = new byte[MAX_BUFF_SIZE]; int len = 0; fis = new FileInputStream(s1); int times = 0; int lst = 0; while ((len=fis.read(data))!=-1){ lst=len; os.write(data,0,len); times++; } os.flush(); System.out.println("lst = " + lst); } //通過路徑獲取文件長度 private long getFileLenByPath(String path) throws IOException { File file = new File(path); return file.length(); } //發送獲取成功的回復 private void sendResSuccess(long len) throws IOException { sendRes(200,"len = "+len); } //發送獲取失敗的回復 private void sendResFailed() throws IOException { sendRes(404,"文件不存在"); } //發送回復 private void sendRes(int code,String message) throws IOException { Datagram datagram = new Datagram(code,message); oos.writeObject(datagram); oos.flush(); } //將Object轉化為字節 private byte[] ObjectToByte(Datagram datagram){ ByteArrayOutputStream baos = null; ObjectOutputStream oos = null; byte[] data = new byte[MAX_BUFF_SIZE]; try { baos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(baos); oos.writeObject(datagram); return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } finally { try { if(oos!=null){ oos.close(); } if(baos!=null){ baos.close(); } } catch (IOException e) { e.printStackTrace(); } } return new byte[0]; } //以字節的方式發送回復 private void sendResByte(int code,String message) throws IOException { Datagram datagram = new Datagram(code,message); byte[] bytes = ObjectToByte(datagram); os.write(bytes); System.out.println(bytes.length); } @Override public void run() { try { is = socket.getInputStream(); os = socket.getOutputStream(); oos = new ObjectOutputStream(os); String path = receURL(); System.out.println("path = " + path); long len = 0; try{ long fileLen = getFileLenByPath(path); System.out.println(fileLen); sendResSuccess(fileLen); sendFile(path); sendResByte(200,"東西收到了吧,哈哈"); }catch (IOException e){ sendResFailed(); System.out.println("沒有找到對應的資源"); } } catch (IOException e) { System.err.println("socket輸入輸出流創建失敗"); } finally { try { if(oos!=null)oos.close(); if(os!=null)os.close(); if(fis!=null)fis.close(); if(is!=null)is.close(); if(socket!=null)socket.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("OVERD\n"); }
}
public class Task1 {
//單例模式
static Task1 task=null;
ExecutorService threadPool = null;
private Task1(){
threadPool=Executors.newFixedThreadPool(50);
}
public static Task1 getInstance(){
if (task == null) {
synchronized (Task1.class) {
if (task == null) {
task = new Task1();
}
}
}
return task;
}//調用此函數開始服務 public void server() { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(9527); while (true){ threadPool.execute(new ServerUtil(serverSocket.accept())); } } catch (IOException e) { e.printStackTrace(); } finally { try { if(serverSocket!=null)serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } }
}
客戶端代碼
package TCP;
import DatagramBean.Datagram;
import com.sun.istack.internal.ByteArrayDataSource;
import com.sun.scenario.effect.Merge;
import java.io.*;
import java.net.Socket;
/**
* @Author : ZGQ
* @Date : 2020/2/16 19:01
* @Version : 1.0
*/
public class Task1 {
public static final int MAX_BUF_SIZE = 1024;
Socket socket = null;
OutputStream os = null;
InputStream is = null;
FileOutputStream fos = null;
ObjectInputStream ois = null;
//發送要訪問的服務端文件地址
private void sendURL() throws IOException {
os.write("D:\\Java\\server_source\\setu.png".getBytes());
os.flush();
}
//將byte[]轉化為Object
private Object byteToObject(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(byteArrayInputStream);
return ois.readObject();
}
//接收文件
//注意TCP面向字節流,收到的數據中結尾可能存在不屬於文件的部分
//此方法的返回值即為收到的多余數據,它屬於TCP傳送的下一個(或多個)對象
private byte[] receFile(String dest, int fileLen) throws IOException {
fos = new FileOutputStream(dest);
byte[] data = new byte[MAX_BUF_SIZE];
int receivedLen = 0;
while (receivedLen < fileLen) {
int len = is.read(data);
if (len == -1) {
return new byte[0];
}
int len_of_remain = Math.min(fileLen - receivedLen, MAX_BUF_SIZE);
fos.write(data, 0, len_of_remain);
receivedLen += len;
if (receivedLen == fileLen) {
System.out.println("沒有多余數據");
return new byte[0];
} else if (receivedLen > fileLen) {
return subByte(data, len_of_remain, len);
}
}
return new byte[0];
}
//接收服務端的回復,內容可能包括文件是否存在,文件長度等
private Datagram receRes() throws IOException, ClassNotFoundException {
return (Datagram) ois.readObject();
}
//通過服務端的回復,獲取文件長度
private int getLen(Datagram datagram) {
return Integer.parseInt(datagram.getMessage().substring(6));
}
//獲取一個byte[] 的一段
private byte[] subByte(byte[] data, int start, int totLen) {
byte[] newData = new byte[totLen - start];
int cnt = 0;
for (int i = start; i < totLen; i++) {
newData[cnt++] = data[i];
}
return newData;
}
//合並兩個byte[]
private byte[] byteMerge(byte[] bytes1, byte[] bytes2) {
int len = bytes1.length + bytes2.length;
byte[] newByte = new byte[len];
int cnt = 0;
for (byte b : bytes1) {
newByte[cnt++] = b;
}
for (byte b : bytes2) {
newByte[cnt++] = b;
}
return newByte;
}
//receFile()中,可能已經讀取了下一個對象的一部分,這里讀取剩下的所有部分
private byte[] receRemain() throws IOException {
byte[] data = new byte[MAX_BUF_SIZE];
byte[] ret = new byte[0];
int len = 0;
int totlen = 0;
while ((len = is.read(data)) != -1) {
ret = byteMerge(ret, subByte(data, 0, len));
totlen += len;
}
if (totlen >= 0) return ret;
else return new byte[0];
}
//客戶端程序
public void client() {
try {
socket = new Socket("127.0.0.1", 9527);
os = socket.getOutputStream();
is = socket.getInputStream();
ois = new ObjectInputStream(is);
sendURL();
Datagram datagram = receRes();
long fileLen = getLen(datagram);
if(fileLen==0){
System.out.println("文件缺失");
return;
}
System.out.println("fineLen = "+fileLen);
byte[] remain1 = receFile("D:\\Java\\client_workspace\\name.png", (int) fileLen);
byte[] remain2 = receRemain();
byte[] Obj = byteMerge(remain1,remain2);
datagram = (Datagram) byteToObject(Obj);
System.out.println(datagram);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (ois != null) ois.close();
if (fos != null) fos.close();
if (is != null) is.close();
if (os != null) os.close();
if (socket != null) socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}