当前位置: 首页 > news >正文

15 网络编程:三要素(IP地址、端口、协议)、UDP通信实现和TCP通信实现 (黑马Java视频笔记)

在这里插入图片描述

文章目录

      • 1. 概述
        • 基本的通信架构 >>> 都依赖网络编程
      • 2. 网络编程的三要素:IP、端口、协议
        • 2.1 IP地址
          • 1)IPV4
          • 2)IPV6
          • 3)IP域名(Domain Name) --- DNS域名解析器(Domain Name System)
          • 4)公网IP、内网IP
          • 5)InetAddress
        • 2.2 端口
        • 2.3 网络通信协议
      • 3. ⭐传输层的2个通信协议
        • 3.1 UDP(User Datagram Protocol):用户数据报协议
        • 3.2 TCP(Transmission Control Protocol):传输控制协议
          • 🤔三次握手建立连接:
          • 🤔四次挥手断开连接:
      • 4. UDP通信
        • 4.1 一发一收
          • 1) 客户端
          • 2) 服务端
        • 4.2 多发多收
          • 1) 客户端
          • 2) 服务端
          • 3) 多次运行同一个java类
          • 4) 结果
      • 5. TCP通信
        • 5.1 一发一收
          • 1)客户端 Socket
          • 2)服务端 ServerSocket
        • 5.2 多发多收(客户端只有一个)
          • 1)客户端
          • 2)服务端
        • 5.3 ⭐同时接收多个客户端的消息
          • 1)接收数据的线程
          • 2)服务端(主线程)
          • 3)客户端
        • 5.4 B/S架构的原理(网站开发)
          • 1)服务端
          • 2)使用线程池优化

网络编程

1. 概述

网络编程:

  • 可以让设备中的程序与网络上其他设备中的程序进行数据交互的技术(实现网络通信)
基本的通信架构 >>> 都依赖网络编程
  • CS结构(Client客户端/Server服务端)

  • BS架构(Browser浏览器/Server服务端)

2. 网络编程的三要素:IP、端口、协议

2.1 IP地址

IP地址

设备在网络中的地址 >>> 设备在网络中的唯一标识

  • 目前,使用广泛的IP地址形式:IPV4,IPV6
1)IPV4

Internet Protocol version 4的缩写,使用32位地址点分十进制

2)IPV6

Internet Protocol version 6的缩写,使用128位地址冒分十六进制

3)IP域名(Domain Name) — DNS域名解析器(Domain Name System)

域名:

  • 用于在互联网上识别和定位网站的人类可读的名称

域名解析器:

  • 用于将域名转换成对应的IP地址的分布式命名系统,将易记的域名映射到数字化的IP地址,使得用户可以通过域名来访问网站和其他网络资源

4)公网IP、内网IP

公网IP

  • 可以连接到互联网的IP地址

内网IP

  • 局域网IP,只能在组织机构内部使用的IP地址

本机IP

  • 127.0.0.1、localhost

IP常用命令

  • ipconfig:查看本机IP地址
  • ping IP地址:检查网络是否连通
5)InetAddress

代表IP地址

// 目标:认识InetAddress获取本机IP对象和对方IP对象
try {
    // 1.1获取本机IP对象 (127.0.0.1)
    InetAddress local = InetAddress.getLocalHost();
    System.out.println(local);  // 自己的IP地址

    // 1.2获取对方IP对象
    InetAddress remote = InetAddress.getByName("www.baidu.com");
    System.out.println(remote); // www.baidu.com/36.152.44.132

    // 1.3 判断主机在指定毫秒内与该ip的连接是否成功
    boolean reachable = remote.isReachable(3000);
    System.out.println(reachable);  // true

    // 2.获取IP地址对应的主机名
    String hostName = remote.getHostName();
    System.out.println(hostName);   // www.baidu.com

    // 3.获取IP地址对象中的IP地址信息
    String hostAddress = remote.getHostAddress();
    System.out.println(hostAddress);    // 36.152.44.132

} catch (Exception e) {
    e.printStackTrace();
}
2.2 端口

