大文件断点上传服务开发--Java服务端Python客户端

目标:文件断点上传,Java编写服务端,Python编写客户端,以容器方式部署

环境:IntelliJ IDEA / Linux (python)

步骤:概述->文件断点上传服务端设计与实现->文件断点上传客户端设计与实现->kubernetes部署服务->运行测试

1.概述

文件断点上传服务的主要功能为实现应用的文件中转,并在大文件传输中断后可以进行断点上传。

文件服务端以容器方式部署,通过NodePort方式对外开放socket服务。

2.文件断点上传服务端设计与实现

文件断点上传服务端设计:

(1)数据通信方式

socket

(2)断点上传方式

设置断点游标,从游标处开始接收数据


服务端使用Java编写,新建maven项目fileuploadserver

修改pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>com.boe.cloud</groupId>
    <artifactId>fileupload-server</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <dependencies>
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.4</version>
            <classifier>jdk15</classifier>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.6</version>
                <configuration>                 
                    <archive>
                        <manifest>
                            <mainClass>com.boe.cloud.fileupload.FileUploadServer</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>                      
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
编写文件信息包,为将信息包转换成json格式提供便利

FileInfoPackage.java


public class FileInfoPackage {
 
    // JSON数据包信息
    int status = 0;
    String name = "";
    Long length = 0l;
 
    // Getter 与 Setter方法
    public int getStatus() {
        return status;
    }
 
    public void setStatus(int status) {
        this.status = status;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Long getLength() {
        return length;
    }
 
    public void setLength(Long length) {
        this.length = length;
    }
}
服务端主函数:FileUploadServer.java
import net.sf.json.JSONObject;
 
import java.io.*;
import java.math.RoundingMode;
import java.net.*;
import java.text.DecimalFormat;
import java.util.*;
 
public class FileUploadServer extends ServerSocket {
    // 退出标识符
    private boolean quit = false;
 
    // 文件大小计算
    private static DecimalFormat df = null;
 
    // 设置数字格式,保留一位有效小数
    static {
        df = new DecimalFormat("#0.0");
        df.setRoundingMode(RoundingMode.HALF_UP);
        df.setMinimumFractionDigits(1);
        df.setMaximumFractionDigits(1);
    }
 
    /**
     * 服务端启动端口参数传入
     *
     */
    public FileUploadServer(int port) throws IOException {
        super(port);
    }
 
    /**
     * 启动阻塞式服务端,接收socket并开启线程
     *
     */
    public void load() throws Exception {
        System.out.println("文件断点上传服务器正在运行......");
        while (!quit) {
            Socket socket = this.accept();
            System.out.println("与客户端的连接已建立!");
 
            // 每接收到一个Socket就建立一个新的线程来处理
            new Thread(new Task(socket)).start();
        }
    }
 
    /**
     * 线程处理任务:文件断点上传
     *
     */
    class Task implements Runnable{
 
        // 当前连接
        private Socket sk;
 
        // 初始化
        public Task(Socket socket) {
            this.sk = socket;
        }
 
