Java 基础进阶篇(十九):一文搞懂网络编程

文章目录

  • 一、网络编程概述
  • 二、网络通信三要素
    • 2.1 IP 地址
    • 2.2 端口
    • 2.3 协议
      • 2.3.1 网络通信协议的两套参考模型
      • 2.3.2 UDP 协议
      • 2.3.3 TCP 协议
  • 三、UDP通信
    • 3.1 DatagramPacket:数据包对象(韭菜)
    • 3.2 DatagramSocket:发送端和接收端对象(人)
    • 3.3 案例:一发一收
    • 3.4 案例:多发多收
    • 3.5 案例:同时接受多个客户端消息
  • 四、TCP通信
    • 4.1 TCP通信模式模拟
    • 4.2 Socket(客户端)
    • 4.3 ServerSocket(服务端)
    • 4.4 案例:一发一收
    • 4.5 案例:多发多收
    • 4.6 案例:同时接受多个客户端消息
  • 五、TCP综合案例:群聊


一、网络编程概述

网络编程可以让程序与网络上的其他设备中的程序进行数据交互。

网络通信基本模式:Client-Server(CS) 、rowser/Server(BS)

  • Client-Server(CS)
    • 客户端:需要程序员开发实现、用户需要安装客户端。
    • 服务端:需要程序员开发实现。
  • Browser/Server(BS)
    • 客户端:采用浏览器,不需要程序员开发实现、用户需要安装浏览器。
    • 服务端:需要程序员开发实现。

二、网络通信三要素

三要素:IP地址、端口、协议。

2.1 IP 地址

IP地址:设备在网络中的地址,是唯一的标识。常见的 IP 分类为:

  • IPV4:32位(4字节)
  • IPv6:128位(16字节):IPv6 分成 8 个整数,每个整数用四个十六进制位表示, 数之间用冒号分开。

地址形式:公网地址、和私有地址(局域网使用),192.168. 开头的就是常见的局域网地址,范围即为 192.168.0.0 ~ 192.168.255.255,专门为组织机构内部使用。

IP常用命令

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

特殊 IP 地址:如本机IP 127.0.0.1或者 localhost 称为回送地址也可称本地回环地址,只会寻找当前所在本机。

IP地址基本寻路:电脑自带 dns 服务器,用于解析域名。
Java 基础进阶篇(十九):一文搞懂网络编程_第1张图片

IP地址操作类 InetAddress:此类表示 Internet 协议(IP)地址。常用 API 如下:

Java 基础进阶篇(十九):一文搞懂网络编程_第2张图片
举例:

public class InetAddressDemo01 {
    public static void main(String[] args) throws Exception {
        // 1.获取本机地址对象。
        InetAddress ip1 = InetAddress.getLocalHost(); // DESKTOP-Q8LL1E1/192.168.171.1
        System.out.println(ip1.getHostName());// 192.168.171.1
        System.out.println(ip1.getHostAddress());// DESKTOP-Q8LL1E1

        // 2.获取域名ip对象
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName()); // www.baidu.com
        System.out.println(ip2.getHostAddress()); // 182.61.200.6

        // 3.获取公网IP对象。
        InetAddress ip3 = InetAddress.getByName("112.80.248.76");
        System.out.println(ip3.getHostName()); // 112.80.248.76
        System.out.println(ip3.getHostAddress()); // 112.80.248.76

        // 4.判断是否在指定毫秒内连通该IP地址对应的主机
        System.out.println(ip3.isReachable(5000));
    }
}

2.2 端口

端口:应用程序在设备中唯一的标识,被规定为一个 16 位的二进制,范围是 0~65535。

端口类型

  • 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用 80,FTP占用21)
  • 注册端口:1024~49151,分配给用户进程或某些应用程序。(如:Tomcat占 用8080,MySQL占用3306)
  • 动态端口:49152~65535,之所以称为动态端口,是因为它 一般不固定分配某种进程,而是动态分配

注:一个设备中不能出现两个程序的端口号一样,不同设备的相同程序,端口号要一样。


2.3 协议

协议:数据在网络中传输的规则,常见的协议有 UDP 协议和 TCP 协议。

2.3.1 网络通信协议的两套参考模型

  • OSI参考模型:世界互联协议标准,全球通信规范,由于此模型过于理想化,未能在因特网上进行广泛推广。
  • TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。

Java 基础进阶篇(十九):一文搞懂网络编程_第3张图片
传输层的2个常见协议:TCP、UDP。


2.3.2 UDP 协议

协议特点:

  • UDP是一种无连接、不可靠传输的协议
  • 将数据源IP、目的地IP和端口封装成数据包,不需要建立连接。
  • 每个数据包的大小限制在64KB内
  • 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的。
  • 可以广播发送 ,发送数据结束时无需释放资源,开销小,速度快。

协议通信场景: 语音通话,视频会话等。

UDP协议比喻:

数据包对象(韭菜)、发送端和接收端对象(人)
Java 基础进阶篇(十九):一文搞懂网络编程_第4张图片


2.3.3 TCP 协议

协议特点:

  • 使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
  • 传输前,采用“三次握手”方式建立连接,所以是可靠的
  • 在连接中可进行大数据量的传输
  • 连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。

协议通信场景: 对信息安全要求较高的场景,例如:文件下载、金融等数据通信。

三次握手确认连接:
Java 基础进阶篇(十九):一文搞懂网络编程_第5张图片

Java 基础进阶篇(十九):一文搞懂网络编程_第6张图片

四次挥手断开连接:
Java 基础进阶篇(十九):一文搞懂网络编程_第7张图片
Java 基础进阶篇(十九):一文搞懂网络编程_第8张图片


三、UDP通信

UDP是一种无连接、不可靠传输的协议,其将数据源IP、目的地IP和端口以及数据封装成数据包,大小限制在64KB内,直接发送出去即可。

3.1 DatagramPacket:数据包对象(韭菜)

构造器:
Java 基础进阶篇(十九):一文搞懂网络编程_第9张图片

常用API:
Java 基础进阶篇(十九):一文搞懂网络编程_第10张图片


3.2 DatagramSocket:发送端和接收端对象(人)

构造器:
Java 基础进阶篇(十九):一文搞懂网络编程_第11张图片

常用API:
Java 基础进阶篇(十九):一文搞懂网络编程_第12张图片


3.3 案例:一发一收

发送消息:

① 创建 DatagramSocket 对象(发送端对象)
② 创建 DatagramPacket 对象封装需要发送的数据(数据包对象)
③ 使用 DatagramSocket 对象的 send 方法传入 DatagramPacket 对象
④ 释放资源

public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        // 1. 创建发送端对象,发送端自带默认端口号 (人)
        DatagramSocket socket = new DatagramSocket();
        // 2. 创建一个数据包对象封装数据(韭菜)
        byte[] bytes = "甜晕了".getBytes();
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8888);
        // 接收端需指定端口号8888
        // 3. 发送数据出去
        socket.send(packet);
        // 4. 释放资源
        socket.close();
    }
}		

接收消息:

① 创建 DatagramSocket 对象并指定端口(接收端对象)
② 创建 DatagramPacket 对象接收数据(数据包对象)
③ 使用 DatagramSocket 对象的 receive 方法传入 DatagramPacket 对象
④ 释放资源

public class ServerDemo2{
    public static void main(String[] args) throws Exception {
        // 1. 创建接收端对象:注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);
        // 2. 创建一个数据包对象接收数据(韭菜)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        // 3. 等待接收数据。
        socket.receive(packet);
        // 4. 取出数据即可
        int len = packet.getLength(); // 读取多少倒出多少
        String rs = new String(buffer, 0, len);
        System.out.println("收到了:" + rs);
        String ip = packet.getSocketAddress().toString(); // 获取发送端的 ip 和端口
        System.out.println("对方地址:" + ip);
        int port  = packet.getPort();
        System.out.println("对方端口:" + port);
        // 5. 释放资源
        socket.close();
    }
}

输出:

Java 基础进阶篇(十九):一文搞懂网络编程_第13张图片
注:先运行服务端,在运行客户端。


3.4 案例:多发多收

在上文案例的基础上添加 while 循环。

客户端:

public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        // 1、创建发送端对象:发送端自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket(7777);
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();
            if("exit".equals(msg)){
                System.out.println("离线成功!");
                socket.close();
                break;
            }
            // 2、创建一个数据包对象封装数据(韭菜盘子)
            byte[] buffer = msg.getBytes();
            DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
                    InetAddress.getLocalHost() , 8888);
            // 3、发送数据出去
            socket.send(packet);
        }
    }
}

服务端:

public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("=====服务端启动======");
        // 1、创建接收端对象:注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);
        // 2、创建一个数据包对象接收数据(韭菜)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        while (true) {
            // 3、等待接收数据。
            socket.receive(packet);
            // 4、取出数据即可
            // 读取多少倒出多少
            int len = packet.getLength();
            String rs = new String(buffer,0, len);
            System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs);
        }
    }
}

注:接收端只负责接收数据包,无所谓是哪个发送端的数据包。

输出:

Java 基础进阶篇(十九):一文搞懂网络编程_第14张图片
Java 基础进阶篇(十九):一文搞懂网络编程_第15张图片


3.5 案例:同时接受多个客户端消息

idea 默认一个程序开启一次,当重复运行同一个程序时,会有如下提示:
Java 基础进阶篇(十九):一文搞懂网络编程_第16张图片

因此,如果想实现客户端多开,需要修改配置,如下:

第一步:点击 Edit Configurations… 按钮。
Java 基础进阶篇(十九):一文搞懂网络编程_第17张图片
第二步:点击 Modify options 修改参数。
Java 基础进阶篇(十九):一文搞懂网络编程_第18张图片
点击 Allow multiple instances 选项,即可实现客户端多开功能:
Java 基础进阶篇(十九):一文搞懂网络编程_第19张图片
其次,如果客户端指定端口号,会抛出端口号已经使用的异常。
Java 基础进阶篇(十九):一文搞懂网络编程_第20张图片
Java 基础进阶篇(十九):一文搞懂网络编程_第21张图片

因此,需要不用写指定端口,使用其默认端口号,不会导致端口号冲突。

Java 基础进阶篇(十九):一文搞懂网络编程_第22张图片

Java 基础进阶篇(十九):一文搞懂网络编程_第23张图片


四、TCP通信

  TCP 是一种面向连接,安全、可靠的传输数据的协议。 数据传输前,采用 “三次握手” 方式,点对点通信,是可靠的。 并且在连接中可进行大数据量的传输。

4.1 TCP通信模式模拟

Java 基础进阶篇(十九):一文搞懂网络编程_第24张图片
注意:在 java 中只要是使用 java.net.Socket 类实现通信,其底层使用了 TCP 协议。


4.2 Socket(客户端)

构造器:
在这里插入图片描述
常用 API:
Java 基础进阶篇(十九):一文搞懂网络编程_第25张图片


4.3 ServerSocket(服务端)

构造器:
Java 基础进阶篇(十九):一文搞懂网络编程_第26张图片
常用 API:
Java 基础进阶篇(十九):一文搞懂网络编程_第27张图片


4.4 案例:一发一收

发送消息:

① 创建客户端的 Socket 对象,请求与服务端的连接。
② 使用 Socket 对象调用 getOutputStream() 方法得到字节输出流。
③ 使用字节输出流完成数据的发送。
④ 释放资源:关闭 Socket 管道。(程序结束,管道自动关闭)

public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成数据输出流
            DataOutputStream dos = new DataOutputStream(os);

            // 4、发送消息
            dos.writeUTF("我是TCP的客户端,我已经与你对接,并发出邀请:约吗?");
            dos.close();

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

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

接收消息:

① 创建 ServerSocket 对象,注册服务端端口。
② 调用 ServerSocket 对象的 accept() 方法,等待客户端的连接,并得到 Socket 管道对象。
③ 通过 Socket 对象调用 getInputStream() 方法得到字节输入流、完成数据的接收。
④ 释放资源:关闭 Socket 管道

public class ServerDemo2 {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            
            // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
            Socket socket = serverSocket.accept();
            
