记录一次用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);
        }
    }
}
原文地址:https://www.cnblogs.com/yao-xi/p/13914963.html