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


    前面介绍了怎样通过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开发笔记(序)章节目录

  • 相关阅读:
    Oracle学习笔记(oracle日期处理)
    Oralce学习笔记(plsql链接客户端)
    innerText和innerHTML应用
    oracle学习笔记(行转列列转行)
    js工作笔记基础一(分隔字符串)
    Oracle学习笔记(动态函数调用)
    理解!Page.IsPostBack和NET控件中的AutoPostBack
    oracle学习笔记(包头模板)
    div拖动层自己写
    oralce学习笔记(包体模板)
  • 原文地址:https://www.cnblogs.com/pinlantu/p/11079661.html
Copyright © 2020-2023  润新知