            // 3、从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            
            // 4、把原始字节输入流包装成 数据输入流进行消息的接收
            DataInputStream dis = new DataInputStream(is);
            
            // 5、使用数据输入流读取客户端发送过来的消息
            String msg = dis.readUTF();
            System.out.println(msg);

            // 补充:获取客户端的地址
            System.out.println(socket.getRemoteSocketAddress());

            // 6、关闭流
            dis.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出:

Java 基础进阶篇(十九):一文搞懂网络编程_第28张图片

TCP 通信原理:

客户端怎么发,服务端就应该怎么收。
客户端如果没有消息,服务端会进入阻塞等待。
Socket 一方关闭或者出现异常、对方 Socket 也会失效或者出错。


4.5 案例:多发多收

步骤:

① 客户端也可以使用死循环等待用户不断输入消息。
② 可以使用死循环控制服务端收完消息继续等待接收下一个消息。
③ 客户端一旦输入了 exit ,则关闭客户端程序,并释放资源。

客户端:

public class ClientDemo1 {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成数据输出流
            DataOutputStream dos = new DataOutputStream(os);

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.print("请说:");
                String msg = sc.nextLine();
                if("exit".equals(msg)){
                    System.out.println("离线成功");
                    // 关闭资源
                    dos.close();
                    socket.close();
                    break;
                }
                // 4、发送消息
                dos.writeUTF(msg);
                dos.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端:

public class ServerDemo2 {
    public static void main(String[] args) throws Exception {       
        System.out.println("===服务端启动成功===");
        // 1、注册端口
        ServerSocket serverSocket = new ServerSocket(7777);

        // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
        Socket socket = serverSocket.accept();

        // 3、从socket通信管道中得到一个字节输入流
        InputStream is = socket.getInputStream();

        // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
        DataInputStream dis = new DataInputStream(is);

        while(true){
            // 5、使用数据输入流读取客户端发送过来的消息
            String msg = dis.readUTF();
            System.out.println(socket.getRemoteSocketAddress()+ "说:" + msg);
        }
    }
}

输出:
Java 基础进阶篇(十九):一文搞懂网络编程_第29张图片

Java 基础进阶篇(十九):一文搞懂网络编程_第30张图片


注意:若此时关闭客服端,服务端会报错!原因客户端与服务端建立的是同一个管道。
Java 基础进阶篇(十九):一文搞懂网络编程_第31张图片
可以通过异常捕获来解决以上问题:

public class ServerDemo2 {
    public static void main(String[] args) throws Exception {

        System.out.println("===服务端启动成功===");
        // 1、注册端口
        ServerSocket serverSocket = new ServerSocket(7777);

        // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
        Socket socket = serverSocket.accept();

        // 3、从socket通信管道中得到一个字节输入流
        InputStream is = socket.getInputStream();

        // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
        DataInputStream dis = new DataInputStream(is);

        while(true){
            try {
                // 5、使用数据输入流读取客户端发送过来的消息
                String msg = dis.readUTF();
                System.out.println(socket.getRemoteSocketAddress()+ "说:" + msg);
            } catch (IOException e) {
                System.out.println(socket.getRemoteSocketAddress() + "离线了!");
                dis.close();
                socket.close();
                break;
            }
        }
    }
}

Java 基础进阶篇(十九):一文搞懂网络编程_第32张图片

Java 基础进阶篇(十九):一文搞懂网络编程_第33张图片

注意:服务端不可以同时接收多个客户端的消息,因为服务端现在只有一个线程,只能与一个客户端建立通信管道。


4.6 案例:同时接受多个客户端消息

对于上文案例的问题,可以通过多线程的方式来解决。

Java 基础进阶篇(十九):一文搞懂网络编程_第34张图片

主线程定义了循环负责接收客户端 Socket 管道连接,每接收到一个Socket通信管道后分配一个独立的线程负责处理它。

public class ServerDemo2 {
    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
            while (true) {
                // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+ " ta来了,上线了!");
                // 3、开始创建独立线程处理socket
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 3、从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();

            // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
            DataInputStream dis = new DataInputStream(is);

            // 5、按照行读取消息
            while(true){
                try {
                    // 5、使用数据输入流读取客户端发送过来的消息
                    String msg = dis.readUTF();
                    System.out.println(socket.getRemoteSocketAddress()+ " 说:" + msg);
                } catch (IOException e) {
                    System.out.println(socket.getRemoteSocketAddress() + " 离线了!");
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出:

Java 基础进阶篇(十九):一文搞懂网络编程_第35张图片


五、TCP综合案例:群聊

TCP 通信,需要实现 端口转发 的思想,即客户端是不能和客户端直接通信的,需要先发送到服务端,然后通过服务端,转发到客户端。

此外,还需要使用集合存储在线 socket,方便为哪些 socket 推送群聊信息。

实现:

public class Client {
    public static void main(String[] args) {
        try {
            System.out.println("====客户端启动===");
            // 1、创建Socket通信管道请求有服务端的连接
            // public Socket(String host, int port)
            // 参数一:服务端的IP地址
            // 参数二:服务端的端口
            Socket socket = new Socket("127.0.0.1", 7777);

            // F. 创建一个独立的线程,负责随机从 socket 中接收服务端发送过来的消息。
            new ClientReaderThread(socket).start();

            // 2、从socket通信管道中得到一个字节输出流 负责发送数据
            OutputStream os = socket.getOutputStream();

            // 3、把低级的字节流包装成数据输出流
            DataOutputStream dos = new DataOutputStream(os);

            Scanner sc =  new Scanner(System.in);
            while (true) {
                System.out.println("请说:");
                String msg = sc.nextLine();
                if("exit".equals(msg)){
                    System.out.println("离线成功");
                    // 关闭资源
                    dos.close();
                    socket.close();
                    break;
                }
                // 4、发送消息
                dos.writeUTF(msg);
                dos.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
// E. 创建 客户端线程 接收其他客户端的信息
public class ClientReaderThread extends Thread{

    private Socket socket;
    public ClientReaderThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 3、从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();

            // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
            DataInputStream dis = new DataInputStream(is);

            // 5、按照行读取消息
            while(true){
                try {
                    // 5、使用数据输入流读取客户端发送过来的消息
                    String msg = dis.readUTF();
                    System.out.println(socket.getRemoteSocketAddress()+ " 说:" + msg);
                } catch (IOException e) {
                    System.out.println("自己离线了!");
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class Server {
    // A. 在线 socket 集合
    public static List<Socket> onLineSockets = new ArrayList<>();

    public static void main(String[] args) {
        try {
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            // 定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
            while (true) {
                // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
                Socket socket = serverSocket.accept();
                // B. 加入socket集合中
                onLineSockets.add(socket);

                System.out.println(socket.getRemoteSocketAddress()+ " ta来了,上线了!");
                // 3、开始创建独立线程处理socket
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 3、从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();

            // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
            DataInputStream dis = new DataInputStream(is);

            // 5、按照行读取消息
            while(true){
                try {
                    // 5、使用数据输入流读取客户端发送过来的消息
                    String msg = dis.readUTF();
                    System.out.println(socket.getRemoteSocketAddress()+ " 说:" + msg);

                    // D. 把这个消息分发给全部客户端进行接收!
                    sendMsgToAll(msg);
                } catch (IOException e) {
                    System.out.println(socket.getRemoteSocketAddress() + " 离线了!");
                    // C. 在线 socket 集合删除当前 socket
                    Server.onLineSockets.remove(socket);
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送全部所有的socket的管道接收
     */
    private void sendMsgToAll(String msg) throws IOException {
        for (Socket onLineSocket : Server.onLineSockets) {
            OutputStream os = onLineSocket.getOutputStream();
            DataOutputStream dos = new DataOutputStream(os);
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

输出:

Java 基础进阶篇(十九):一文搞懂网络编程_第36张图片


文章参考:Java入门基础视频教程,java零基础自学就选黑马程序员Java入门教程(含Java项目和Java真题)

你可能感兴趣的:(JavaSE,网络,java,网络协议,tcp,udp)