day15 网络编程、UDP、TCP、服务端优化

目录

一、网络编程

1. 常用命令

2. 网络编程三要素

3. Inetaddress类

二、UDP协议

2.1 UDP发送端

2.2 UDP接收端

2.3 练习

2.4 UDP三种通讯方式

2.4.1. 单播

2.4.2. 组播

2.4.3 广播

三、TCP协议

3.1 TCP发送端

3.2 TCP接收端

3.3 TCP原理分析

3.4 TCP的三次握手

3.5 TCP 的四次挥手

3.6 练习1

3.7 练习2

四、服务端优化

4.1 循环优化

4.2 UUID优化

4.3 多线程优化

4.4 线程池优化


一、网络编程

1. 常用命令

ipconfig---查看本机ip地址

ping 域名/地址---检查指定域名网络是否连接正常,查看延迟

特殊IP地址127.0.0.1: 称为"回地址"也称"本机回环地址", 代表本机的IP地址, 测试使用

2. 网络编程三要素

网络编程三要素
    1. IP地址: 设备在网络中的地址, 是一个唯一标识
    2. 端口号: 应用程序在设备中的唯一标识
    3. 协议: 数据在网络中传输的规则, 常见协议有UDP协议和TCP协议

IP
    IP全称"互联网协议地址", 也称"IP地址", 是分配给上网设备的数字标签
    常见的IP分类为IPv4和IPv6
    
平时我们用的是域名呀, 怎么不是IP地址?
    计算机 -> 域名 -> DNS服务器 -> DNS服务器解析域名为IP -> 返回给计算机
    计算机 -> IP -> 目标服务器 -> 目标服务器返回数据 

端口

端口的注意事项:
    1. 端口号用两个字节(整数)表示, 取值范围是0-65535
    2. 其中0-1024的被一些知名的网络服务或者应用占用

    3. 我们自己使用1024-65535之间的即可
    4. 一个端口号只能被一个应用程序使用,  

协议
    计算机网络中, 连接和通信的规则被称为网络"通信协议"
    
络通信协议的分类
    1. UDP协议: 用户数据报协议, 面向无连接, 速度快一次最多发送64K, 数据不安全易丢失 
    2. TCP协议: 传输控制协议, 面向连接, 速度慢没有大小限制, 数据安全 

3. Inetaddress类

Inetaddress类作用?
    为了方便我们对IP地址的获取和操作, Inetaddress也表示Internet协议地址(IP地址)

Inetaddress没有对外提供构造方法, 方法有静态和非静态的
    对于这种类, 都会提供一个静态方法返回该类对象


常用方法
    1. static Inetaddress getByName(String host); 获取主机名的IP对象
    2. String getHostName(); 获取主机名
    3. String getHostAddress(); 获取字符串类型的IP地址

代码示例

public class MyInetAddress {
    public static void main(String[] args) throws UnknownHostException {

        InetAddress address = InetAddress.getByName("lihuan");

        System.out.println(address); //lihuan/10.254.2.93

        System.out.println("地址"+address.getHostAddress()); //地址10.254.2.93

        System.out.println("主机名"+address.getHostName()); //主机名lihuan
    }
}

二、UDP协议

2.1 UDP发送端

UDP发送步骤:
    1. 创建发送端的DatagramSocket对象
    2. 创建数组, 并将数据使用DatagramPacket打包
    3. 调用DatagramSocket的send方法发送数据
    4. 释放资源

代码示例

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //1. 创建发送端的DatagramSocket对象
        DatagramSocket ds = new DatagramSocket();

        //2.创建数据
        String message = "你好,我是小李";

        //3.将字符串转换为字节数组形式
        byte[] bytes = message.getBytes();
        InetAddress address = InetAddress.getByName("127.0.0.1");

        //4.设置端口
        int port = 10086;

        //5.将数据使用DatagramPacket打包
        //DatagramPacket(字节数组,内容长度,InetAddress对象,端口号)
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        
        //6.调用DatagramSocket的send方法发送打包好的数据
        ds.send(dp);
        //7.释放资源
        ds.close();
    }
}

2.2 UDP接收端

UDP接收数据步骤:
    1. 创建接收端的DatagramSocket对象, 指定接收数据的端口
    2. 创建一个新箱子DatagramPacket, 用于接收数据
    3. 调用DatagramSocket的receive方法接收数据, 放入新箱子中
    4. 解析数据包, 将数据展示在控制台
    5. 释放资源

