记录一次用TCP协议传文件的探索
总的思路,客户端创建Socket对象和服务端通信,通过Socket对象获取的IO流进行数据传输.基本的代码如下:
客户端(发送端)部分
public class Client { public static void main(String[] args) throws IOException { // 要上传的文件 File file = new File("C:\\Users\\Yaoxi\\Pictures\\Saved Pictures\\图像.jpg"); // 创建一个socket对象用来连接服务器 // Socket(套接字)是两台计算机之间进行通信的端点 Socket socket = new Socket("127.0.0.1", 7341); // 通过socket对象获取输入输出流 // 通过流分别创建字符缓冲输入流(读服务器反馈)和打印流(传数据给服务器) BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintStream ps = new PrintStream(socket.getOutputStream()); // 发文件数据 FileInputStream fis = new FileInputStream(file); byte[] bytes = new byte[1024 * 8]; int len; while ((len = fis.read(bytes)) != -1) { ps.write(bytes, 0, len); } // 读服务器反馈 String s = br.readLine(); System.out.println(s); // 资源释放 socket.close(); }}
服务器(接收端)部分
public class Server { public static void main(String[] args) throws IOException { // 创建ServerSocket对象 // ServerSocket会等待请求通过网络进入 ServerSocket ss = new ServerSocket(7341); // 监听客户端的连接(阻塞方法,如果没有会一直等待) Socket socket = ss.accept(); // 获取I/O流 PrintStream ps = new PrintStream(socket.getOutputStream()); InputStream in = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); // 设置文件接收位置 File fileName = new File("D:\\server\\a.jpg"); // 收文件 FileOutputStream fos = new FileOutputStream(fileName); byte[] bytes = new byte[1024 * 8]; int len; while ((len = br.read()) != -1) { fos.write(bytes, 0, len); } // 发反馈给客户端 ps.println("接收完毕"); }}
这样子将会存在一定的问题
1.客户端未发送结束标记,服务端的read会一直等待读取
2.文件名被固定写死,每次接收都会覆盖
3.服务端一次运行后只能接收一次文件
4.服务端不能同时处理多个文件上传
对于问题1,可以给客户端发文件的时候加个结束标记
shutdownOutput()方法会通过关闭io流作为文件传输结束的标记通知服务端.
客户端(发送端)部分改造后
public class Client { public static void main(String[] args) throws IOException { // 要上传的文件 File file = new File("C:\\Users\\Yaoxi\\Pictures\\Saved Pictures\\图像.jpg"); // 创建一个socket对象用来连接服务器 // Socket(套接字)是两台计算机之间进行通信的端点 Socket socket = new Socket("127.0.0.1", 7341); // 通过socket对象获取输入输出流 // 通过流分别创建字符缓冲输入流(读服务器反馈)和打印流(传数据给服务器) BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintStream ps = new PrintStream(socket.getOutputStream()); // 发文件数据 FileInputStream fis = new FileInputStream(file); byte[] bytes = new byte[1024 * 8]; int len; while ((len = fis.read(bytes)) != -1) { ps.write(bytes, 0, len); } // 写结束标记 socket.shutdownOutput(); // 读服务器反馈 String s = br.readLine(); System.out.println(s); // 资源释放 socket.close(); }}
对于问题2,可以采取加一个随机的文件名去解决.
在客户端发送的时候可以先发一次文件名,然后服务端收到后拼接随机的字符串,作为即将接收的文件名,再接收文件存入本地磁盘中.
这个过程中会遇到另一个问题,接收文件名的时候服务端用的是BufferedReader,有缓冲区的存在导致之后传输文件时,可能会出现数据丢失.因此当文件名接收到后,服务端回写一条信息,通知客户端再发文件,作为操作的分隔.
客户端(发送端)部分改造后
public class Client { public static void main(String[] args) throws IOException { // 要上传的文件 File file = new File("C:\\Users\\Yaoxi\\Pictures\\Saved Pictures\\图像.jpg"); // 创建一个socket对象用来连接服务器 // Socket(套接字)是两台计算机之间进行通信的端点 Socket socket = new Socket("127.0.0.1", 7341); // 通过socket对象获取输入输出流 // 通过流分别创建字符缓冲输入流(读服务器反馈)和打印流(传数据给服务器) BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintStream ps = new PrintStream(socket.getOutputStream()); // 给服务器传递文件名 ps.println(file.getName()); // 接收服务器回写的分隔信息 String start = br.readLine(); System.out.println(start); // 再发文件数据 FileInputStream fis = new FileInputStream(file); byte[] bytes = new byte[1024 * 8]; int len; while ((len = fis.read(bytes)) != -1) { ps.write(bytes, 0, len); } // 写结束标记 socket.shutdownOutput(); // 读服务器反馈 String s = br.readLine(); System.out.println(s); // 资源释放 socket.close(); }}
服务器(接收端)部分改造后
public class Server { public static void main(String[] args) throws IOException { // 创建ServerSocket对象 // ServerSocket会等待请求通过网络进入 ServerSocket ss = new ServerSocket(7341); // 监听客户端的连接(阻塞方法,如果没有会一直等待) Socket socket = ss.accept(); // 获取I/O流 PrintStream ps = new PrintStream(socket.getOutputStream()); InputStream in = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); // 读文件名 String fileName = br.readLine(); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(socket.getInetAddress().getHostAddress() + " 发送文件 " + fileName); // 回写消息给客户端 // 作为两个读取操作的分隔,防止用InputStream读数据时,BufferedReader缓冲区内容丢失 ps.println("开始接收文件"); // 创建流关联到本地硬盘 File dir = new File("D:\\server"); // 防止文件重复使用UUID作为随机字符串 String uuid = UUID.randomUUID().toString().replace("-", ""); // 设置文件接收位置 File dir = new File("D:\\server"); // 收文件并拼接文件名 FileOutputStream fos = new FileOutputStream(dir + "\\" + uuid + "_" + fileName); byte[] bytes = new byte[1024 * 8]; int len; while ((len = br.read()) != -1) { fos.write(bytes, 0, len); } // 发反馈给客户端 ps.println("接收完毕"); }}
对于问题3,在服务端的处理方法外加一层循环即可
服务器(接收端)部分改造后
public class Server { public static void main(String[] args) throws IOException { // 创建ServerSocket对象 // ServerSocket会等待请求通过网络进入 ServerSocket ss = new ServerSocket(7341); // 使用死循环保证程序一直能接收请求 while (true) { // 监听客户端的连接(阻塞方法,如果没有会一直等待) Socket socket = ss.accept(); // 获取I/O流 PrintStream ps = new PrintStream(socket.getOutputStream()); InputStream in = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); // 读文件名 String fileName = br.readLine(); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(socket.getInetAddress().getHostAddress() + " 发送文件 " + fileName); // 回写消息给客户端 // 作为两个读取操作的分隔,防止用InputStream读数据时,BufferedReader缓冲区内容丢失 ps.println("开始接收文件"); // 创建流关联到本地硬盘 File dir = new File("D:\\server"); // 防止文件重复使用UUID作为随机字符串 String uuid = UUID.randomUUID().toString().replace("-", ""); // 设置文件接收位置 File dir = new File("D:\\server"); // 收文件并拼接文件名 FileOutputStream fos = new FileOutputStream(dir + "\\" + uuid + "_" + fileName); byte[] bytes = new byte[1024 * 8]; int len; while ((len = br.read()) != -1) { fos.write(bytes, 0, len); } // 发反馈给客户端 ps.println("接收完毕"); } }}
对于问题4,在服务端改用多线程去实现
其中需要注意 accept
方法是个阻塞方法,所以它不能放在线程的run
方法中.否则就会一直开线程,然后每个线程在执行accept的时候阻塞.
服务器(接收端)部分改造后
public class Server { public static void main(String[] args) throws IOException { // 创建ServerSocket对象 // ServerSocket会等待请求通过网络进入 ServerSocket ss = new ServerSocket(7341); // 使用死循环保证程序一直能接收请求 // 可以多次访问 while (true) { // 监听客户端的连接(阻塞方法) // (用来阻塞main线程,当有访问的时候才继续运行开线程) Socket socket = ss.accept(); // 用Runnable开线程 Runnable runnable = new Runnable() { @Override public void run() { FileOutputStream fos = null; try { // 获取I/O流 PrintStream ps = new PrintStream(socket.getOutputStream()); InputStream in = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); // 读文件名 String fileName = br.readLine(); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(socket.getInetAddress().getHostAddress() + " 发送文件 " + fileName); // 回写消息给客户端 // 作为两个读取操作的分隔,防止用InputStream读数据时,BufferedReader缓冲区内容丢失) ps.println("开始接收文件"); // 创建流关联到本地硬盘 File dir = new File("D:\\server"); // 防止文件重复 String uuid = UUID.randomUUID().toString().replace("-", ""); fos = new FileOutputStream(dir + "\\" + uuid + "_" + fileName); byte[] bytes = new byte[1024 * 8]; int len; // 从客户端的流读 while ((len = in.read(bytes)) != -1) { // 写到本地文件的流 fos.write(bytes, 0, len); } // 回写给客户端 ps.println("接收完毕"); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(socket.getInetAddress().getHostAddress() + " 发送文件接收完毕"); } catch (IOException e) { e.printStackTrace(); } finally { // 释放资源 if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }; // 提交到线程池 new Thread(runnable).start(); } }}
现在有了一个线程,不如顺便加个线程池,方便使用.
服务器(接收端)部分改造后
public class Server { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(7341); // 创建线程池 ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); // 可以多次访问 while (true) { // 监听客户端的连接(阻塞方法) // (用来阻塞main线程,当有访问的时候才继续运行开线程) Socket socket = ss.accept(); // 用Runnable开线程 Runnable runnable = new Runnable() { @Override public void run() { FileOutputStream fos = null; try { // 获取I/O流 PrintStream ps = new PrintStream(socket.getOutputStream()); InputStream in = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); // 读文件名 String fileName = br.readLine(); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(socket.getInetAddress().getHostAddress() + " 发送文件 " + fileName); // 回写消息给客户端 // (作为两个读取操作的分隔, // 防止用InputStream读数据时, // BufferedReader缓冲区内容丢失) ps.println("开始接收文件"); // 创建流关联到本地硬盘 File dir = new File("D:\\server"); // 防止文件重复 String uuid = UUID.randomUUID().toString().replace("-", ""); fos = new FileOutputStream(dir + "\\" + uuid + "_" + fileName); byte[] bytes = new byte[1024 * 8]; int len; // 从客户端的流读 while ((len = in.read(bytes)) != -1) { // 写到本地文件的流 fos.write(bytes, 0, len); } // 回写给客户端 ps.println("接收完毕"); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println(socket.getInetAddress().getHostAddress() + " 发送文件接收完毕"); } catch (IOException e) { e.printStackTrace(); } finally { // 释放资源 if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }; // 提交到线程池 pool.submit(runnable); } }}
原文转载:http://www.shaoqun.com/a/485961.html
捷汇:https://www.ikjzd.com/w/419
amazon go:https://www.ikjzd.com/w/67
腾邦:https://www.ikjzd.com/w/1382
记录一次用TCP协议传文件的探索总的思路,客户端创建Socket对象和服务端通信,通过Socket对象获取的IO流进行数据传输.基本的代码如下:客户端(发送端)部分publicclassClient{publicstaticvoidmain(String[]args)throwsIOException{//要上传的文件Filefile=newFile("C:\\Users\\Yaoxi\
一淘网:https://www.ikjzd.com/w/1698
海豚村:https://www.ikjzd.com/w/1779
深圳四海公园要门票吗?:http://tour.shaoqun.com/a/859.html
亚马逊海外销售指南(套路技巧总结):https://www.ikjzd.com/tl/97084
KOL营销干货技巧助你快速提升品牌影响力!:https://www.ikjzd.com/home/100538
No comments:
Post a Comment