week05_day02_Network&&Reflection_网络编程之TCP&&反射

UDP:首先应用层逻辑将传输的数据封装成一个数据报包,将数据报包交给传输层的DatagramSocket对象,然后DatagramSocket对象将数据报包传输给对端传输层的DatagramSocket对象,然后对端的DatagramSocket对象将数据报包交给应用层逻辑,应用层经过解析得到数据。

TCP:客户端和服务器端建立好连接,通过输入流和输出流传输数据。
注意事项:
1. ServerSocket 仅仅只处理客户端的连接请求,并且其accept方法在接收到连接请求之后,会为本次连接创建新的Socket对象
2. 是服务器端的Socket和客户端的Socket对象,之间建立连接并传输数据
在这里插入图片描述

我们不去关心,不同协议实现上的不同,只从使用基于TCP协议实现的Socket的角度来说,基于TCP的socket使用起来,似乎要简单许多!

为什么这么说呢?因为基于TCP的Socket的数据传输是基于流来实现的。

这样一来,目的主机就可以被看做一个普通的文件!!

客户端:
1.建立客户端的Socket对象,并明确要连接的服务器。
2.如果对象建立成功,就表明已经建立了连接.就可以在该通道通过IO进行数据的读取和写入
根据需要从socket对象中获取输入,或输出流
3.向流中读取或写入数据
4.释放资源

1.服务器给客户端反馈
Client:

package com.cskaoyan.tcp.basic;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/**
 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/5/5.
 * @version 1.0
 * 
    Socket: 此类实现客户端套接字(也可以就叫“套接字”)。
     构造方法:
      该Socket对象本身,绑定的本机ip + 一个创建该对象时随机分配的端口号
      Socket(String host, int port)
      创建一个流套接字,并将其连接到 指定主机上 的 指定端口号。


    host和port,指的要连接的对端的ip地址和端口号
    host - 主机名或ip地址字符串,或者为 null,表示回送地址(127.0.0.1)。
    port - 端口号。

    如果在没有服务器端的时候,直接运行客户端:
    java.net.ConnectException: Connection refused: connect
    原因是还没有和服务器端建立连接
 */
public class Client {

  public static void main(String[] args) throws IOException {

    //1.建立客户端的Socket对象,并明确要连接的服务器。
    Socket socket = new Socket("192.168.0.100", 11111);

    //如果要发送数据,那么就从Socket对象中获取,输出流
    OutputStream outputStream = socket.getOutputStream();

    //通过输出流,向对端发送数据
    String s = "hello, tcp";

    // 利用输出流的write方法传输数据
    outputStream.write(s.getBytes());

    //关闭资源, Socket中的流,不用专门关闭,socket会负责关闭它所持有的流
    socket.close();
  }

}

服务器端:
1.创建ServerSocket对象,在指定端口,监听客户端连接请求
2.收到客户端连接请求后,建立Socket连接
3.如果连接建立成功,就表明已经建立了数据传输的通道.就可以在该通道通过IO进行数据的读取和写入
从socket中根据需要获取输入,或输出流
4.根据需要向流中写入数据或从流中读数据
5.释放资源
Server:

package com.cskaoyan.tcp.basic;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/5/5.
 * @version 1.0
 *
	 ServerSocket(int port)   // 本机ip + port
    创建绑定到特定端口的服务器套接字。

    public Socket accept()
      侦听并接受到此套接字的连接(请求)。此方法在连接传入之前一直阻塞。
     注意: BindException: Address already in use
     同一个端口号,只能属于一个进程

 */
public class Server {

  public static void main(String[] args) throws IOException {

    //1.创建ServerSocket对象,在指定端口,监听客户端连接请求
    ServerSocket serverSocket = new ServerSocket(11111);

    //2.收到客户端连接请求后,建立Socket连接
    Socket socket = serverSocket.accept();

    //3. 在建立好的连接中,通过在服务器端的Socket对象中,获取流,来进行数据传输
    InputStream inputStream = socket.getInputStream();

    //4. 在输入流中读取客户端发送的数据
    byte[] buffer = new byte[1024];
    int len = inputStream.read(buffer);

    //解析接收到的数据
    String s = new String(buffer, 0, len);
    System.out.println(s);

    //关闭资源, Socket中的流,不用专门关闭,socket会负责关闭它所持有的流
    socket.close();
    serverSocket.close();
  }

}

