《計算機網絡·自頂向下方法》 第二章 套接字編程作業 JAVA


作業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();
            }
        }
    }
}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM