任务描述

使用 TCP 通信的知识,编写一个文件上传的程序,完成将本地机器 C 盘中名称为 1.jpg 的文件上传到 D 盘中名称为 upload 的文件夹中。要求把客户端的 IP 地址加上 count 标识作为上传后的文件名。即 IP(count)的形式。其中,count 是随着重名文件的增多而增大的,例如 127.0.0.1(1).jpg、127.0.0.1(2).jpg。

实现思路

  1. 从任务描述中使用 TCP 通信的知识实现文件上传功能可知,要实现此功能,需要定义一个服务器端接收文件的程序和一个客户端上传文件的程序。
  2. 首先需要编写服务器端程序来接收图片。服务器需要使用 ServerSocket 对象的 accept()方法接收客户端请求,由于一个服务器可能对应多个客户端,所以当客户端与服务器端建立连接后,服务器端需要单独开一一个新的线程来处理和客户端的交互,这时需要服务器端编写开启新线程方法。在新线程的方法中,需要获取客户端的端口号,并且使用输入输出流来传输图片到指定目录中。
  3. 实现完服务器端的代码后,还需要实现客户端功能代码。客户端功能的实现,需要使用 Scoket 类来创建客户端对象,并且通过输入输出流来定义指定的图片。
  4. 最后先运行服务器端程序,再运行客户端程序来测试结果。

代码实现

1、首先编写服务器端程序,用来接收图片,如下所示:

  1. package task02;
  2. import java.io.*;
  3. import java.net.*;
  4. public class FileServer {
  5. public static void main(String[] args) throws Exception {
  6. //创建 ServerSocket 对象
  7. ServerSocket serverSocket = new ServerSocket(10001);
  8. while (true) {
  9. //调用 accept()方法接收客户端请求,得到 Socket 对象
  10. Socket s = serverSocket.accept();
  11. //每当和客户建立 Socket 连接后,单独开启一个线程处理和客户端的交互
  12. new Thread(new ServerThread(s)).start();
  13. }
  14. }
  15. }
  16. //服务器端
  17. class ServerThread implements Runnable{
  18. private Socket socket;
  19. public ServerThread(Socket socket) {
  20. super();
  21. this.socket = socket;
  22. }
  23. @Override
  24. public void run() {
  25. //获取客户端的 IP 地址
  26. String ip = socket.getInetAddress().getHostAddress();
  27. int count = 1; //上传图片个数
  28. try {
  29. InputStream in = socket.getInputStream();
  30. //创建上传图片目录的 File 对象
  31. File parentFile = new File("C:\\upload");
  32. //如果不存在,就创建这个目录
  33. if (!parentFile.exists()) {
  34. parentFile.mkdir();
  35. }
  36. //把客户端的 IP 地址作为上传文件的文件名
  37. File file = new File(parentFile, ip + "(" + count + ").jpg");
  38. while (file.exists()) {
  39. //如果文件名存在,则把 count++
  40. file = new File(parentFile, ip + "(" + (count++) + ").jpg");
  41. }
  42. //创建 FileOutputStream 对象
  43. FileOutputStream fos = new FileOutputStream(file);
  44. byte[] btf = new byte[1024]; //定义一个 byte 数组
  45. int len = 0; //定义一个 int 类型的变量 len,初始值为 0
  46. while ((len = in.read(btf)) != -1) { //循环读取数据
  47. fos.write(btf, 0, len);
  48. }
  49. OutputStream out = socket.getOutputStream(); //获取服务端的输出流
  50. out.write("上传成功".getBytes()); //上传成功后向客户端写出“上传成功”
  51. fos.close();
  52. out.close();
  53. in.close();
  54. socket.close();
  55. } catch (Exception e) {
  56. //TODO
  57. }
  58. }
  59. }

运行结果如下所示:
image.png
上述代码块中,第 9 行代码用于创建一个 ServerSocket 对象,第 10 15 行代码用于再 while(true)无限循环中调用 ServerSocket 的 accept()方法来接收客户端的请求,每当和一个客户端建立 Socket 连接后,就开启一个新的线程和这个客户端进行交互。开启的新线程是通过实现 Runnable 接口创建的,重写的 run()方法中实现了服务器端接收并保存客户端上传图片的功能。第 35 行代码对上传图片的保存目录用一个 File 对象进行封装,如果这个目录不存在就调用 File 的 mkdir()方法创建这个目录。为了避免存放的图片重复而导致新上传的图片把已存在的图片覆盖,再第 31 行代码处定义了一个整型变量 count,用于统计图片的数目,使用“IP 地址(count).jpg”作为上传图片的名字。第 42 ~ 45 行代码用于对表示图片名的 File 对象进行循环判断,如果图片文件名存在,则一直执行 count++。最后将从客户端接收的图片信息写入指定的目录中,第 53 行和 54 行代码用于获得服务器端的输出流,向用户端输出“上传成功”信息。通过上图运行结果可以看出,服务器端加入阻塞状态,等待用户端连接。

2、编写客户端上传文件的程序,如下所示:

import java.io.*;
import java.net.Socket;

public class FileClient {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("127.0.0.1", 10001);    //创建客户端 Socket
        OutputStream out = socket.getOutputStream();    //获取 Socket 的输出流对象
        //创建 FileInputStream 对象
        FileInputStream fis = new FileInputStream("D:\\1.jpg");
        byte[] buf = new byte[1024];    //定义一个字节数组
        int len;    //定义一个 int 类型的变量 len
        while ((len = fis.read(buf)) != -1) {    //循环读取数据
            out.write(buf, 0, len);
        }
        socket.shutdownOutput();    //关闭输出流
        InputStream in = socket.getInputStream();    //获取 Socket 的输入流对象
        byte[] bufMsg = new byte[1024];    //定义一个字节数组
        int num = in.read(bufMsg);    //接收服务器端的信息
        String Msg = new String(bufMsg, 0, num);
        System.out.println(Msg);
        fis.close();
        socket.close();
    }
}

运行结果如下所示:
image.png
上述代码块中,第 6 行代码用于创建一个 Socket 对象,指定连接服务器的 IP 地址和端口号,然后获取 Socket 的输出流对象。第 9 ~ 14 行代码用于创建 FileInputStream 对象读取图片 1.jpg,共同过 Socket 的输出流对象向服务器端发送图片。发送完毕后调用 Socket 的 shutDownOutput()方法关闭客户端的输出流。

需要注意的是,shutDownOutput()方法非常重要,因为服务器端程序再 while 循环中读取客户端发送的数据时,如果读取不到数据,fis.read(buf)方法会返回 -1。

也就是说,只要返回的值不是 -1,就说明还有数据,需要一直读取,只有 fis.read(buf)方法的返回值为 -1 时才会结束循环。

如果在客户端不调用 shutDownOutput()方法关闭输出流,服务器端的 fis.read(buf)方法就不会返回 -1,而会一直执行 while 循环,同时客户端读取服务端数据的 read(byte[])方法就不会返回 -1,而会一直执行 while 循环,同时客户端读取服务端数据的 read(byte[])方法也是一个阻塞方法,这样服务器和客户端程序就进入了一个“死锁”状态,两个程序都不能结束。

客户端上传图片成功后,会读取服务端发送的“上传完毕”信息,至此,客户端程序的编写也就完成了。为了证实图片是否上传成功,下面进入到 C:\upload 目录下,在该目录下可以看到以 IP+count 编号命名的图片,说明图片上传成功,如下图所示:
QQ截图20200713230054.png