代码示例

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //1.创建接收端的DatagramSocket对象,传入发送端的端口
        DatagramSocket ds = new DatagramSocket(10086);

        //2.创建数组,将数据放进数组
        byte[] bytes = new byte[1024];

        //3.创建新的包,将这个数组放进新的包
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
        System.out.println("-------正在接收数据------");

        //4.接收数据报
        ds.receive(dp);

        System.out.println("-------已接收到数据------");
        //String s= new String(bytes, 0, dp.getLength());

        //5.解析数据,并将数据打印在控制台
        System.out.println(new String(bytes, 0, dp.getLength()));

        //6.释放资源
        ds.close();
    }
}

2.3 练习

需求:
UDP发送端发送键盘录入的数据, 直到输入"886", 发送数据结束
UDP接收端由于不知道什么时候发送停止, 使用死循环一直接收

1. 发送端 

代码示例

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //创建接收端的DatagramSocket对象
        DatagramSocket ds = new DatagramSocket();
        System.out.println("请输入要发送的内容");
        Scanner sc= new Scanner(System.in);
        while (true) {
            String message = sc.nextLine();
            if(message.equals("888")) {
                break;
            }
                //将输入的内容转换为字节数组
                byte[] bytes = message.getBytes();
                InetAddress address = InetAddress.getByName("127.0.0.1");

                int port = 10086;

                //创建DatagramPacket对象,将数组放入包中
                //DatagramPacket(字节数组,内容长度,InetAddress对象,端口号)
                DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
                //发送数据
                ds.send(dp);
            }
        }
    }

2. 接收端

代码示例

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

        //创建DatagramSocket接收端对象,将端口放进去
        DatagramSocket ds = new DatagramSocket(10086);

        //要持续接收数据,所以使用死循环
        while (true) {

            //创建新的包,放入数据
            byte[] bytes = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

            System.out.println("-------正在接收数据------");

            //接收数据
            ds.receive(dp);

            System.out.println("-------已接收到数据------");

            //String s= new String(bytes, 0, dp.getLength());

            //解析数据
            System.out.println(new String(bytes, 0, dp.getLength()));
        }
    }
}

2.4 UDP三种通讯方式


    1. 单播: 一个发送端 -> 路由器 -> 一个接收端
    2. 组播: IPv4的概念, 在IPv6中称为"多播", 一个发送端 -> 路由器 -> 一组接收端
    3. 广播: 一个发送端 -> 路由器 -> 所有接收端

2.4.1. 单播

以前写的都是单播方式,只有一个接收端

2.4.2. 组播

UDP的组播
    发送端: 需要指定组播地址, 而不是端口号
        组播地址: 224.0.0.0-239.255.255.255
        预留地址: 224.0.0.0-224.0.0.255(计算机用了我们不能用)从224.0.1.0开始朝后       
    接收端: 有一组计算机, 指定IP

1. 组播发送端

组播发送端步骤
        1. 创建组播发送端的DatagramSocket对象
        2. 创建数组, 并将数据使用DatagramPacket打包, 这次InetAddress对象为组播地址
        3. 调用DatagramSocket的send方法发送数据
        4. 释放资源

代码示例

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

        //1.创建组播发送端的DatagramSocket对象
        DatagramSocket ds = new DatagramSocket();

        //2.创建字节数组,并将数据使用DatagramPacket打包,InetAddress对象为组播地址
        InetAddress address = InetAddress.getByName("224.0.1.0");
        int port = 10086;
        byte[] bytes ="组播发送".getBytes();

        //3.调用DatagramSocket的send方法发送数据
        DatagramPacket dp = new DatagramPacket(bytes,0,bytes.length,address,port);

        //4.调用DatagramSocket的send方法发送数据
        ds.send(dp);
        ds.close();

    }
}

2. 组播接收端

组播接收端步骤
        1. 创建组播接收端的MulticastSocket对象, 指定接收数据的端口
        2. 创建一个新箱子DatagramPacket, 用于接收数据
        3. 使用MulticastSocket的joinGroup方法, 指定组播地址
        4. 调用MulticastSocket的receive方法接收数据, 放入新箱子中
        5. 解析数据包, 将数据展示在控制台
        6. 释放资源

代码示例

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

        //1.创建组播接收端的MulticastSocket对象,指定接收端的端口
        MulticastSocket ms = new MulticastSocket(10086);
        
        //2.创建新的包DatagramPacket,接收数据
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
        
        //3.使用MulticastSocket的joinGroup方法,指定组播地址
        ms.joinGroup(InetAddress.getByName("224.0.1.0"));

        //4.解析数据包,打印数据
        ms.receive(dp);
        System.out.println(new String(bytes,0,dp.getLength()));
        
        //5.释放资源
        ms.close();
    }
}

2.4.3 广播

UDP的广播
广播地址255.255.255.255

1. 发送端

广播发送端步骤
        1. 创建广播发送端的DatagramSocket对象
        2. 创建数据, 并将数据使用DatagramPacket打包, InetAddress对象为广播地址
        3. 调用DatagramSocket的sent方法发送数据
        4. 释放资源

代码示例

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

        //1.创建广播发送端的DatagramSocket对象
        DatagramSocket ds = new DatagramSocket();

        //2.创建数组,并将数据使用DatagramPacket打包,InetAddress对象为广播地址
        byte[] bytes = "广播发送".getBytes();
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getByAddress("255.255.255.255"),10086);

        //3.调用DatagramSocket的send方法发送数据
        ds.send(dp);

        //4.释放资源
        ds.close();
    }
}

2. 接收端

广播接收端步骤
        1. 创建接收端的DatagramSocket对象, 指定接收数据的端口
        2. 创建一个新箱子DatagramPacket, 用于接收数据
        3. 调用DatagramSocket的receive方法接收数据, 放入新箱子中
        4. 解析数据包, 将数据展示在控制台
        5. 释放资源

代码示例

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

        //1.创建接收端的DatagramSocket对象, 指定接收数据的端口
        DatagramSocket ds= new DatagramSocket(10086);

        //2.创建一个新的DatagramPacket,接收数据
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

        //3.调用DatagramSocket的receive方法接收数据,并放入新的包
        ds.receive(dp);

        //4.解析数据包,并打印
        System.out.println(new String(bytes,0,dp.getLength()));

        //5.释放资源
        ds.close();
    }
}

三、TCP协议

TCP通信原理
    TCP通信协议是一种可靠的网络协议, 它在通信的两端各建立一个Socket对象
    通信之前要保证连接已经建立才可以, 否则报错
    通过Socket产生IO流来进行网络通信

3.1 TCP发送端

TCP发送端(客户端)步骤
1.创建一个客户端对象Socket ,构造方法绑定服务器的IP地址和端口号

2.使用Socket 对象中的方法getOutputStream()获取网络字节输出流Outputstream 对象

3.使用网络字节输出流Outputstream 对象中的方法write() ,给服务器发送数据

4.使用Socket 对象中的方法getInputStream()获取网络字节输入流Inputstream)对象

5.使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据

6.释放资源Socket

注意:

1.客户端和服务器端进行交互,必须使用Socket 中提供的网络流,不能使用自己创建的流对象

2.当我们创建客户端对象Socket 的时候,就会去请求服务器和服务器经过3次握手建立连接通路 这时如果服务器没有启动,那么就会抛出异常 如果服务器已经启动,那么就可以进行交互了

代码示例

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

        //1.创建服务端Socket对象,指定端口
        Socket socket =new Socket("127.0.0.1",10086);//给定目标主机和端口

        //2.获取输出流,写对象
        //通过socket对象获取输出流,写数据
        OutputStream os = socket.getOutputStream();

        //3.获取输入流,读取数据
        os.write("hello".getBytes());

        //4.释放资源
        socket.close();
        os.close();

    }
}

3.2 TCP接收端

服务器端必明确一件事情,必的知道是哪个客端请求的服务器 所以可以使用accept 方法获取到请求的客端对象Socket 成员方法 : Socket accept ()听并接受到此套接字的连接。

TCP接收端(服务器)步骤
1.创建服务器ServerSocket 对象和系统要指定的端口号

2.使用ServerSocket)对象中的方法accept ,获取到请求的客端对象Socket

3.使用Socket对象中的方法getInputStream ()获取网络字节输入流InputStream 对象

4.使用网络字节输入流InputStream 对象中的方法read,读取客户端发送的数据

5.使用对象中的方法getOutputStream ()获取网络节输出流OutputStream 对象