用来标记正在计算机设备上运行的应用程序(一个16位的二进制:0~65535) — 应用程序在设备中的唯一标识

分类:

2.3 网络通信协议

连接和数据在网络中传输的规则

3. ⭐传输层的2个通信协议

3.1 UDP(User Datagram Protocol):用户数据报协议

3.2 TCP(Transmission Control Protocol):传输控制协议
  • 面向连接、可靠通信
  • 最终目的:保证在不可靠的信道上实现可靠的数据传输
  • 实现可靠传输的三个步骤:三次握手建立连接、传输数据进行确认、四次挥手断开连接

🤔三次握手建立连接:

①客户端发送请求,服务端接收请求 → 服务端:知道客户端发消息正常

②服务端返回响应,客户端接收到响应 → 客户端:知道服务端收消息和发消息都正常(收到了才会响应嘛)

③客户端发出确认信息,服务端收到信息 → 服务端:知道客户端收到了自己的响应,收消息正常

🤔四次挥手断开连接:

①客户端发送断开连接请求,服务端接收请求 → 服务端:知道客户端已经做好断开连接的准备了

②服务端返回“稍等”响应,客户端收到响应 → 客户端:知道服务端收到了“断开连接”的请求

③服务端将信息处理完毕后,返回给客户端断开响应,客户端接收响应 → 客户端:知道服务端也已经做好断开的准备,并且可能会接收到服务端最后返回的信息

④两边都准备好了,服务端发送确认断开信息 → 两边断开


4. UDP通信

  • 无连接、不可靠通信
  • 不会事先建立连接 >> 发送端每次把要发送的数据(限制在64KB内),接收端IP等信息封装成一个数据包,发出去
  • Java提供java.net.DatagramSocket类来实现UDP通信

4.1 一发一收
1) 客户端

① 创建DatagramSocket发送端对象

② 创建DatagramPacket数据包对象,封装发送数据

③ 发送端对象发送数据包的数据

public class UDPClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("========== 客户端启动...");

        // 目标:完成UDP通信一发一收:客户端开发
        // 1、创建发送端对象
        DatagramSocket socket = new DatagramSocket();   // 创建发送端对象,不指定端口号,系统随机分配一个端口号
        // 2、创建数据包对象,封装要发送的数据
        byte[] bytes = "你好,我是客户端".getBytes();
        /**
         * 参数一:要发送的数据
         * 参数二:要发送的数据的长度
         * 参数三:目标主机的IP地址 getByName("127.0.0.1")
         * 参数四:目标主机的端口号
         */
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length,
                InetAddress.getLocalHost(), 8080);
        
        // 3、让发送端对象发送数据包的数据
        socket.send(packet);
    }
}
2) 服务端

① 创建DatagramSocket服务端对象,并注册端口

② 创建DatagramPacket数据包对象,用于接收客户端发来的数据

③ 接收客户端发来的数据

④ 服务端可以获取客户端数据包中的IP地址和端口号

public class UDPServerDemo2 {
    public static void main(String[] args) throws Exception{
        System.out.println("============= 服务端启动...");

        // 目标:完成UDP通信一发一收:服务端开发
        // 1、创建接收端对象,注册端口
        DatagramSocket socket = new DatagramSocket(8080);
        //2、创建数据包对象,用于接受客户端发来的数据
        byte[] bytes = new byte[1024*64];   // 64KB
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length);

        // 3、接收客户端发来的数据,将数据封装到数据包对象的字节数组中
        socket.receive(packet);

        // 4、获取数据包对象中的数据,并打印
        String str = new String(bytes, 0, packet.getLength());
        System.out.println("服务端收到数据:" + str);

        // 5、获取对方的IP地址和端口号
        String ip = packet.getAddress().getHostAddress();   // 获取IP地址
        int port = packet.getPort();
        System.out.println("对方IP地址:" + ip);
        System.out.println("对方端口号:" + port);

    }
}

