《计算机网络·自顶向下方法》 第二章 套接字编程作业 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