6.使用网络字节输出流OutputStream 对象中的方法write 给客端回写数据

7.释放资源(Socket ,ServerSocket )

代码示例

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

        //1.创建服务端ServerSocket对象,指定端口
        //通过ServerSocket对象,监听是否有客户连接
        ServerSocket ss = new ServerSocket(10086);

        //2.等待客户连接
        //通过accept对象获取输入流,读数据
        Socket accept =ss.accept();//accept是你阻塞的,如果等不到连接,会一直等

        //3.获取输入流,读取数据
        InputStream is = accept.getInputStream();
        int b;
        while ((b=is.read())!=-1) {
            System.out.print((char)b);

        }

        //4.释放资源
        ss.close();
        is.close();
        accept.close();
    }

}

3.3 TCP原理分析

TCP通信原理分析及注意
    1. accept方法是阻塞的, 作用就是等待客户端连接
    2. 客户端创建对象并连接服务器, 此时是通过"三次握手"保证跟服务器之间的连接
    3. 针对客户端讲是往外写(输出流), 针对服务器是往里读(输入流)
    4. read方法也是阻塞的
    5. 客户端在关流时, 会写入一个结束标记作为终止, 服务器读到结束标记才会结束读的动作
    6. 客户端最后断开连接时, 通过"四次挥手"来保证连接终止

3.4 TCP的三次握手

TCP三次握手: 应用于客户端创建对象并连接服务器时
    第一次: 客户端服务器发出请求, 等待服务器确认
    第二次: 服务器客户端反馈响应, 告知客户端收到了请求
    第三次: 客户端服务器再次发出确认信息, 建立连接

3.5 TCP 的四次挥手

TCP四次挥手: 客户端最后断开连接时
    第一次: 客户端服务器发出取消连接的请求
    第二次: 服务器客户端反馈响应, 告知客户端收到了取消连接的请求
    第三次: 服务器客户端再次发出确认取消连接的请求信息 (已经存在了连接, 做数据最后的处理)
    第四次: 客户端服务器再次发出确认取消信息, 取消连接

3.6 练习1

练习1需求:
    客户端发送数据, 接收服务器反馈
    服务器接收数据, 给出反馈

1. 发送端

代码示例

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

        //1.创建客户端Socket对象,将ip地址和端口传入
        Socket socket = new Socket("127.0.0.1",10086);

        //2.通过Socket对象获取字节输出流,发送数据
        OutputStream os = socket.getOutputStream();

        //3.将字符串转换为字节数组,写入输出流对象
        os.write("hello".getBytes());
        /*String s= "hello";
        byte[] bytes = s.getBytes();
        System.out.println(Arrays.toString(bytes));
        [104, 101, 108, 108, 111]*/

        //关闭输出流, 写一个结束标记, 对Socket无影响
        socket.shutdownOutput();

        //字符流读取反馈数据
        BufferedReader br =new BufferedReader(new InputStreamReader(socket.getInputStream()));

        String line;
        while ((line = br.readLine())!=null){
            System.out.println(line);
        }
        socket.close();
        os.close();
        br.close();
    }
}

2. 接收端

    ServerSocket的accept()方法从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,并将它返回。
    如果队列中没有连接请求,accept()方法就会一直等待,直到接收到了连接请求才返回。
接下来,服务器从Socket对象中获得输入流和输出流,就能与客户交换数据。
    当服务器正在进行发送数据的操作时,如果客户端断开了连接,那么服务器端会抛出一个IOException的子类SocketException异常
代码示例

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

        //1.创建服务器端ServerSocket对象,将端口传入
        ServerSocket ss = new ServerSocket(10086);
        
        //2.等待连接
        Socket accept = ss.accept();

        //3.获取输入流对象,读取数据
        InputStream is = accept.getInputStream();
        int b;
        while ((b = is.read())!=-1){
            System.out.print((char)b);
        }
        //4.字符流写反馈数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));

        bw.write("你谁啊");
        bw.newLine();
        bw.flush();

        //5.释放资源
        is.close();
        bw.close();
        accept.close();
        ss.close();
    }

}

3.7 练习2

练习2需求:
    客户端将本地文件上传到服务器, 接收服务器的反馈
    服务器接收客户端上传的文件, 上传完毕后给出反馈

1. 客户端

