Java开发笔记(一百一十五)使用Socket开展文件传输

时间:2023-02-01 11:13:06

前面介绍了怎样通过Socket在客户端与服务端之间传输文本,当然Socket也支持在客户端与服务端之间传输文件,因为文件本身就是通过I/O流实现读写操作的,所以在套接字的输入输出流中传输文件真是再合适不过了。只是套接字属于长连接,倘若Socket一直不关闭,连接将总是处于就绪状态,也就无法判断文件数据是否已经传输完成。为了检验文件传输的结束时刻,可以考虑实时下列的两种技术方案之一:
1、客户端每次连上Socket之后,只发送一个文件的数据,且发送完毕的同时立即关闭套接字,从而告知服务端已经成功发送文件,不必继续保留这个Socket。
2、客户端的Socket连上了服务端,仍然像文本传输那样保持长连接,但是另外定义文件传输的专用数据格式,比如每次传输操作都由开始指令、文件数据、结束指令这些要素组成。然后客户端按照该格式发送文件,服务端也按照该格式接收文件,由于传输操作包含开始指令和结束指令,所以即使客户端不断开连接,服务端也能凭借开始指令和结束指令来分清文件数组的开头和结尾。
考虑到编码的复杂度,这里采取前一种方案,即每次Socket连接只发送一个文件。据此编写的文件发送任务框架类似于文本发送任务,差别在于待发送的数据来自于本地文件,详细的客户端主要代码示例如下:

//定义一个文件发送任务
public class SendFile implements Runnable {
// 以下为Socket服务器的IP和端口,根据实际情况修改
private static final String SOCKET_IP = "192.168.1.8";
private static final int FILE_PORT = 52000; // 文件传输专用端口
private String mFilePath; // 待发送的文件路径 public SendFile(String filePath) {
mFilePath = filePath;
} @Override
public void run() {
PrintUtils.print("向服务器发送文件:" + mFilePath);
// 创建一个套接字对象。同时根据指定路径构建文件输入流对象
try (Socket socket = new Socket();
FileInputStream fis = new FileInputStream(mFilePath)) {
// 命令套接字连接指定地址的指定端口,超时时间为3秒
socket.connect(new InetSocketAddress(SOCKET_IP, FILE_PORT), 3000);
// 获取套接字对象的输出流
OutputStream writer = socket.getOutputStream();
long totalLength = fis.available(); // 文件的总长度
int tempLength = 0; // 每次发送的数据长度
double sendedLength = 0; // 已发送的数据长度
byte[] data = new byte[1024 * 8]; // 每次发送数据的字节数组
// 以下从文件中循环读取数据
while ((tempLength = fis.read(data, 0, data.length)) > 0) {
writer.write(data, 0, tempLength); // 往Socket连接中写入数据
sendedLength += tempLength; // 累加已发送的数据长度
// 计算已发送数据的百分比,并打印当前的传输进度
String ratio = "" + (sendedLength / totalLength * 100);
PrintUtils.print("已传输:" + ratio.substring(0, 4) + "%");
}
PrintUtils.print(mFilePath+" 文件发送完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
}

至于服务端的文件接收任务,依然为每个连上的客户端分配子线程,并把接收到的数据保存为文件形式,详细的服务端主要代码示例如下:

//定义一个文件接收任务
public class ReceiveFile implements Runnable {
private static final int FILE_PORT = 52000; // 文件传输专用端口 @Override
public void run() {
PrintUtils.print("接收文件的Socket服务已启动");
try {
// 创建一个服务端套接字,用于监听客户端Socket的连接请求
ServerSocket server = new ServerSocket(FILE_PORT);
while (true) { // 持续侦听客户端的连接
// 收到了某个客户端的Socket连接请求,并获得该客户端的套接字对象
Socket socket = server.accept();
// 启动一个服务线程负责与该客户端的交互操作
new Thread(new ServerTask(socket)).start();
}
} catch (Exception e) {
e.printStackTrace();
}
} // 定义一个伺候任务,好生招待这位顾客
private class ServerTask implements Runnable {
private Socket mSocket; // 声明一个套接字对象 public ServerTask(Socket socket) throws IOException {
mSocket = socket;
} @Override
public void run() {
PrintUtils.print("开始接收文件");
int random = new Random().nextInt(1000); // 生成随机数
String file_path = "D:/" + random + ".jpg"; // 本地临时保存的文件
// 根据指定的临时路径构建文件输出流对象
try (FileOutputStream fos = new FileOutputStream(file_path)) {
// 获取套接字对象的输入流
InputStream reader = mSocket.getInputStream();
int tempLength = 0; // 每次接收的数据长度
byte[] data = new byte[1024 * 8]; // 每次接收数据的字节数组
// 以下从Socket连接中循环接收数据
while ((tempLength = reader.read(data, 0, data.length)) > 0) {
fos.write(data, 0, tempLength); // 把接收到的数据写入文件
}
// 注意客户端的Socket要先调用close方法,服务端才会退出上面的循环
mSocket.close(); // 关闭套接字连接
PrintUtils.print(file_path+" 文件接收完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

接着服务端程序开启Socket专用的文件接收线程,线程启动代码如下所示:

		// 启动一个文件接收线程
new Thread(new ReceiveFile()).start();

然后客户端程序启动多个文件发送任务,并且每个任务都使用单独的分线程来执行,于是文件发送代码如下所示:

	// 发送本地文件
private static void testSendFile() {
// 为文件发送任务开启分线程
new Thread(new SendFile("E:/bliss.jpg")).start();
// 为文件发送任务开启分线程
new Thread(new SendFile("E:/qq_qrcode.png")).start();
}

最后完整走一遍流程,先运行服务端的测试程序,再运行客户端的测试程序,观察到的客户端日志如下:

12:42:08.258 Thread-1 向服务器发送文件:E:/qq_qrcode.png
12:42:08.258 Thread-0 向服务器发送文件:E:/bliss.jpg
12:42:08.351 Thread-1 E:/qq_qrcode.png已传输:47.6%
12:42:08.352 Thread-1 E:/qq_qrcode.png已传输:95.2%
12:42:08.354 Thread-0 E:/bliss.jpg已传输:0.41%
12:42:08.355 Thread-0 E:/bliss.jpg已传输:0.83%
12:42:08.356 Thread-0 E:/bliss.jpg已传输:1.25%
12:42:08.357 Thread-0 E:/bliss.jpg已传输:1.67%
12:42:08.354 Thread-1 E:/qq_qrcode.png已传输:100.%
12:42:08.358 Thread-1 E:/qq_qrcode.png 文件发送完毕
12:42:08.365 Thread-0 E:/bliss.jpg已传输:2.09%
12:42:08.366 Thread-0 E:/bliss.jpg已传输:2.50%
…………这里省略中间的传输进度…………
12:42:08.461 Thread-0 E:/bliss.jpg已传输:99.9%
12:42:08.462 Thread-0 E:/bliss.jpg已传输:100.%
12:42:08.462 Thread-0 E:/bliss.jpg 文件发送完毕

同时观察到下面的服务端日志:

12:41:56.718 Thread-0 接收文件的Socket服务已启动
12:42:08.295 Thread-1 开始接收文件
12:42:08.305 Thread-2 开始接收文件
12:42:08.362 Thread-2 D:/265.jpg 文件接收完毕
12:42:08.462 Thread-1 D:/34.jpg 文件接收完毕

根据以上的客户端日志以及服务端日志,可知通过Socket成功实现了文件传输功能。


更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(一百一十五)使用Socket开展文件传输的更多相关文章

  1. Java开发笔记(六十五)集合:HashSet和TreeSet

    对于相同类型的一组数据,虽然Java已经提供了数组加以表达,但是数组的结构实在太简单了,第一它无法直接添加新元素,第二它只能按照线性排列,故而数组用于基本的操作倒还凑合,若要用于复杂的处理就无法胜任了 ...

  2. Java开发笔记(八十五)通过字符流读写文件

    前面介绍了文件的信息获取.管理操作,以及目录下的文件遍历,那么文件内部数据又是怎样读写的呢?这正是本文所要阐述的内容.File工具固然强大,但它并不能直接读写文件,而要借助于其它工具方能开展读写操作. ...

  3. Java开发笔记(二十五)方法的输入参数

    前面通过main方法介绍了方法的定义形式,对于方法的输入参数来说,还有几个值得注意的地方,接下来分别对输入参数的几种用法进行阐述.一个方法可以有输入参数,也可以没有输入参数,倘若无需输入参数,则方法定 ...

  4. Java开发笔记(三十五)字符串格式化

    前面介绍了字符串变量的四种赋值方式,对于简单的赋值来说完全够用了,即便是两个字符串拼接,也只需通过加号把两个目标串连起来即可.但对于复杂的赋值来说就麻烦了,假设现在需要拼接一个很长的字符串,字符串内部 ...

  5. Java开发笔记(四十五)成员属性与成员方法

    前面介绍了许多数据类型,除了基本类型如整型int.双精度型double.布尔型boolean之外,还有高级一些的如包装整型Integer.字符串类型String.本地日期类型LocalDate等等,那 ...

  6. Java开发笔记(七十五)异常的处理:扔出与捕捉

    前面介绍的几种异常(不包含错误),编码的时候没认真看还发现不了,直到程序运行到特定的代码跑不下去了,程序员才会恍然大悟:原来这里的代码逻辑有问题.像这些在运行的时候才暴露出来的异常,又被称作“运行时异 ...

  7. OpenCV开发笔记(六十五):红胖子8分钟带你深入了解ORB特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  8. Java开发笔记(八十二)注解的基本单元——元注解

    Java的注解非但是一种标记,还是一种特殊的类型,并且拥有专门的类型定义.前面介绍的五种内置注解,都可以找到对应的类型定义代码,例如查看注解@Override的源码,发现它的代码定义是下面这样的: @ ...

  9. Java开发笔记(三十二)字符型与整型相互转化

    前面提到字符类型是一种新的变量类型,然而编码实践的过程中却发现,某个具体的字符值居然可以赋值给整型变量!就像下面的例子代码那样,把字符值赋给整型变量,编译器不但没报错,而且还能正常运行! // 字符允 ...

  10. Java开发笔记(三十八)利用正则表达式校验字符串

    前面多次提到了正则串.正则表达式,那么正则表达式究竟是符合什么定义的字符串呢?正则表达式是编程语言处理字符串格式的一种逻辑式子,它利用若干保留字符定义了形形色色的匹配规则,从而通过一个式子来覆盖满足了 ...

随机推荐

  1. reader

    http://git.oschina.net/jayqqaa12/abase-reader https://github.com/JustWayward/BookReader https://gith ...

  2. 25Mybatis_查询缓存的基本知识

    mybatis提供查询缓存,用于减轻数据压力,提高数据库性能. mybaits提供一级缓存,和二级缓存. 一级和二级缓存的示意图:

  3. Tomcat启动过程(三):从SocketProcessor到Container

    1.Http11Protocol中的内部类Http11ConnectionHandler,执行其process方法 if (processor == null) { processor = creat ...

  4. windows server 2003 系统重装蓝屏

    错误码:0X0000007B 这个代码和硬盘有关系,不过不用害怕,不是有坏道了,是设置问题或者病毒造成的硬盘引导分区错误.如果您在用原版系统盘安装系统的时候出这个问题,那说明您的机器配置还是比较新的, ...

  5. Android studio 项目(Project)依赖(非Module)

    Android studio 项目(Project)依赖(非Module) 0. 前言 对于Module 级别的依赖大家都知道,今天说下Android Studio下的项目依赖. 场景: A Proj ...

  6. 点击图片或者鼠标放上hover .图片变大. 1)可以使用css中的transition, transform 2) 预先设置一个 弹出div. 3)弹出层 alert ; 4) 浏览器的宽度document.documentElement.clientWidth || document.body.clientWidth

    变大: 方法一: 利用css属性. 鼠标放上 hover放大几倍. .kecheng_02_cell_content img { /*width: 100px; height: 133px;*/ wi ...

  7. 开源小程序CMS网站, JeeWx-App-CMS 1.0 首版本发布

    JeeWx-App-CMS 是jeewx开发的小程序网站开源项目,基于小程序wepy语言,具备cms网站的基本功能,能够打造简单易用的小程序公司官网.项目结构简单,逻辑清晰,代码规范,非常适合作为小程 ...

  8. 七种RAID技术

    想把好多硬盘组在一起使用的基本方式为:连接所有硬盘,先向第一个硬盘中写数据,满了之后,再向第二个硬盘上写数据,如此只是简单的连通了多个硬盘. 再此基础上发展了RAID技术:由独立磁盘组成的具有冗余特性 ...

  9. .net WebService方法之重载、支持Session、支持request请求和response格式的浅析

    .net的webservice不支持web方法的重载,但是可以通过设置WebMethod属性的MessageName字段来达到重载web方法的目的. 通过设置WebMethod属性的EnableSes ...

  10. 转载:SQL按照日、周、月、年统计数据的方法

    转载源:http://www.jb51.net/article/42613.htm SQL按照日.周.月.季度.年统计数据的方法 方式一: --按日 select sum(consume),day([ ...