JavaDay7

JavaDay7

网络编程

网络通信三要素为:ip地址

​ 端口号

​ 协议

而网络编程主要应用为TCP协议编程和UDP协议编程

无论是TCP或是UDP,每个端都要创建对应的socket对象

UDP协议

  • 发送端
/*

	1:建立udp的socket服务
        UDP协议发送端的Socket类叫做:DatagramSocket
    2:将要发送的数据封装成数据包
    3:通过udp的socket服务,将数据包发送出
    4:关闭资源

*/

public class SendDemo {
    public static void main(String[] args) throws Exception{
        // DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口。
        DatagramSocket ds = new DatagramSocket();

        //将要发送的数据封装成数据包 DatagramPacket
        //DatagramPacket(byte[] buf, int length, InetAddress address, int port)
        //构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
        byte[] bytes = "欢迎观看!".getBytes(); // 发送数据的字节数组形式
        int length = bytes.length; // 数据内容字节数组的长度
        InetAddress address = InetAddress.getByName("xxx.xxx.xxx.xxx"); // 目标ip地址的InetAddress对象
        int port = 10086;
        DatagramPacket datagramPacket = new DatagramPacket(bytes, length, address, port);

        //3:通过udp的socket服务,将数据包发送出
        //DatagramPacket
        ds.send(datagramPacket);

        // 关闭资源
        ds.close();
    }
}
  • 接收端
/*
    1:建立udp的socket服务.
    2:通过receive方法接收数据
    3:将收到的数据存储到数据包对象中
    4:通过数据包对象的功能来完成对接收到数据进行解析.
    5:可以对资源进行关闭
    
*/

public class ReceiveDemo {
    public static void main(String[] args) throws Exception{
        //DatagramSocket(int port) 构造数据报套接字并将其绑定到本地主机上的指定端口。
        DatagramSocket ds = new DatagramSocket(10086);

        //创建一个空的数据包
        //DatagramPacket(byte[] buf, int length) 构造一个 DatagramPacket用于接收长度的数据包 length 。指定包的大小和长度
        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);


        //void receive(DatagramPacket p)
        ds.receive(datagramPacket); // 发生阻塞,直到有数据过来被接收

        //接收到的数据在数据包中,需要解析数据包中的数据
        byte[] data = datagramPacket.getData();
        //获取实际接收到的数据长度
        int length = datagramPacket.getLength();
        String info = new String(data, 0, length);

        InetAddress address = datagramPacket.getAddress();
        String ip = address.getHostAddress();
        String hostName = address.getHostName();
        System.out.println("主机名:"+hostName+" ip地址:"+ip+"发来一条消息:");
        System.out.println(info);


        //关闭资源
        ds.close();

    }
}

以上为一次性发送和一次性接收信息

可改进为一直手动发送信息和一直接收信息

//发送端:


public class SendDemo {
    public static void main(String[] args) throws Exception{
        // DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口。
        DatagramSocket ds = new DatagramSocket();
        //创建键盘录入对象
        Scanner sc = new Scanner(System.in);

        while (true){
            System.out.println("请输入要发送的内容:");
            String info = sc.next();
            if("exit".equals(info)){
                System.out.println("----------------------");
                System.out.println("发送端已关闭........");
                break;
            }

            //将要发送的数据封装成数据包 DatagramPacket
            //DatagramPacket(byte[] buf, int length, InetAddress address, int port)
            //构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
            byte[] bytes = info.getBytes(); // 发送数据的字节数组形式
            int length = bytes.length; // 数据内容字节数组的长度
            InetAddress address = InetAddress.getByName("xxx.xxx.xxx.xxx"); // 目标ip地址的InetAddress对象
            int port = 10086;
            DatagramPacket datagramPacket = new DatagramPacket(bytes, length, address, port);

            //3:通过udp的socket服务,将数据包发送出
            //DatagramPacket
            ds.send(datagramPacket);

        }

        // 关闭资源
        ds.close();
    }
}


//接收端:


public class ReceiveDemo {
    public static void main(String[] args) throws Exception{
        //DatagramSocket(int port) 构造数据报套接字并将其绑定到本地主机上的指定端口。
        DatagramSocket ds = new DatagramSocket(10086);

        //创建一个空的数据包
        //DatagramPacket(byte[] buf, int length) 构造一个 DatagramPacket用于接收长度的数据包 length 。指定包的大小和长度
        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);

