目标:文件断点上传,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目录
以上,测试完成。