代码示例

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        
        //1.创建客户端Socket对象,指定地址和端口
        Socket socket  =new Socket("127.0.0.1",10086);

        //2.创建输入流,读取本地文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("day15\\1.png"));

        //3.网络中的输出流,将数据写到服务器
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        int b;
        while ((b=bis.read())!=-1){
            bos.write(b);
        }
        
        //4.给服务器一个结束标记,表示文件已经传输完毕
        socket.shutdownOutput();

        //5.网络中的输入流,接收反馈并打印
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }

        //6.释放资源
        socket.close();
        bis.close();
    }
}

2. 服务端

代码示例

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //1.创建服务端ServerSocket对象
        ServerSocket ss = new ServerSocket(10086);

        //2.监听连接
        Socket accept = ss.accept();

        //3.网络中的输入流,从客户端读取数据
        BufferedInputStream bis  =new BufferedInputStream(accept.getInputStream());

        //4.本地的输出流,将接收的数据写到本地
        BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("day15\\copy.jpg"));
        int b;
        while ((b=bis.read())!=-1){
            bos.write(b);
        }
        
        //5.网络中的输出流,给出反馈
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("上传成功");
        bw.newLine();
        bw.flush();
        
        //6.释放资源
        ss.close();
        accept.close();
        bos.close();
    }
}

四、服务端优化

4.1 循环优化

循环优化案例: 服务器一直接收数据, 客户端可以多次上传

1. 客户端没有变化

2. 服务端

服务端接收数据加入while循环

代码示例

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

        ServerSocket ss = new ServerSocket(10086);

        while (true) {
            Socket accept = ss.accept();

            BufferedInputStream bis  =new BufferedInputStream(accept.getInputStream());

            BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("day15\\copy.jpg"));

            int b;
            while ((b=bis.read())!=-1){

                bos.write(b);
            }
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));

            bw.write("上传成功");

            bw.newLine();
            bw.flush();

            accept.close();
            bos.close();
        }
        //ss.close();
    }
}

4.2 UUID优化

UUID优化案例: 在服务端的文件名,中加入随机数,可以使多次运行代码后产生的文件不会被覆盖

UUID是Java提供的类, 可以生成一个随机且唯一的值 (可能重复,但几率太小, 可以忽略不计)
    1. public static UUID randomUUID(); 生成一个随机值
    2. toString(); 返回随机值的字符串表现形式

1. 客户端没有变化

2. 接收端

代码示例

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10086);

        while (true) {
            Socket accept = ss.accept();

            BufferedInputStream bis  =new BufferedInputStream(accept.getInputStream());

            BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("day15\\"+ UUID.randomUUID().toString().replace("-","") +".jpg"));

            int b;
            while ((b=bis.read())!=-1){

                bos.write(b);
            }
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));

            bw.write("上传成功");

            bw.newLine();
            bw.flush();

            accept.close();
            bos.close();
        }
    }
    //ss.close();
}

4.3 多线程优化

多线程优化案例: 通过多线程实现多个客户端同时上传文件

1. 客户端没有变化

2. 服务端

代码示例

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

        //1.创建客户端ServerSocket对象,传入端口
        ServerSocket ss = new ServerSocket(10086);

        //2.循环接收数据
        while (true) {
            Socket accept = ss.accept();

            //3.创建线程类
            ThreadSocket ts = new ThreadSocket(accept);

            //4.开启线程,
            new Thread(ts).start();
        }
    }
}

3. 创建Runable 的实现类ThreadSocket

代码示例

public class ThreadSocket implements Runnable {
    private Socket accept;

    public ThreadSocket(Socket accept) {
        this.accept = accept;
    }

    @Override
    public void run() {
        BufferedOutputStream bos = null;
        try {
            BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());

            bos = new BufferedOutputStream(new FileOutputStream("day15\\" + UUID.randomUUID().toString().replace("-", "") + ".jpg"));

            int b;
            while ((b = bis.read()) != -1) {

                bos.write(b);
            }
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));

            bw.write("上传成功");

            bw.newLine();
            bw.flush();
            accept.close();
            bos.close();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    accept.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (accept != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.4 线程池优化

线程池优化案例: 

1. 客户端没有变化

2. Runable 的实现类ThreadSocket没有变化

3. 服务端

代码示例

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10086);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,
                10,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            pool.submit(ts);
        }
    }
}

你可能感兴趣的:(Java进阶,网络,tcp/ip,udp)