        while (true){
            //void receive(DatagramPacket p)
            ds.receive(datagramPacket); // 发生阻塞,直到有数据过来被接收

            //接收到的数据在数据包中,需要解析数据包中的数据
            byte[] data = datagramPacket.getData();
            //获取实际接收到的数据长度
            int length = datagramPacket.getLength();
            String info = new String(data, 0, length);

            InetAddress address = datagramPacket.getAddress();
            String ip = address.getHostAddress();
            String hostName = address.getHostName();
            System.out.println("主机名:"+hostName+" ip地址:"+ip+"发来一条消息:");
            System.out.println(info);
            System.out.println();
        }

        //关闭资源
        ds.close();

    }
}

需要注意的是:

UDP协议,发送的数据的时候,虽然发送端将数据封装在了数据包中,但是实际发送过程中是将数据解析出来进行传输,所以到达接收的端的时候,需要接收端自己创建一个空的数据包进行接收数据,接收了之后,再对数据包中的数据进行解析

TCP协议

  • 客户端(传输端):
/*

        1:建立客户端的Socket服务,并明确要连接的服务器。
        2:如果连接建立成功,就表明,已经建立了数据传输的通道.就可以在该通道通过IO进行数据的读取和写入.该通道称为Socket流,Socket流中既有读取流,也有写入流.
        3:通过Socket对象的方法,可以获取这两个流
        4:通过流的对象可以对数据进行传输
        5:如果传输数据完毕,关闭资源

 */

public class ClientDemo {
    public static void main(String[] args) throws Exception{
        //创建客户端Socket对象,需要指定连接的服务器地址和端口号
        //Socket(String host, int port)
        //创建流套接字并将其连接到指定主机上的指定端口号
        Socket socket = new Socket("xxx.xxx.xxx.xxx", 12345);

        //获取通道中的输出流对象
        //返回值类型是OutputStream,本质上是字节输出流
        OutputStream os = socket.getOutputStream();
        os.write("欢迎观看!".getBytes());
        os.flush(); // 刷
        
        //如果传输数据完毕,关闭资源
        socket.close();
    }
}
  • 服务端
/*
    1:建立服务器端的socket服务,需要一个端口
    2:服务端没有直接流的操作,而是通过accept方法获取客户端对象,在通过获取到的客户端对象的流和客户端进行通信
    3:通过客户端的获取流对象的方法,读取数据或者写入数据
    4:如果服务完成,需要关闭客户端,然后关闭服务器,但是,一般会关闭客户端,不会关闭服务器,因为服务端是一直提供服务的

 */


public class ServerDemo {
    public static void main(String[] args) throws Exception {
        //建立服务器端的socket服务,需要一个端口
        ServerSocket ss = new ServerSocket(12345);

        //调用方法监听客户端的连接
        Socket s = ss.accept(); // 服务器运行到这一步的时候,发送阻塞,等待一个客户端的连接


        //获取通道中的输入流对象,获取客户端发送的数据
        InputStream inputStream = s.getInputStream();
        byte[] bytes = new byte[1024];
        int length = inputStream.read(bytes);
        String info = new String(bytes, 0, length);
        System.out.println("服务器端收到信息: " + info);

        // 关闭与客户端的连接
        s.close();

        // 释放资源,一般不关,因为服务器除特殊情况外一般不会关闭
        if(xxxxxx){
            ss.close();
        }
        ss.close();
    }
}

注意:

​ 目前的第一版tcp协议编程有些缺点:

​ 1、服务器端没有给客户端一个反馈
​ 2、改进成一个手动输入内容的版本
​ 3、就目前代码而言,服务器端只监听一次,也就是说只允许一个客户端进行连接
解决方案:服务端使用多线程的方式改进

  • 服务器端没有给客户端一个反馈【服务端修改】
//解决如下:

public class ServerDemo {
    public static void main(String[] args) throws Exception {
        //建立服务器端的socket服务,需要一个端口
        ServerSocket ss = new ServerSocket(12345);

        //调用方法监听客户端的连接
        Socket s = ss.accept(); // 服务器运行到这一步的时候,发送阻塞,等待一个客户端的连接
        //获取通道中的输入流对象,获取客户端发送的数据
        InputStream inputStream = s.getInputStream();
        //获取通道中的输出流对象,给客户端写反馈
        OutputStream outputStream = s.getOutputStream();

        //在服务器端获取客户端的信息
        InetAddress inetAddress = s.getInetAddress();
        String hostName = inetAddress.getHostName();
        String ip = inetAddress.getHostAddress();

        try {
            while (true){

                byte[] bytes = new byte[1024];
                int length = inputStream.read(bytes);
                String time = DateUtil.getTime(System.currentTimeMillis());

                String info = new String(bytes, 0, length);
                System.out.println(time);
                System.out.println("ip地址为: "+ip+"发来一条消息:");
                System.out.println(info);
                System.out.println();

                outputStream.write("服务器已收到!".getBytes());
                outputStream.flush();

            }
        }catch (Exception e){
            System.out.println("-----------客户端已下线.....------------");
        }
        
        // 关闭与客户端的连接
        s.close();
        
        // 释放资源,一般不关,因为服务器除特殊情况外一般不会关闭
        if(xxxxxx){
            ss.close();
        }
        ss.close();
    }
}
  • 改进成一个手动输入内容的版本【客户端修改】
//解决如下:

public class ClientDemo {
    public static void main(String[] args) throws Exception{
        //创建客户端Socket对象,需要指定连接的服务器地址和端口号
        //Socket(String host, int port)
        //创建流套接字并将其连接到指定主机上的指定端口号
        Socket socket = new Socket("192.168.1.20", 12345);
        Scanner sc = new Scanner(System.in);
        //获取通道中的输出流对象
        //返回值类型是OutputStream,本质上是字节输出流
        OutputStream os = socket.getOutputStream();
        //将通道中的字节输出流包装成字符缓冲输出流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));

        //获取通道中的输入流对象
        //返回值类型是InputStream,本质上是字节输入流
        InputStream is = socket.getInputStream();

        while (true){
            System.out.println("请输入要发送的内容:");
            String info = sc.next();
            if("exit".equals(info)){
                break;
            }

            bw.write(info);
            bw.flush();

            byte[] bytes = new byte[1024];
            int length = is.read(bytes);
            String res = new String(bytes, 0, length);
            System.out.println("服务器反馈:"+res);
            System.out.println();
        }

        is.close();
        bw.close();
        //如果传输数据完毕,关闭资源
        socket.close();
    }
}
  • 就目前代码而言,服务器端只监听一次,也就是说只允许一个客户端进行连接【服务端修改,加入线程】
//线程建立
//重写run()方法
//解决如下:



public class ClientThread extends Thread{

    private Socket s;

    public ClientThread(Socket socket) {
        this.s = socket;
    }

    @Override
    public void run() {
        //在服务器端获取客户端的信息
        InetAddress inetAddress = s.getInetAddress();
        String hostName = inetAddress.getHostName();
        String ip = inetAddress.getHostAddress();
        System.out.println("用户: "+hostName+"已上线!!");
        try {
            //获取通道中的输入流对象,获取客户端发送的数据
            InputStream inputStream = s.getInputStream();
            //获取通道中的输出流对象,给客户端写反馈
            OutputStream outputStream = s.getOutputStream();


            while (true){

                byte[] bytes = new byte[1024];
                int length = inputStream.read(bytes);
                String time = DateUtil.getTime(System.currentTimeMillis());

                String info = new String(bytes, 0, length);
                System.out.println(time);
                System.out.println(hostName+": "+info);
                System.out.println();

                outputStream.write("服务器已收到!".getBytes());
                outputStream.flush();

            }
        }catch (Exception e){
            System.out.println("------------------------------");
            System.out.println("用户: "+hostName+"已下线.....");
            System.out.println("------------------------------");
        }
    }
}
//重新编写服务端程序:

public class ServerDemo1 {
    public static void main(String[] args) throws Exception {
        //建立服务器端的socket服务,需要一个端口
        ServerSocket ss = new ServerSocket(12345);

        while (true){
            //使用while true的方式将监听进行包装,这样一直监听,可以有多个客户端进行连接
            //调用方法监听客户端的连接
            Socket s = ss.accept(); // 服务器运行到这一步的时候,发送阻塞,等待一个客户端的连接
            
            //创建用户线程对象
            ClientThread clientThread = new ClientThread(s);
            clientThread.start();
        }
        
        // 关闭与客户端的连接
        s.close();

        // 释放资源,一般不关,因为服务器除特殊情况外一般不会关闭
        if(xxxxxx){
            ss.close();
        }
        ss.close();
    }
}

注意:

​ tcp协议中,客户端不能先启动,要先启动服务器端,让服务器端绑定一个端口号,然后再让客户端做连接。

类加载器和反射

类加载器

反射的前奏,首先学习如何获取一个类的Class对象

我们首先创建一个类Student

public class Student {
    private int id;
    protected String name;
    int age;
    public String address;