注意:两次启动Server端也会报错,BindException: Address already in use。原因同UDP编程中的Receiver,因为serverSocket.accept()方法是一个阻塞方法,阻塞方法意味着进程还没有结束,第二次运行Server端就会新建一个进程,而同一台主机上的一个端口号只能绑定一个进程。
············································································································································································································

2.客户端键盘录入,服务器输出文本文件

package com.cskaoyan.tcp.exercise03;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;

/**
 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/5/5.
 * @version 1.0
 *
 * 客户端键盘录入,服务器输出文本文件
 */
public class Client {

  public static void main(String[] args) throws IOException {

    //1.建立客户端的Socket对象,并明确要连接的服务器。
    Socket socket = new Socket("192.168.1.2", 10086);

    //如果要发送数据,那么就从Socket对象中获取,输出流
    OutputStream outputStream = socket.getOutputStream();
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));

    //通过输出流,向对端发送数据
    String s = "hello, tcp";

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    while ((s = br.readLine()) != null) {
      // 利用输出流的write方法传输数据
      bw.write(s);
      bw.newLine();
      bw.flush();
    }

    //关闭标准输入流
    br.close();
    //关闭资源, Socket中的流,不用专门关闭,socket会负责关闭它所持有的流
    socket.close();
  }
}

Servet:

package com.cskaoyan.tcp.exercise03;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/5/5.
 * @version 1.0
 */
public class Server {

  public static void main(String[] args) throws IOException {

    //1.创建ServerSocket对象,在指定端口,监听客户端连接请求
    ServerSocket serverSocket = new ServerSocket(10086);

    //2.收到客户端连接请求后,建立Socket连接
    Socket socket = serverSocket.accept();

    byte[] buffer = new byte[1024];
    InputStream in = socket.getInputStream();

    FileOutputStream fos = new FileOutputStream("record.txt");

    //准备文件输出流,将客户端发送的内容输出到文件中去
    int len;
    while ((len = in.read(buffer)) != -1) {
      fos.write(buffer, 0, len);
    }

    //关闭资源, Socket中的流,不用专门关闭,socket会负责关闭它所持有的流
    fos.close();
    socket.close();
    serverSocket.close();
  }
}

3.客户端文本文件,服务器输出到控制台
Client:

package com.cskaoyan.tcp.exercise04;

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

/**
 * @author shihao
 * @create 2020-05-06 15:57
 *
 * 3.客户端文本文件,服务器输出到控制台
 */
public class Client {

    public static void main(String[] args) throws IOException {

        Socket socket = new Socket("192.168.1.2",11111);

        OutputStream os = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));

        //读取文件内容
        BufferedReader br = new BufferedReader(new FileReader("F:\\test\\test01.txt"));

        //按行将文本文件数据输出到服务器端
        String s;
        while((s=br.readLine())!=null){
            bw.write(s);
            bw.newLine();
        }

        bw.close();
        br.close();
        socket.close();

    }
}

Server:

package com.cskaoyan.tcp.exercise04;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author shihao
 * @create 2020-05-06 15:57
 */
public class Server {

    public static void main(String[] args) throws IOException {

        //1.
        ServerSocket serverSocket = new ServerSocket(11111);

        //2.
        Socket socket = serverSocket.accept();

        //3.
        InputStream is = socket.getInputStream();

        //4.
        byte[] bytes = new byte[1024];
        int len;
        while((len=is.read(bytes))!=-1){
            System.out.println(new String(bytes,0,len));
        }

        //5.
        //is.close();  is流是从socket.getInputStream中读取的,
        // 不需自己要关闭,将socket关闭即可
        socket.close();
        serverSocket.close();
    }
}

4.客户端文本文件,服务器输出文本文件(文件上传功能)
Client:

package com.cskaoyan.tcp.exercise05;

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

/**
 * @author shihao
 * @create 2020-05-06 15:57
 *
 * 3.客户端文本文件,服务器输出到控制台
 */