⚠️先启动服务端,再启动客户端 → 不可靠传输,客户端发送数据包不会管对方是否接收到

4.2 多发多收

同时收很多客户端的数据,只管接受数据包,不管是哪个客户端

在这里插入图片描述

1) 客户端

循环获取需要发送的数据

public class UDPClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("========== 客户端启动...");

        // 目标:完成UDP通信多发多收:客户端开发
        // 1、创建发送端对象
        DatagramSocket socket = new DatagramSocket();   // 创建发送端对象,不指定端口号,系统随机分配一个端口号

        Scanner scanner = new Scanner(System.in);
        byte[] bytes = null;

        // 循环发送数据
        while (true) {
            System.out.println("请输入要发送的数据:");
            // 2、创建数据包对象,封装要发送的数据
            String msg = scanner.nextLine();    // 键盘录入数据
            bytes = msg.getBytes(); // 将字符串转换成字节数组
            // 判断用户输入的指令是否为Exit:是 → 退出循环
            if ("Exit".equals(msg)) {
                System.out.println("====客户端退出...");
                break;
            }

            DatagramPacket packet = new DatagramPacket(bytes, bytes.length,
                    InetAddress.getLocalHost(), 8080);  // 指定发送的ip地址和端口号

            // 3、让发送端对象发送数据包的数据
            socket.send(packet);
        }
    }
}
2) 服务端

只需要将接收数据包的代码段用while循环包起来就可以了

while (true) {
    // 3、接收客户端发来的数据,将数据封装到数据包对象的字节数组中
    socket.receive(packet);

    // 4、获取数据包对象中的数据,并打印
    String str = new String(bytes, 0, packet.getLength());
    System.out.println("服务端收到数据:" + str);

    // 5、获取对方的IP地址和端口号
    String ip = packet.getAddress().getHostAddress();   // 获取IP地址
    int port = packet.getPort();
    System.out.println("对方IP地址:" + ip);
    System.out.println("对方端口号:" + port);
    System.out.println("-----------------------------------------");
}
3) 多次运行同一个java类

需要同时运行两个客户端

在这里插入图片描述

4) 结果

在这里插入图片描述

5. TCP通信

  • 面向连接、可靠通信

  • 三次握手建立可靠连接,实现端到端的通信

  • Java提供java.net.Socket类来实现TCP通信

  • 基于管道通信

在这里插入图片描述

5.1 一发一收
1)客户端 Socket

在这里插入图片描述在这里插入图片描述

public class TCPClientDemo1 {
    public static void main(String[] args) throws Exception {
        // 目标:实现TCP通信下一发一收,客户端开发
        // 1、常见Socket管道对象,请求与服务端的Socket链接,可靠连接
        Socket socket = new Socket("127.0.0.1", 9999);

        // 2、从Socket通信管道中得到一个字节输出流对象
        OutputStream os = socket.getOutputStream();

        // 3、特殊数据流:接收方接收的顺序和发送方一致
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeInt(1);
        dos.writeUTF("你好,我是客户端");
        // 按照字节数组的方式发送数据
        // os.write("你好,我是客户端".getBytes());

        // 4、关闭流资源
        socket.close();
    }
}
  • 按照行输出,就要按照行接收,按照字节打印,就要按字节接收

在这里插入图片描述

2)服务端 ServerSocket

在这里插入图片描述
在这里插入图片描述

public class TCPServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("=========服务端启动...");
        // 目标:实现TCP通信下一发一收,服务端开发
        // 1、创建一个服务器Socket对象,绑定监听端口
        ServerSocket ss = new ServerSocket(9999);
        // 2、调用accept()方法,阻塞等待客户端连接:一旦有客户端连接,会返回一个Socket对象
        Socket socket = ss.accept();
        // 3、获取输入流,读取客户端发送的数据
        InputStream is = socket.getInputStream();
        // 4、把字节输入流包装为特殊数据流输入流
        DataInputStream dis = new DataInputStream(is);
        // 5、读取数据并输出
        int id = dis.readInt();
        String msg = dis.readUTF();
        System.out.println("服务端收到:" + id + " " + msg);

        // 6、获取客户端信息
        System.out.println("客户端IP地址:" + socket.getInetAddress().getHostAddress());
        System.out.println("客户端端口号:" + socket.getPort());
        System.out.println("-----------------------------------------------------------");
    }
}