    private Student(){

    }

    private Student(String name){
        this.name = name;
    }


    public Student(int id){
        this.id = id;
    }

    protected Student(int id, String name){
        this.id = id;
        this.name = name;
    }


    public String fun1(){
        System.out.println("这是无参公共的方法fun1");
        return "hello world";
    }

    public void fun2(String s){
        System.out.println("这是有参公共的方法fun2");
    }

    private void fun3(){
        System.out.println("这是私有的方法fun3");
    }

    private void fun4(String s){
        System.out.println("这是私有的方法fun4");
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}

我们可以看见该类中并没有setter和getter

且存在两个成员变量id、name和两个方法fun3()、fun4()是私有的无法直接获取

那么我们该如何获取其中的元素呢?

获取Student类的Class对象

public class ClassDemo1 {
    public static void main(String[] args) throws Exception{
        //方式1:在已经有该类的对象前提下获取 getClass();
        Student s1 = new Student(1001);
        Class<? extends Student> c1 = s1.getClass();

        Student s2 = new Student(1002);
        Class<? extends Student> c2 = s2.getClass();
        System.out.println(s1==s2); // 不同的对象地址值不一样
        System.out.println(c1==c2); // 同一个类的Class对象在内存中只有一个

        System.out.println("------------------------------------");
        //方式2:直接通过类名.class获取
        Class<Student> c3 = Student.class;
        System.out.println(c1==c3); // 同一个类的Class对象在内存中只有一个
        System.out.println(c2==c3); // 同一个类的Class对象在内存中只有一个

        System.out.println("-------------------------------------");
        //方式3:通过Class类中的静态方法forName()
        Class<?> c4 = Class.forName("shujia.day18.Student");
        System.out.println(c1==c4);
        System.out.println(c2==c4);
        //对于这个案例来说,都是获取Student类class文件在内存中的Class对象
    }
}

在了解完如何获取类中Class对象,我们仍不能对Student类中的成员变量和方法进行赋值和调用

那么接下来可以使用反射来进行赋值和调用

反射

通过反射获取Student中的构造方法并使用

public class ClassDemo2 {
    public static void main(String[] args) throws Exception{
        Class<?> studentClass = Class.forName("Student");

        //获取公共public的构造方法
        //Constructor<T> getConstructor(Class<?>... parameterTypes)
        //获取某一个构造方法,根据参数列表来获取
        //获取一个参数列表为一个int类型的构造方法
        Constructor<?> c1 = studentClass.getConstructor(int.class);
        System.out.println("c1: "+c1);

        //获取一个无参的构造方法
        Constructor<?> c2 = studentClass.getConstructor(int.class, String.class);
        System.out.println("c2: "+c2);
        Constructor<?> c2 = studentClass.getDeclaredConstructor();
        System.out.println("c2: "+c2);
        Constructor<?> c3 = studentClass.getDeclaredConstructor(int.class, String.class);
        System.out.println("c3: "+c3);
        Constructor<?> c4 = studentClass.getDeclaredConstructor(int.class);
        System.out.println("c4: "+c4);
        
        //获取任意修饰符的构造方法
        //public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
        //Constructor类中有一个方法可以根据一个构造方法,创建对应类的对象
        Object o1 = c4.newInstance(1001);
        System.out.println(o1);

        c2.setAccessible(true); // 暴力访问
        Object o2 = c2.newInstance();
        System.out.println(o2);

        //获取类中所有的公共的public修饰的构造方法
        Constructor<?>[] constructors = studentClass.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
        //获取类中所有的构造方法
        Constructor<?>[] declaredConstructors = studentClass.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
    }
}

通过反射获取Student类中的成员变量并使用

public class ClassDemo3 {
    public static void main(String[] args) throws Exception {
        Class<?> studentClass = Class.forName("Student");

        //public Field getField(String name) 只能获取公共的public修饰的成员变量
        // 根据成员变量的名字获取对应的成员变量的对象
        Field id = studentClass.getField("id");
        System.out.println("id: " + id);

        //public Field getDeclaredField(String name) 获取任意修饰符修饰的成员变量
        Field id = studentClass.getDeclaredField("id");
        System.out.println("id: "+id);
        Field name = studentClass.getDeclaredField("name");
        System.out.println("name: "+name);
        Field age = studentClass.getDeclaredField("age");
        System.out.println("age: "+age);
        Field address = studentClass.getDeclaredField("address");
        System.out.println("address: "+address);

        //如何使用成员变量
        //之前的思想:给某一个对象中的某一个成员变量进行赋值
        //反射的思想:哪一个对象的成员变量要赋什么值
        //在使用成员变量之前,先知道给哪一个对象的该成员变量赋值
        Constructor<?> c1 = studentClass.getDeclaredConstructor();
        c1.setAccessible(true);
        Student s = (Student) c1.newInstance();
        System.out.println("s: "+s);
        
        //给学生对象s中的address成员变量进行赋值
        address.set(s, "安徽合肥");
        System.out.println("s: "+s);
        id.setAccessible(true);
        id.set(s, 1001);
        System.out.println("s: "+s);
        name.setAccessible(true);
        name.set(s,"张三");
        System.out.println("s: "+s);
        age.setAccessible(true);
        age.set(s, 18);
        System.out.println("s: "+s);

        Field[] fields = studentClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }

        Field[] declaredFields = studentClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }
    }
}

通过反射获取Student类中的成员方法并使用

public class ClassDemo4 {
    public static void main(String[] args) throws Exception {
        Class<?> studentClass = Class.forName("Student");

        //public Method getMethod(String name, Class<?>... parameterTypes) 获取某一个公共的成员方法
        // 获取一个无参的方法,方法名叫做fun1
        Method fun1 = studentClass.getMethod("fun1");
        System.out.println("fun1: " + fun1 );
        // 获取一个有一个参数为String类型的方法,方法名叫做fun2
        Method fun2 = studentClass.getMethod("fun2", String.class);
        System.out.println("fun2: " + fun2);
        Method fun3 = studentClass.getMethod("fun3");
        System.out.println("fun3: " + fun3);

        //public Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取任意权限修饰符的成员方法
        Method fun3 = studentClass.getDeclaredMethod("fun3");
        System.out.println("fun3: " + fun3);

        //根据参数的类型和方法名获取对应的方法,将其封装成Method对象进行返回
        Method fun4 = studentClass.getDeclaredMethod("fun4", String.class);
        System.out.println("fun4: " + fun4);


        //如何使用获取到的成员方法
        //之前写代码的思想:对象是固定的,调用对象中的某一个方法
        //反射中的思想:方法是固定的,是哪一个对象的该方法
        Constructor<?> c1 = studentClass.getDeclaredConstructor();
        c1.setAccessible(true);
        Student s = (Student) c1.newInstance();
        // 调用对象s中的fun1方法
        //Method类中有一个方法invoke'
        //public Object invoke(Object obj, Object... args)
        Object o = fun1.invoke(s);
        System.out.println(o);

        //调用对象s中的fun4方法
        fun4.setAccessible(true);
        fun4.invoke(s, "shujia");

        //getMethods 获取自己本类或父类所有的public修饰的方法
        Method[] methods = studentClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }

        //getDeclaredMethods 只能获取本类中的所有方法,父亲的不管
        Method[] declaredMethods = studentClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println(method);
        }
    }
}

个人认为对于一个已知构造类,频繁利用反射进行对象的创建的话,代码量十分繁杂并且操作麻烦。并且,拿成员变量举例,在反射出成员变量之后,同样要对新建的对象进行类的转型。所以对于已知构造类,我们可以利用new一个对象对非私有成员变量和成员方法进行操作,而对于那些私有且无set、get方法的成员变量和成员方法,则利用反射进行操作

如下展示:

  • 首先我们还是创建一个Student类,但不同的是类中存在部分成员变量有Set和Get方法:
public class Student {
    private int id;
    protected String name;
    String gender;
    int age;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;

    }

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 而后便可以利用new对象和反射结合来对对象进行操作
public class Fanshe {
    public static void main(String[] args){
//        try{
//            Class<?> c1 = Class.forName("HomeWork.Jan17.Student");
//
//            Constructor<?> constructor = c1.getConstructor(int.class,String.class);
//            constructor.setAccessible(true);
//            System.out.println(constructor);
//        }catch (Exception e){
//            e.printStackTrace();
//        }
		//new对象
        Student student = new Student();
        student.setAge(16);
        student.setGender("男");
        student.setName("查镕贤");
        
        //反射
        try{
            Class<?> c2 = Class.forName("HomeWork.Jan17.Student");
            Field id = c2.getDeclaredField("id");
            id.setAccessible(true);
            id.set(student,13);
            System.out.println(student);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

来源链接:https://www.cnblogs.com/Roxan-bd/p/18677443

请登录后发表评论

    没有回复内容