        // 核心方法
        public void run(){
 
            Socket socket = sk;
 
            //输出与发送流定义
            InputStreamReader isr = null;
            DataInputStream dis = null;
            BufferedReader br = null;
            OutputStreamWriter osm = null;
            BufferedWriter bw = null;
            FileOutputStream fos = null;
            RandomAccessFile raf = null;
 
            //文件信息
            int fileExist = 0;
            String fileName = "";
            Long fileLength = 0l;
            Long clientFilelength = 0l;
            String filePath = "/data";
            String filePathName = "";
            String fileCopydest = "/mnt";
            String fileCopyPathName = "";
            char pathChar = File.separatorChar;
 
            try {
                // 输入对象初始化
                isr = new InputStreamReader(socket.getInputStream());
                br = new BufferedReader(isr);
 
                // 获取客户端第一次传入的信息:文件名称、大小、存在标识符
                String strTemp = br.readLine();
                // JSON格式转换
                JSONObject object = JSONObject.fromObject(strTemp);
//                System.out.println("Status: " + object.getBoolean("status"));
//                System.out.println("File Name: "+ object.getString("name"));
//                System.out.println("File Length: " + object.getLong("length"));
 
                // 获取文件名,本地查询
                fileName = object.getString("name");
                clientFilelength = object.getLong("length");
                System.out.println("服务器获取客户文件名称:" + fileName);
                System.out.println("客户端文件大小:" + clientFilelength);
 
                // 检查目录是否存在
                File directory = new File(filePath + pathChar + "receive" +pathChar);
                if(!(directory.exists())){
                    directory.mkdirs();
                }
                filePathName = filePath + pathChar + "receive" + pathChar + fileName;
                fileCopyPathName = fileCopydest + pathChar + fileName;
                File file = new File(filePathName);
                FileInfoPackage fileInfo = new FileInfoPackage();
 
                // 本地查询文件,判断是否存在
                if(file.exists()){
                    fileExist = 1;
                    fileLength = file.length();
                }else {
                    fileExist = 0;
                }
                // 数据包准备
                fileInfo.setStatus(fileExist);
                fileInfo.setName(fileName);
                fileInfo.setLength(fileLength);
                // 转换为JSON格式
                JSONObject sendObject = JSONObject.fromObject(fileInfo);
 
                // 输出对象初始化
                osm = new OutputStreamWriter(socket.getOutputStream());
                bw = new BufferedWriter(osm);
                // 发送对象
                bw.write(sendObject.toString());
                //bw.write("test for python");
                bw.flush();
 
                System.out.println("文件参数发送成功!");
//                System.out.println("Status: " + sendObject.getInt("status"));
//                System.out.println("File Name: " + sendObject.getString("name"));
//                System.out.println("File Length: " + sendObject.getLong("length"));
 
                System.out.println("服务器返回信息成功,准备传输数据......");
 
                // 开始传输文件
                //isr = new InputStreamReader(socket.getInputStream());
                dis = new DataInputStream(socket.getInputStream());
                // 传输参数
                int length = 0;
                byte[] bytes = new byte[1024];
                if(fileExist == 0){
                    fos = new FileOutputStream(file);
                    while((length = dis.read(bytes, 0, bytes.length)) != -1){
                        fos.write(bytes, 0, length);
                        fos.flush();
                        fileLength += length;
                        if(fileLength == clientFilelength){
                            break;
                        }
                    }
                    System.out.println("初次文件传输完成!");
                }else if (fileExist == 1){
                    Long filePoint = 0l;
                    fileLength = file.length();
                    if(clientFilelength >= fileLength){
                        // 文件断点续传
                        filePoint = fileLength;
                    }else{
                        // 出错,重新传输
                        filePoint = 0l;
                        file.delete();
                        file.createNewFile();
                    }
                    raf = new RandomAccessFile(file,"rw");
                    raf.seek(filePoint);
                    while((length = dis.read(bytes, 0, bytes.length)) != -1){
                        raf.write(bytes, 0, length);
                        fileLength += length;
                        if(fileLength == clientFilelength){
                            break;
                        }
                    }
                    System.out.println("断点文件续传成功!");
                }else{
                    System.out.println("状态错误!");
                }
                // 文件传输完成
                System.out.println("-------- 文件接收成功 [File Name:" + fileName + "] [Size:" + getFormatFileSize(file.length()) + "] --------");
                // 文件传输完成,拷贝并删除文件
                if (file.length() == clientFilelength) {
                    File dest = new File(fileCopyPathName);
                    copyFileUsingFileStreams(file, dest);
                    System.out.println("文件拷贝成功!");
                    if (fos != null){
                        fos.close();
                    }
                    if (raf != null){
                        raf.close();
                    }
                    deleteFile(file);
                    System.out.println("文件删除成功!");
                }
 
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                try {
                    if (dis != null){
                        dis.close();
                    }
                    if (isr != null){
                        isr.close();
                    }
                    if (osm != null){
                        osm.close();
                    }
                    socket.close();
                    System.out.println("Socket已关闭!");
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
 
    }
 
    /**
     * 拷贝文件
     *
     */
    public static void copyFileUsingFileStreams(File source, File dest) throws IOException {
        InputStream input = null;
        OutputStream output = null;
        try {
            input = new FileInputStream(source);
            output = new FileOutputStream(dest);
            byte[] buf = new byte[1024];
            int bytesRead;
            while ((bytesRead = input.read(buf)) > 0) {
                output.write(buf, 0, bytesRead);
            }
        } catch (Exception e){
            e.printStackTrace();
        }
        finally {
            input.close();
            output.close();
        }
    }
 
    /**
     * 删除文件
     *
     */
    public static void deleteFile(File file) throws IOException {
        try {
            file.delete();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 格式化文件大小
     *
     */
    public String getFormatFileSize(long length) {
        double size = ((double) length) / (1 << 30);
        if (size >= 1) {
            return df.format(size) + "GB";
        }
        size = ((double) length) / (1 << 20);
        if (size >= 1) {
            return df.format(size) + "MB";
        }
        size = ((double) length) / (1 << 10);
        if (size >= 1) {
            return df.format(size) + "KB";
        }
        return length + "B";
    }
 
    public static void main(String[] args) {
        try {
            FileUploadServer fileUploadServer = new FileUploadServer(8899);
            fileUploadServer.load();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端为每一个socket连接新建一个线程,结束后关闭socket,循环等待连接

3.文件断点上传客户端设计与实现

文件上传客户端设计:

(1)数据通信方式

socket

(2)断点上传方式

读取服务端返回的文件断点游标,实现断点续传


客户端使用Python编写,引入json包,将读取标识符转换为json格式,方便使用。

FileUploadClient.py

# -*- coding: utf-8 -*
import socket
import os
import json
import sys
 
def load():
    sk = socket.socket()
    sk.connect(('10.80.25.143', 30089))
    print ('连接服务器成功')
    file_name = input('请输入需要上传文件的文件名:').strip()
    file_size = os.stat(file_name).st_size
    file_point = 0
    print (file_name)
    print (file_size)
    # 文件信息
    send_data = dict(status=0, name=file_name, length=file_size)
    send_json = json.dumps(send_data)
    print (send_json)
    # 发送需上传文件信息
    sk.send((send_json + '\n').encode())
    # 接收服务器返回信息
    recv_data = sk.recv(1024).decode()
    print (recv_data)
    recv_json = json.loads(recv_data)
    print (recv_json)
    # 判断是否断点续传
    if recv_json['status'] == 0:
        print ('文件初次传输!')
        file_point = 0
    elif recv_json['status'] == 1:
        print ('文件已存在,继续传输!')
        file_point = recv_json['length']
    else:
        print ('状态码无效!')
    # 打开文件并传输
    f = open(file_name, 'rb')
    f.seek(file_point)
    while file_point < file_size:
        data = f.read(1024)
        sk.sendall(data)
        file_point += len(data)
        # print ('已发送Byte:' + str(file_point) + '\t' + '文件总Byte:' + str(file_size))
        percent = int(100*(file_point/file_size))
        print ('已发送文件比例:' + str(percent) + '%')
    f.close()
    print ('发送成功!')
 
while True:
    load()

其中客户端也设置为循环输入。

4.kubernetes部署服务

将服务端代码打包成jar包,通过Dockerfile生成镜像,最后通过k8s yaml文件实现服务部署

服务端打包:

mvn compile

mvn package

Dockerfile:

#使用java 8版本基础镜像
FROM java:8
 
#将jar包拷贝至容器内
ADD FileUploadServer.jar app.jar
RUN bash -c 'touch /app.jar'
 
#容器开放端口
EXPOSE 8899
 
#容器启动时执行的命令
ENTRYPOINT ["java","-jar","/app.jar"]
部署文件:fileupload.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: fileupload-deployment
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: fileupload
    spec:
      containers:
      - name: fileupload-test
        image: caoxintest/fileuploadserver:1.0
        ports:
        - containerPort: 8899
        volumeMounts:
        - name: my-vol
          mountPath: /mnt
      volumes:
      - name: my-vol
        persistentVolumeClaim:
          claimName: mynfs-pvc       
 
---
apiVersion: v1
kind: Service
metadata:
  name: fileupload-service
  labels:
    app: fileupload
spec:
  selector:
    app: fileupload
  type: NodePort
  ports:
  - port: 8899
    nodePort: 30089

执行:

kubectl -f create fileupload.yaml

5.运行测试

python3 FileUploadClient.py

运行客户端:

输入test.rar,并在传输过程中强行终止


查看服务端:


继续上传直至结束:


服务端查看:自动拷贝至mnt目录


以上,测试完成。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值