在这里插入图片描述

5.2 多发多收(客户端只有一个)

在这里插入图片描述

1)客户端
Scanner scanner = new Scanner(System.in);
// 循环发送数据
while (true) {
    System.out.println("请输入要发送的内容:");
    String msg = scanner.nextLine();
    if ("exit".equals(msg)) {
        System.out.println("====客户端退出...");
        dos.close();
        socket.close();
        break;
    }
    dos.writeUTF(msg);
    dos.flush();    // 刷新缓冲区,将数据写入到Socket缓存区中
}
2)服务端
// 5、读取数据并输出
while(true){
    String msg = dis.readUTF();
    System.out.println("服务端收到:" + msg);
    // 客户端信息:IP地址、端口号
    System.out.println("客户端IP地址:" + socket.getInetAddress().getHostAddress());
    System.out.println("客户端端口号:" + socket.getPort());
    System.out.println("-----------------------------------------");
}

在这里插入图片描述
在这里插入图片描述

5.3 ⭐同时接收多个客户端的消息

🤔服务端目前只有一个主线程,只能处理一个客户端的通信

  • 主线程定义一个循环,负责接收客户端Socket管道连接
  • 每接收到一个Socket通信管道后就分配一个独立的线程负责处理它
    在这里插入图片描述
1)接收数据的线程
public class ServerReader extends Thread{
    private Socket socket;
    public ServerReader(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            DataInputStream dis = new DataInputStream(is);

            // 一直循环读取数据并输出
            while(true){
                String msg = dis.readUTF();
                // 监测客户端退出
                if("exit".equals(msg)){
                    System.out.println("端口为"+socket.getPort()+"的客户端退出了");
                    System.out.println("-----------------------------------------");
                    break;	//客户端退出,接收的管道也关闭,退出循环
                }

                System.out.println("服务端收到:" + msg);
                // 客户端信息:IP地址、端口号
                System.out.println("客户端IP地址:" + socket.getInetAddress().getHostAddress());
                System.out.println("客户端端口号:" + socket.getPort());
                System.out.println("-----------------------------------------");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
2)服务端(主线程)

循环接收访问的客户端,然后创建一个ServerReader对象,对管道中的数据进行读取

public class TCPServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("=========服务端启动...");
        ServerSocket ss = new ServerSocket(9999);

        while (true) {
            // 调用accept()方法,阻塞等待客户端连接:一旦有客户端连接,会返回一个Socket对象
            Socket socket = ss.accept();
            new ServerReader(socket).start();
        }
    }
}
3)客户端
public class TCPClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("============客户端启动...");
        Socket socket = new Socket("127.0.0.1", 9999);
        // 从Socket通信管道中得到一个字节输出流对象
        OutputStream os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);

        Scanner scanner = new Scanner(System.in);
        // 循环发送数据
        while (true) {
            System.out.println("请输入要发送的内容:");
            String msg = scanner.nextLine();
            dos.writeUTF(msg);

            if ("exit".equals(msg)) {
                System.out.println("====客户端退出...");
                dos.close();
                socket.close();
                break;
            }
            dos.flush();    // 刷新缓冲区,将数据写入到Socket缓存区中
        }
    }
}

在这里插入图片描述

5.4 B/S架构的原理(网站开发)

1)服务端

服务器必须给浏览器响应HTTP协议规定的数据格式

public class ServerReader extends Thread {
    private Socket socket;