public class Client {

    public static void main(String[] args) throws IOException {

        Socket socket = new Socket("192.168.1.2",11111);

        OutputStream os = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));

        //读取文件内容
        BufferedReader br = new BufferedReader(new FileReader("F:\\test\\test01.txt"));

        //按行将文本文件数据输出到服务器端
        String s;
        while((s=br.readLine())!=null){
            bw.write(s);
            bw.newLine();
        }
        bw.flush();
        br.close();

		//当文件上传完之后
        socket.shutdownOutput();

        //接收服务器端的反馈信息
        InputStream in = socket.getInputStream();
        byte[] buf = new byte[1024];

        // Socket中的流的read方法都是阻塞方法
        int len = in.read(buf);
        System.out.println("接收到反馈:" + new String(buf, 0, len));

        socket.close();

    }
}

Server:

package com.cskaoyan.tcp.exercise05;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author shihao
 * @create 2020-05-06 15:57
 */
public class Server {

    public static void main(String[] args) throws IOException {

        //1.
        ServerSocket serverSocket = new ServerSocket(11111);

        //2.
        //accept方法是一个阻塞方法
        Socket socket = serverSocket.accept();

        //3.
        InputStream is = socket.getInputStream();

        //4.
        FileOutputStream fos = new FileOutputStream("F:\\test\\test13.txt");
        byte[] bytes = new byte[1024];
        int len;
		//Socket对象中的read是一个阻塞方法	
        while((len=is.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }

        //给客户端发送反馈信息
        OutputStream os = socket.getOutputStream();
        os.write("文件上传完毕!".getBytes());

        //5.
        //is.close();  is流是从socket.getInputStream中读取的,
        // 不需自己要关闭,将socket关闭即可
        fos.close();
        socket.close();
        serverSocket.close();
    }
}

文件传输完毕后,我们让服务器端给客户端发送反馈信息:文件传输完毕!,但真正执行时,Client的控制台却没有反馈信息,并且Client和Server都处于阻塞状态。why?
分析一下代码:
首先启动Server端,serverSocket.accept();是一个阻塞方法,会让Server端处于阻塞状态。

然后启动Client端,会和Server端建立连接请求,创建一个文件输出流和文件输入流,将txt文件内容传输到服务端。
然后读取反馈信息,由于Socket中的流的read方法都是阻塞方法,所以当执行到int len = in.read(buf);时,Client端阻塞在这里,就等着Server端给他发送反馈数据。

再看Server端,会将读取到的内容写入test13.txt文件中。
但是,Socket对象中的read是一个阻塞方法
while((len=is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
也就是说这个循环永远不会结束,所以当传输完毕时,Server的代码就会永远阻塞在len=is.read(bytes))!=-1这里。

也就是说,Server在等待Client发送从test01.txt文件中读取的文件内容。而Client已经读取完test01.txt中的所有内容并发送给了Server。目前,Client在等待Server发送反馈信息。
所以,现在出现了相互等待的情况。我们看到了Client和Server的程序都没有结束,但文件内容已经上传完毕。

调动shutdown后,单方面的关闭Client端的输出流,代表不会向Server端传输数据了,此时Server端自然就不会卡在read处。

  • public void shutdownOutput()
    禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列
    当Client关闭输出流后,会发送TCP 的正常连接终止序列,Server端一看,Client端要终止传输了,也就不再等待。

··············································································································································································································

之前讲的都是一个客户端对应一个服务端,但其实一个服务器端可以对应多个客户端。
改进一:
Client端代码不变,Server端代码如下:

package com.cskaoyan.tcp.exercise06;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author shihao
 * @create 2020-05-06 15:57
 */
public class Server {

    public static void main(String[] args) throws IOException {

        //1.
        boolean flag = true;
        ServerSocket serverSocket = new ServerSocket(11111);

        while(flag){
            //2.
            //accept方法是一个阻塞方法
            Socket socket = serverSocket.accept();
            uploadFile(socket);

            sendResponse(socket);

            //5.
            socket.close();
        }

        serverSocket.close();

    }

    private static void sendResponse(Socket socket) throws IOException {
        //给客户端发送反馈信息
        OutputStream os = socket.getOutputStream();
        os.write("文件上传完毕!".getBytes());
        os.close();
    }

    private static void uploadFile(Socket socket) throws IOException {
        //3.
        InputStream is = socket.getInputStream();

        //4.
        FileOutputStream fos = new FileOutputStream("F:\\test\\test13.txt");
        byte[] bytes = new byte[1024];
        int len;
        while((len=is.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        fos.close();
    }
}

这样会有一个问题:
假如Client端网速不同,上传文件大小不同,显然客户端3虽然传输时间很少,但它却要等待很长时间。
在这里插入图片描述

··············································································································································································································

如何改善呢?
同步建立连接。异步传输数据
会发现整个代码其实分为两步,连接请求和数据传输。
我们应当把数据传输放在子线程中执行。而Server端一直建立连接建立连接。Server端在三个线程中分别接受client端传输的数据。

public static void main(String[] args) throws IOException {

        //1.
        boolean flag = true;
        ServerSocket serverSocket = new ServerSocket(11111);

        while(flag){
            //2.
            //accept方法是一个阻塞方法
            //连接请求
            Socket socket = serverSocket.accept();

            //-----------------------------------------------------------------------

			//数据传输
            uploadFile(socket);

            sendResponse(socket);

            //5.
            socket.close();
        }

        serverSocket.close();

    }

接下来,上最终版代码:
UploadServerThread:

package com.cskaoyan.tcp.exercise07;

import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

/**
 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/5/5.
 * @version 1.0
 */
public class UploadServerThread extends Thread {

  private Socket socket;

  private String fileName;

  public UploadServerThread(Socket socket, String fileName) {
    this.socket = socket;
    this.fileName = fileName;
  }


  @Override
  public void run() {

    //开始读取数据
    InputStream inputStream = null;
    FileOutputStream fos = null;
    
    try {
      inputStream = socket.getInputStream();
      BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

      fos = new FileOutputStream(fileName);

      // Socket对象中的read方法都是阻塞方法
      String s;
      while ((s = br.readLine()) != null) {
        fos.write(s.getBytes());
        fos.write('\n');
      }

      //发送响应
      OutputStream out = socket.getOutputStream();
      out.write("文件上传完毕".getBytes());
      out.close();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      //自己创建的流,自己关闭
      if (fos != null) {
        try {
          fos.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

      try {
        socket.close();
      } catch (IOException e) {
        e.printStackTrace();
      }

    }
  }
}

UploadClientThread:

package com.cskaoyan.tcp.exercise07;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;

/**
 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/5/5.
 * @version 1.0
 */
public class UploadClientThread extends Thread{


  private File uploadFile;

  public UploadClientThread (File uploadFile) {
    this.uploadFile = uploadFile;
  }

  @Override
  public void run() {

    Socket socket = null;
    try {
        socket = new Socket("localhost", 9801);

        BufferedWriter bw = new BufferedWriter
              (new OutputStreamWriter(socket.getOutputStream()));
        BufferedReader br = new BufferedReader(new FileReader(uploadFile));

        //按行,将文本文件数据,输出到服务器端
        String s;
        while ((s = br.readLine()) != null) {
            bw.write(s);
            bw.newLine();
        }
        bw.flush();
        br.close();

        //当文件上传完之后
        socket.shutdownOutput();

        //接收服务器端的反馈信息
        InputStream in = socket.getInputStream();
        byte[] buf = new byte[1024];

        // Socket中的流的read方法都是阻塞方法
        int len = in.read(buf);
        System.out.println("接收到反馈:" + new String(buf, 0, len));

    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
          socket.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
    }
  }
}

Server:

package com.cskaoyan.tcp.exercise07;

import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/5/5.
 * @version 1.0
 *
 * 多个客户端传输文件到一个服务器
 */
public class Server {

    //给每一个上传文件一个唯一编号
    static long fileId = 0;

    public static void main(String[] args) throws IOException {

        boolean flag = true;
        ServerSocket serverSocket = new ServerSocket(9801);

        while (flag) {
            // accept是一个阻塞方法
            Socket socket = serverSocket.accept();

            //对于每一个上传文件,都要存储到一个服务器端的文件中,所以也就说对每一个上传的文件
            //起一个不同的文件名
            String filename = "F:\\test\\" + System.currentTimeMillis() + "-" + fileId++ + ".txt";
            new UploadServerThread(socket , filename).start();
        }

        // 7 * 24小时工作 服务器,服务器是不会关的,不能让服务器宕机
        serverSocket.close();
    }
}

Client:

package com.cskaoyan.tcp.exercise07;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;

/**
 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/5/5.
 * @version 1.0
 *
 * public void shutdownOutput()
      禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列
 */
public class Client {

  public static void main(String[] args) throws IOException {
  
    File[] uploadFiles = {new File("F:\\test\\test01.txt"),
            new File("F:\\test\\test01.txt"),
            new File("F:\\test\\test01.txt")};

    for (int i = 0; i < uploadFiles.length; i++) {
      new UploadClientThread(uploadFiles[i]).start();
    }

  }
}

··············································································································································································································

作业:

  1. 参考com.cskaoyan.tcp包下的exercise07中的代码,实现多线程(每个线程代表一个客户端)上传,多个jpg图片文件的代码,但是要注意,exercise04中实现的是文本文件的上传,但是我们只能用字节流处理图片文件

TCPSender:

package com.cskaoyan.tcp.hoework2;

import java.io.IOException;

/**
 * @author shihao
 * @create 2020-05-06 21:25
 */

public class TCPSender {

    public static void main(String[] args) throws IOException, InterruptedException {


        String[] files = {"upload.jpg", "upload1.jpg"};

        for (int i = 0; i < files.length; i++) {
            new UploadSenderThread(files[i]).start();
        }
    }
}

UploadSenderThread:

package com.cskaoyan.tcp.hoework2;

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

/**
 * @author shihao
 * @create 2020-05-06 21:26
 */
public class UploadSenderThread extends Thread {

    public String fileName;

    public UploadSenderThread (String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void run() {

        //1. 创建tcp的发送端(客户端)Socket对象,并且和指定的 接收端ip和端口的 socket建立连接
        Socket socket = null;
        byte[] buffer = new byte[1024];
        BufferedInputStream bis = null;
        try {
            socket = new Socket("192.168.0.100", 10086);

            System.out.println(socket.getLocalAddress() + ":" + socket.getLocalPort());

            //建立好连接之后要发送数据
            // 从已经建立好连接的Socket对象中,获取传输数据的输出流
            OutputStream out = socket.getOutputStream();
            BufferedOutputStream bw = new BufferedOutputStream(out);

            //创建文件输入流,读取文件内容
            FileInputStream fis = new FileInputStream(fileName);
            bis = new BufferedInputStream(fis);


            int len;
            while ((len = bis.read(buffer)) != -1) {
                bw.write(buffer, 0, len);
            }

            //刷新缓冲输出流缓冲区
            bw.flush();

            //禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列
            socket.shutdownOutput();

            接收一下服务器端的反馈
            InputStream inputStream = socket.getInputStream();
            //如果服务器端没有发送反馈,发送端,会卡在这里
            buffer = new byte[1024];
            len = inputStream.read(buffer);
            System.out.println(new String(buffer, 0, len));

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            //关闭socket
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            //关闭读取文件的缓冲流
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }
}

TCPReceiver:

package com.cskaoyan.tcp.hoework2;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author shihao
 * @create 2020-05-06 21:27
 */
public class TCPReceiver {

    //与uploadFileName配合解决文件重名问题
    // 给上传的文件编个号相当于(或者只是用该文件编号来解决上传文件的命名冲突问题也可以)
    static long fileId = 0L;

    public static void main(String[] args) throws IOException {

        boolean flag = true;

        ServerSocket serverSocket = new ServerSocket(10086);

        //ServerSocket 只是接受,连接请求,并建立连接
        // accept方法是一个阻塞方法
        Socket socket = null;
        while (flag) {
            socket = serverSocket.accept();
            //解决客户端文件重名问题
            String uploadFileName = System.currentTimeMillis() + "-" + fileId++;
            new UploadReceiverThread(socket, uploadFileName).start();
        }
        serverSocket.close();
    }

}

UploadReceiverThread:

package com.cskaoyan.tcp.hoework2;

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

/**
 * @author shihao
 * @create 2020-05-06 21:27
 */
public class UploadReceiverThread extends Thread {

    private Socket socket;
    private String fileName;

    public UploadReceiverThread(Socket socket, String fileName) {
        this.socket = socket;
        this.fileName = fileName;
    }

    @Override
    public void run() {
        //从建立好连接的服务器端(接收端)的Socket对象中拿得到,输入流,准备通过输入流接收数据
        InputStream inputStream = null;
        byte[] buffer = new byte[1024];
        BufferedOutputStream bos = null;
        try {
            inputStream = socket.getInputStream();
            System.out.println(socket.getLocalPort());
            BufferedInputStream bis = new BufferedInputStream(inputStream);

            //创建文件字节输出流
            FileOutputStream fos = new FileOutputStream(fileName + ".jpg");
            bos = new BufferedOutputStream(fos);

            int len;
            // Socket 中的InputStream的read也是一个阻塞方法
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            //刷新缓冲字节流缓冲区
            bos.flush();

            //发送反馈信息
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("文件上传完毕".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭写文件内容的缓冲输出流
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //关闭socket
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

············································································································································································································
反射

  1. 比如说现在我们持有了一个对象
    A a = new A();
    对于该对象,在不看A类代码前提下,我们根本不知道如何访问使用a对象
    原因在于,我们不知道a对象中,有哪些成员(类中定义的)

  2. 我们自己定义的类,jvm天生是不认识的,但是jvm有办法认识这些类对应的数据类型
    -> 通过加载并解析类对应的字节码文件

  3. 在jvm类加载的过程中,会对每一个类生成一个Class对象,一个Class对象中就包含了
    一个类定义的完整信息,所以我们可以在程序运行的过程中,通过访问某个类对应的Class对象
    通过访问Class对象,获取到相应的类型信息

    说白了,反射,就是用来获取运行时类型信息(Class对象):

    1. 构造方法 -> 创建对象
    2. 成员变量 -> 在该类型任意对象上访问该成员变量(即使是private成员变量)
    3. 成员方法 -> 在该类型的任意对象,调用该成员方法(即使是private成员方法)

所以,这阶段我们的学习步骤是:

  1. 简单了解类加载过程(经过类加载才有Class对象)
  2. 如何获取Class对象?(反射技术的起点)(三种方式)
  3. 如何获取Class对象中,所包含的类定义相关信息(成员方法、成员变量)

············································································································································································································
1. 简单了解类加载过程
类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载
通过一个类的全限定名来获取定义此类的二进制字节流
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
连接
验证:确保被加载类的正确性(如果一个文件的前四个字是 ca fe ba be,jvm才认为它是字节码文件,才会加载)
准备:负责为类的静态成员分配内存并设置默认初始化值
解析:将类中的符号引用替换为直接引用(简单理解:将引用变量的值替换为内存地址)
初始化
给静态成员变量赋初值,执行静态代码块内容(所以说静态代码块随着类加载而执行)

类加载时机
创建类的实例(首次创建该类对象)
访问类的静态变量
调用类的静态方法
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象,如后面讲的:static Class forName(String className)
初始化某个类的子类,会先触发父类的加载
直接使用java.exe命令来运行某个主类

类加载器
完成通过一个类的全限定名来获取描述此类的二进制字节流的功能

类加载器的分类(jdk8)
Bootstrap ClassLoader 根类加载器
负责Java核心类的加载,JDK中JRE的lib目录下rt.jar

Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录

Sysetm ClassLoader 系统类加载器
负责加载自己定义的Java类

那.jar文件是啥呢?jar包中方的都是一些写好的类对应的字节码文件,相当于我们把字节码文件打包了。
如何查看其中的字节码文件呢?可以将jar包当做普通的压缩文件解压,然后查看。
所以为什么很多类我们可以直接拿来用呢,因为这些类已经在程序运行之前,利用根类加载器,把java程序运行时可能用到的核心类的字节码文件已经加载到内存中来了。

············································································································································································································

回忆一下,在之前我们如何认识类型? source code

也就是说,之前我们认识类型都是在程序运行之前
也就是说,如果之前我们没有看过某对象所属类的代码,那么在程序运行期间,对于该对象我们将“一无所知”

综上所述,我们就有一个问题了,我们能否在运行时发现和使用类型信息呢?

当然可以,今天所学习的Java反射技术,就可以帮助我们在运行时发现和使用运行时的类型信息。

其实仔细想想,所谓的反射技术,他又能如何实现运行时发现和使用类型信息的呢?仔细想想Java程序的运行过程。

问题的关键在于,Java中类型(即类)信息在运行时如何表示?

不难想到Java中的类型信息,都是由Class对象来表示的。

因此,反射技术也没有什么特别,主要就是研究Class及与它对象及其使用。

学习反射技术,你就像站在了上帝视角!没有你不能知道和使用的东西。

············································································································································································································

2. 如何获取Class对象?
通过对象
通过类的字面值常量
通过静态方法

public class Demo1 {

    public static void main(String[] args) throws ClassNotFoundException {

        //1.通过对象  Object类中的方法  getClass()
        Demo1 demo = new Demo1();
        System.out.println(demo.getClass());

        //2.通过类的字面值常量
        Class class1 = Demo1.class;
        System.out.println(class1);

        //3.Class.forName(String className)方法
        Class class2 = Class.forName("com.cskaoyan.mytest.Demo1");
        System.out.println(class2);
    }
}

Class类中定义的静态方法

static Class forName(String className)
className - 所需类的完全限定名(包名+类名)。
// 在某个类没被加载的时候,可以利用该方法,强制加载目标类(通过类名指定),返回和该类对应的Class对象
// 如果这个类已经被加载了,就会直接返回这个类对应的Class对象。

注意事项:

  1. Class.forName方法,参数必须是一个类全类名
  2. 类名.class触发的类加载过程,不完整的类加载过程(静态代码块中的代码不会执行)
    而Class.forName触发的是完整的类加载过程
  3. 开发中,多用第三种方式,来获取某个类的Class对象, 因为这种方式很灵活,该方法可以根据不同全类名,加载全类名
    第一二种如果以后类名更改的话需要改代码,而第三种只需要改参数
    三者比较:第一种必须得创建对象,第二种是不完整的类加载过程,第三种不用创建对象且是完整的类加载

············································································································································································································
第三种可以通过配置文件的方式来进行修改:
选中Network&&Reflection这个Moudle,右击-----new------Resource Bundle,可以新建一个.properties文件。
在这里插入图片描述
配置文件中的内容读取到内存中只会被当做字符串,也就是说key和value的类型一定是String类型。

package com.cskaoyan.mytest;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

/**
 * @author shihao
 * @create 2020-05-07 13:11
 */
public class Demo02 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        //1.读取配置文件中的信息
        Properties properties = new Properties();

        //2.创建指向目标文件的文件输入流
        FileInputStream fis = new FileInputStream("config.properties");

        //3.通过load方法,将配置文件中的每一条配置信息内容,读取并保存到properties的一个存储单元中
        //load方法会利用已经创建好的FileInputStream读取配置文件内容,并保存到Properties中
        properties.load(fis);

        //4.获取类名
        String className = properties.getProperty("className");
        System.out.println(className);

        //5.利用Class.forName方法加载目标类
        Class aclass = Class.forName(className);
        System.out.println(aclass);

    }
}

class LoadClass {

    static {
        System.out.println("hello class");
    }
}

这段代码运行出来后说找不到文件config.properties,后来发现是工作目录的问题,如何解决呢?
首先你要知道你的路径----点击Run----找到EditConfigurations…找到Application----在Working directory:-----------设置项目路径(一般设置成当前工作目录的路径即可,或设置成 $ M O D U L E D I R MODULE_DIR MODULEDIR $)----点击Apply—点击OK就可以运行了,出现问题一般是这个没设置好。在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-玫瑰少年-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值