zoukankan      html  css  js  c++  java
  • 记录一次用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);
            }
        }
    }
    
  • 相关阅读:
    Oracle9使用oradata恢复数据库
    我该怎么安排下属的工作项目经理如何分配任务
    如果说中国的程序员技术偏低,原因可能在这里
    项目经理问:为什么总是只有我在加班 – 挂包袱现象
    【转】面试真经
    [JAVA]PING和TELNET用法介绍
    Hello World 你懂的
    线程间操作控件
    获取客户端相关信息
    winfrom 特效 [转载]
  • 原文地址:https://www.cnblogs.com/yao-xi/p/13914963.html
Copyright © 2011-2022 走看看