    public ServerReader(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 给当前对象的浏览器管道响应网页数据
            OutputStream os = socket.getOutputStream();
            // 使用打印流:方便自动换行
            PrintWriter pw = new PrintWriter(os);
            // 打印网页数据
            pw.println("HTTP/1.1 200 OK");
            pw.println("Content-Type:text/html;charset=utf-8");
            pw.println("");     // 空行
            pw.println("<html>");
            pw.println("<head>");
            pw.println("<title>");
            pw.println("测试");
            pw.println("</title>");
            pw.println("</head>");
            pw.println("<body>");
            pw.println("<h1 style = 'color:red'>你好,我是服务器</h1>");
            pw.println("</body>");
            pw.println("</html>");
            pw.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("服务器异常!");
        }
    }
}
public class ServerDemo {
    public static void main(String[] args) throws Exception {
        //目标:学习B/S架构的服务器端程序
        System.out.println("=========服务器端启动...");
        // 创建服务器对象,并注册端口
        ServerSocket ss = new ServerSocket(8080);

        while (true) {
            System.out.println("服务器端在8080端口监听...");

            // 调用accept()方法,阻塞等待客户端连接:一旦有客户端连接,会返回一个Socket对象
            Socket socket = ss.accept();
            System.out.println("一个客户端连接了..."+socket.getInetAddress()+":"+socket.getPort());
            new ServerReader(socket).start();
        }
    }
}

⚠️这样会创建很多线程

2)使用线程池优化

public class ServerDemo {
    public static void main(String[] args) throws Exception {
        //目标:学习B/S架构的服务器端程序
        System.out.println("=========服务器端启动...");
        // 创建服务器对象,并注册端口
        ServerSocket ss = new ServerSocket(8080);

        // 创建一个线程池,用来存储线程对象
        ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 10,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        while (true) {
            System.out.println("服务器端在8080端口监听...");

            // 调用accept()方法,阻塞等待客户端连接:一旦有客户端连接,会返回一个Socket对象
            Socket socket = ss.accept();
            System.out.println("一个客户端连接了..."+socket.getInetAddress()+":"+socket.getPort());
            // 创建一个线程对象,完成数据的读取工作 
            pool.execute(new ServerReader(socket));
        }
    }
}

💡这里直接将继承了Thread的ServerReader对象作为参数传给了pool,而不是使用Runnable的实现类对象,是因为Thread本身就是Runnable的实现类对象,如果要将ServerReader改写成Runnable的实现类对象也是可以的

相关文章:

  • C语言复习笔记--操作符详解(下)
  • 蓝桥杯 之 LCA算法
  • 搜广推校招面经六十一
  • 多线程案例-单例模式
  • Tcp套接字编程
  • go - grpc入门
  • 5G_WiFi_CE_杂散测试
  • C语言入门教程100讲(0)从了解C语言的发展史开始
  • 3月29日星期六今日早报简报微语报早读
  • 【Qt】Qt 类的继承与内存管理详解:QObject、信号槽与隐式共享
  • Conda配置Python环境
  • 实时目标检测新突破:AnytimeYOLO——随时中断的YOLO优化框架解析
  • 侯捷 C++ 课程学习笔记:C++ 中引用与指针的深度剖析
  • CS2 DEMO导入blender(慢慢更新咯)
  • Mayo Clinic Platform在人工智能医疗领域的现状及启示意义研究
  • 深度学习——图像余弦相似度
  • 基于华为设备技术的端口类型详解
  • 嵌入式八股RTOS与Linux--中断篇
  • vue如何实现前端控制动态路由
  • 基于pycatia的CATIA零部件激活状态管理技术解析
  • 4月制造业PMI为49%,比上月下降1.5个百分点
  • 大型长读长RNA测序数据集发布,有助制定精准诊疗策略
  • 体坛联播|欧冠半决赛阿森纳主场不敌巴黎,北京男篮险胜山西
  • 安徽省公安厅原副厅长刘海石主动投案,正接受审查调查
  • 法治日报调查直播间“杀熟”乱象:熟客越买越贵,举证难维权不易
  • 专访丨青年作家杜梨:以动物的视角去观察这个世界