2020-11-02

记录一次用TCP协议传文件的探索

记录一次用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