JAVA基础加强篇11——网络编程

网络编程课程安排

什么是网络编程?

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

网络通信基本模式

  • 常见的通信模式有如下 2 种形式:Client-Server(CS)、Browser/Server(BS)

    JAVA基础加强篇11——网络编程_第1张图片

    JAVA基础加强篇11——网络编程_第2张图片

课程安排

JAVA基础加强篇11——网络编程_第3张图片

网络通信三要素

三要素概述、要素一:IP地址

实现网络编程关键的三要素
  • IP 地址:设备在网路中的地址,是唯一的标识。
  • 端口:应用程序在设备中唯一的标识。
  • 协议:数据 网络中传输的规则,常见的协议有 UDP 协议 和 TCP 协议

JAVA基础加强篇11——网络编程_第4张图片

IP 地址
  • IP(Internet Protocol):全称“互联网协议地址”,是分配给上网设备的唯一标志。
  • 常见的 IP 分类为:IPv4 和 IPv6
IPv4

JAVA基础加强篇11——网络编程_第5张图片

IPv6
  • IPv6:128位(16个字节),号称可以为地球每一粒沙子编号。

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

    JAVA基础加强篇11——网络编程_第6张图片

IP 地址基本寻路

JAVA基础加强篇11——网络编程_第7张图片

IP 地址形式:
  • 公网地址、和私有地址(局域网使用)。
  • 192.168. 开头的就是常见的局域网地址,范围即为192.168.0.0–192.168.255.255,专门为组织结构内部使用。
IP常用命令:
  • ipconfig:查看本机 IP 地址
  • ping IP 地址:检查网络是否连通
特殊IP地址:
  • 本机IP:127.0.0.1 或者 localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本机。
总结
  1. 说说网络通信至少需要几个要素
    • IP、端口、协议。
  2. IP 地址是做什么的,具体有几种
    • 定位网络上的设备的,有 IPv4,IPv6。
  3. 如何查看本机IP地址,如何看是否与对方互通
    • ipconfig
    • ping 对方ip地址
  4. 本机IP是谁?
    • 127.0.0.1 或者是 localhost

IP 地址操作类-InetAddress

InetAddress 的使用
  • 此类表示 Internet 协议(IP) 地址。
InetAddress API 如下

JAVA基础加强篇11——网络编程_第8张图片

/**
 * @author : gxd
 * @date : 2022/7/19 14:14
 * 目标:IP 地址操作类-InetAddress
 *
 * InetAddress 的使用
 * - 此类表示 Internet 协议(IP) 地址。
 *
 * InetAddress API 如下:
 * - public static InetAddress getLocalHost():返回本主机的地址对象
 * - public static InetAddress getByName(String host):得到指定主机的 IP 地址对象,参数是域名或者 IP 地址
 * - public String getHostName():获取此 IP 地址的主机名
 * - public String getHostAddress():返回 IP 地址字符串
 * - public boolean isReachable(int timeout):在指定毫秒内连通该 IP 地址对应的主机,连通返回 true
 */
public class InetAddressTest1 {
    public static void main(String[] args) throws Exception {
        //1、获取本机地址对象
        InetAddress ip1 = InetAddress.getLocalHost();
        System.out.println(ip1.getHostName());
        System.out.println(ip1.getHostAddress());
        //2、获取域名 ip 对象
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());
        //3、获取公网 IP 对象
        InetAddress ip3 = InetAddress.getByName("110.242.68.4");
        System.out.println(ip3.getHostName());
        System.out.println(ip3.getHostAddress());
        //4、判断是否能通:ping 5s之前测试是否可通
        System.out.println(ip3.isReachable(5000));
    }
}
总结
  1. IP 地址的代表类是谁?
    • InetAddress 类
  2. 如何获取本机 IP 对象
    • public static InetAddress getLocalHost()
  3. 如何判断与该 IP 地址对象是否互通?
    • public boolean isReachable(int timeout)

要素二:端口号

端口号
  • 端口号:标识正在计算机上运行的进程(程序),被规定为一个 16 位的二进制,范围是 0~65535。
端口类型
  • 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP 占用 80,FTP 占用 21)
  • 注册端口:1024~49151,分配给用户进程或某些应用程序。(如:Tomcat 占用 8080,MySQL 占用 3306)
  • 动态端口:49152到65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配。

注意:我们自己开发的程序选择注册端口,且一个设备汇总不能出现两个程序的端口号一样,否则出错。

总结
  1. 端口号的作用是什么?
    • 唯一标识正在计算机设备上运行的进程(程序)
  2. 一个设备汇总,能否出现 2 个应用程序的端口号一样,为什么?
    • 不可以,如果一样会出现端口冲突错误。

要素三:协议

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

JAVA基础加强篇11——网络编程_第9张图片

传输层的 2 个常见协议
  • TCP(Transmission Control Protocol):传输控制协议
  • UDP(User Datagram Protocol):用户数据报协议
TCP 协议特点
  • 使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
  • 传输前,采用“三次握手”方式建立连接,所以是可靠的。
  • 在连接中可进行大数据量的传输。
  • 连接、发送数据都需要确定,且传输完毕后,还需释放已建立的连接,通信效率较低。
TCP 协议通信场景
  • 对信息安全要求较高的场景,例如:文件下载、金融等数据通信。
TCP 三次握手确定连接

JAVA基础加强篇11——网络编程_第10张图片

TCP 四次挥手断开连接

JAVA基础加强篇11——网络编程_第11张图片

UDP协议:
  • UDP 是一种无连接、不可靠传输的协议。
  • 将数据源 IP、目的地 IP 和端口封装成数据包,不需要建立连接
  • 每个数据包的大小限制在 64KB 内
  • 发送不管对方是否准备好,接收方收到也不确认,故事不可靠的。
  • 可以广播发送,发送数据结束时无需释放资源,开销小,速度快。
UDP 协议通信场景
  • 语音通话,视频会话等。
总结
  1. 通信协议是什么?
    • 计算机网络中,连接和通信数据的规则被称为网络通信协议。
  2. TCP 通信协议的特点是什么样的?
    • 它是一种面向连接的可靠通信协议。
    • 传输前,采用“三次握手”方式建立连接,点对点的通信,所以可靠。
    • 在连接中可进行大数据量的传输。
    • 通信效率较低。
  3. UDP 协议的特点是什么?
    • 用户数据报协议(User Datagram Protocol)
    • UDP是面向无连接,不可靠传输的通信协议。
    • 速度快,有大小限制一次最多发送 64K,数据不安全,易丢失数据。

UDP通信

UDP通信:快速入门

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

JAVA基础加强篇11——网络编程_第12张图片

DatagramPacket 常用方法

在这里插入图片描述

DatagramSocket:发送端和接收端对象

在这里插入图片描述

DatagramSocket 类成员方法

JAVA基础加强篇11——网络编程_第13张图片

步骤 使用UDP通信实现:发送消息、接受消息

需求:客户端实现步骤:

  1. 创建 DatagramSocket 对象(发送端对象)
  2. 创建 DatagramPacket 对象封装需要发送的数据(数据包对象)
  3. 使用 DatagramSocket 对象的 send 方法传入 DatagramPacket 对象
  4. 释放资源

需求:接收端实现步骤

  1. 创建 DatagramSocket 对象并指定端口(接收端对象)
  2. 创建 DatagramPacket 对象接收数据(数据包对象)
  3. 使用 DatagramSocket 对象的 receive 方法传入 DatagramPacket 对象
  4. 取出数据
  5. 释放资源

发送端:

/**
 * @author : gxd
 * @date : 2022/7/19 17:07
 * 发送端: 一发 一收
 */
public class ClientTest1 {
    public static void main(String[] args) throws Exception {
        System.out.println("=============客户端启动===========");
        //1、创建发送端对象:发送端自带默认的端口号
        DatagramSocket socket = new DatagramSocket();

        //2、创建一个数据包对象封装数据
        /**
          public DatagramPacket(byte buf[], int length,
                                    InetAddress address, int port)
          参数一:封装要发送的数据
          参数二:发送数据的大小
          参数三:服务端的主机 IP 地址
          参数四:服务端的接口
         */
        byte[] buffer = "你好,接收端!".getBytes();
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);

        //3、发送数据出去
        socket.send(packet);

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

接收端:

/**
 * @author : gxd
 * @date : 2022/7/19 17:07
 * 接收端
 */
public class ServerTest2 {
    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);

        //3、等待接收数据
        socket.receive(packet);

        //4、取出数据即可
        //读取多少倒出多少
        int len = packet.getLength();
        String rs = new String(buffer,0,len);
        System.out.println("收到了:" + rs);

        //获取发送端的 ip 和端口
        String ip = packet.getSocketAddress().toString();
        System.out.println("对方地址:" + ip);

        int port = packet.getPort();
        System.out.println("对方端口:" + port);
        //5、释放资源
        socket.close();
    }
}
总结
  1. UDP 发送端和接收端的对象是哪个?
    • public DatagramSocket():创建发送端的 Socket 对象
    • public DatagramSocke(int port):创建接收端的 Socket 对象
  2. 数据包对象是哪个?
    • DatagramPacket
  3. 如何发送、接收数据包?
    • 使用 DatagramSocket 的如下方法:
    • public void send(DatagramPacket dp):发送数据包
    • public void receive(DatagramPacket dp):接收数据包

UDP通信:多发多收

案例 使用 UDP 通信实现:多发多收消息

需求

  • 使用 UDP 通信方式开发接收端和发送端。

分析

  1. 发送端可以一直发送消息。
  2. 接收端可以不断的接收多个发送端的消息展示。
  3. 发送端输入了 exit 则结束发送端程序。

客户端实现步骤

  1. 创建 DatagramSocket 对象(发送端对象)
  2. 使用 while 死循环不断的接收用户的数据输入,如果用户输入的 exit 则退出程序
  3. 如果用户输入的不是 exit,把数据封装成 DatagramPacket
  4. 使用 DatagramSocket 对象的 send 方法将数据包对象进行发送
  5. 释放资源

接收端实现步骤

  1. 创建 DatagramSocket 对象并指定端口(接收端对象)
  2. 创建 DatagramPacket 对象接收数据(数据包对象)
  3. 使用 while 死循环不断的进行第4步
  4. 使用 DatagramSocket 对象的 receive 方法传入 DatagramPacket 对象

客户端:

/**
 * @author : gxd
 * @date : 2022/7/19 17:07
 * 发送端: 多发  多收
 */
public class ClientTest1 {
    public static void main(String[] args) throws Exception {
        System.out.println("=============客户端启动===========");
        //1、创建发送端对象:发送端自带默认的端口号
        DatagramSocket socket = new DatagramSocket();

        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();

            if ("exit".equals(msg)){
                System.out.println("连线成功!");
                //4、释放资源
                socket.close();
                break;
            }

            //2、创建一个数据包对象封装数据
            byte[] buffer = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);

            //3、发送数据出去
            socket.send(packet);
        }
    }
}

服务端:

/**
 * @author : gxd
 * @date : 2022/7/19 17:07
 * 接收端
 */
public class ServerTest2 {
    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.getSocketAddress() + ",对方端口是:" + packet.getPort() +"的消息:"  + rs);
        }
    }
}
总结
  1. UDP 的接收端为什么可以接收很多发送端的消息?
    • 接收端只负责接收数据包,无所谓是哪个发送端的数据包。

UDP通信-广播、组播

UDP 的三种通信方式

  • 单播:单台主机与单台主机之间的通信。

  • 广播:当前主机与所在网络中的所有主机通信。

  • 组播:当前主机与选定的一组主机的通信。

    JAVA基础加强篇11——网络编程_第14张图片

UDP 如何实现广播

  • 使用广播地址:255.255.255.255
  • 具体操作:
    1. 发送端发送的数据包的目的地写的是广播地址、且指定端口。(255.255.255.255,9999)
    2. 本机所在网段的其他主机的程序只要匹配端口成功即就可以收到消息了。(9999)

客户端:

/**
 * @author : gxd
 * @date : 2022/7/19 17:07
 * 发送端: 多发  多收
 *
 * UDP 如何实现广播
 * - 使用广播地址:255.255.255.255
 * - 具体操作:
 *   1. 发送端发送的数据包的目的地写的是广播地址、且指定端口。(255.255.255.255,9999)
 *   2. 本机所在网段的其他主机的程序只要匹配端口成功即就可以收到消息了。(9999)
 */
public class ClientTest1 {
    public static void main(String[] args) throws Exception {
        System.out.println("=============客户端启动===========");
        //1、创建发送端对象:发送端自带默认的端口号
        DatagramSocket socket = new DatagramSocket();

        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();

            if ("exit".equals(msg)){
                System.out.println("离线成功!");
                //4、释放资源
                socket.close();
                break;
            }

            //2、创建一个数据包对象封装数据
            byte[] buffer = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getByName("255.255.255.255"),9999);

            //3、发送数据出去
            socket.send(packet);
        }
    }
}

服务端:

/**
 * @author : gxd
 * @date : 2022/7/19 17:07
 * 接收端
 */
public class ServerTest2 {
    public static void main(String[] args) throws Exception {
        System.out.println("=============服务端启动===========");
        //1、创建接收端对象:注册端口
        DatagramSocket socket = new DatagramSocket(9999);

        //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.getSocketAddress() + ",对方端口是:" + packet.getPort() +"的消息:"  + rs);
        }
    }
}

UDP 如何实现组播

  • 使用组播地址:224.0.0.0~239.255.255.255
  • 具体操作:
    1. 发送端的数据包的目的地是组播 IP(例如:224.0.1.1,端口:9999)
    2. 接收端必须绑定该组播IP(224.0.1.1),端口还要对应发送端的目的端口9999,这样即可接收该组播消息。
    3. DatagramSocket的子类 MulticastSocket 可以在接收端绑定组播 IP。

客户端:

/**
 * @author : gxd
 * @date : 2022/7/19 17:07
 * 发送端: 多发  多收
 * 
 * UDP 如何实现组播
 * - 使用组播地址:224.0.0.0~239.255.255.255
 * - 具体操作:
 *   1. 发送端的数据包的目的地是组播 IP(例如:224.0.1.1,端口:9999)
 *   1. 接收端必须绑定该组播IP(224.0.1.1),端口还要对应发送端的目的端口9999,这样即可接收该组播消息。
 *   1. DatagramSocket的子类 MulticastSocket 可以在接收端绑定组播 IP。
 */
public class ClientTest1 {
    public static void main(String[] args) throws Exception {
        System.out.println("=============客户端启动===========");
        //1、创建发送端对象:发送端自带默认的端口号
        DatagramSocket socket = new DatagramSocket();

        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();

            if ("exit".equals(msg)){
                System.out.println("连线成功!");
                //4、释放资源
                socket.close();
                break;
            }

            //2、创建一个数据包对象封装数据
            byte[] buffer = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getByName("224.0.1.1"),9999);

            //3、发送数据出去
            socket.send(packet);
        }
    }
}

服务端:

/**
 * @author : gxd
 * @date : 2022/7/19 17:07
 * 接收端
 */
public class ServerTest2 {
    public static void main(String[] args) throws Exception {
        System.out.println("=============服务端启动===========");
        //1、创建接收端对象:注册端口
        MulticastSocket socket = new MulticastSocket(9999);

        //把当前接收端加入到一个组播组中去:绑定对应的组播消息的组播 IP
        //socket.joinGroup(InetAddress.getByName("224.0.1.1"));//过时
        socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.1.1"),9999),
                NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));

        //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.getSocketAddress() + ",对方端口是:" + packet.getPort() +"的消息:"  + rs);
        }
    }
}
总结
  1. 如何实现广播,具体怎么操作?
    • 发送端目的 IP 使用广播IP:255.255.255.255 9999。
    • 所在网段的其他主机对应了端口(9999)即可接收消息。
  2. 如何实现组播,具体怎么操作?
    • 发送端目的 IP 使用组播 IP,且指定端口。
    • 所在网段的其他主机注册了该组播 IP 和对应端口即可接收消息。

TCP通信-快速入门

编写客户端代码

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

JAVA基础加强篇11——网络编程_第15张图片

注意:在 java 中只要是使用 java.net.Socket 类实现通信,底层即是使用了 TCP 协议

Socket

在这里插入图片描述

Socket 类成员方法

JAVA基础加强篇11——网络编程_第16张图片

步骤 客户端发送消息

需求:客户端实现步骤

  1. 创建客户端的 Socket 对象,请求与服务端的连接。
  2. 使用 socket 对象调用 getOutputStream() 方法得到字节输出流。
  3. 使用字节输出流完成数据的发送。
  4. 释放资源:关闭 socket 管道。
/**
 * @author : gxd
 * @date : 2022/7/20 23:06
 * 目标:完成 Socket网络编程(TCP通信)入门案例的客户端开发,实现1发1收
 *
 * 客户端实现步骤
 * 1. 创建客户端的 Socket 对象,请求与服务端的连接。
 * 2. 使用 socket 对象调用 getOutputStream() 方法得到字节输出流。
 * 3. 使用字节输出流完成数据的发送。
 * 4. 释放资源:关闭 socket 管道。
 */
public class ClientTest1 {
    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、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            //4、发送消息
            ps.println("我是TCP的客户端,我向你发出邀请:约吗?");
            ps.flush();

            //关闭资源
            //socket.close();//不建议关闭,因为可能发送一半数据就关闭了,出现问题。
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
总结
  1. TCP 通信的客户端的代表类是谁?
    • Socket 类
    • public Socket(String host,int port)
  2. TCP 通信如何使用 Socket 管道发送、接受数据。
    • OutputStream getOutputStream():获得字节输出流对象(发)
    • InputStream getInputStream():获得字节输入流对象 (收)

编写服务端代码、原理分析

ServerSocket(服务端)

在这里插入图片描述

ServerSocket 类成员方法

在这里插入图片描述

步骤 服务端实现接受消息

需求:服务端实现步骤

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

服务端:

/**
 * @author : gxd
 * @date : 2022/7/20 23:23
 * 目标:开发 Socket网络编程(TCP通信)入门代码的服务端,实现接收信息
 * 
 * 服务端实现步骤
 * 1. 创建 ServerSocket 对象,注册服务端端口。
 * 2. 调用 ServerSocket 对象的 accept() 方法,等待客户端的连接,并得到 Socket 管道对象。
 * 3. 通过 Socket 对象调用 getInputStream() 方法得到字节输入流、完成数据的接收。
 * 4. 释放资源:关闭 Socket 管道
 */
public class ServerTest2 {
    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、把字节输入流包装成缓冲字符输入流进行消息的接受
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            //5、按照行读取消息
            String msg;
            if ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
总结
  1. TCP 通信服务端的代表类?
    • ServerSocket 类,注册端口。
    • 调用 accept() 方法阻塞等待接收客户端连接。得到 Socket 对象。
  2. TCP 通信的基本原理?
    • 客户端怎么发,服务端就应该怎么收。
    • 客户端如果没有消息,服务端会进入阻塞等待。
    • Socket 一方关闭或者出现异常、对方 Socket 也会失效或者出错。

TCP通信-多发多收消息

案例 使用 TCP 通信实现:多发多收消息

具体要求:

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

客户端:

/**
 * @author : gxd
 * @date : 2022/7/21 9:32
 * 目标:使用 TCP 通信方式实现:多发多收消息
 *
 * 具体要求:
 * 1. 可以使用死循环控制服务端收完消息继续等待接收下一个消息。
 * 2. 客户端也可以使用死循环等待用户不断输入消息。
 * 3. 客户端一旦输入了 exit,则关闭客户端程序,并释放资源。
 */
public class ClientTest1 {
    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、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc = new Scanner(System.in);

            while (true){
                System.out.println("请说:");
                String rs = sc.nextLine();

                if ("exit".equals(rs)){
                    //5、释放资源
                    System.out.println("离线成功!");
                    socket.close();
                    break;
                }

                //4、发送消息
                ps.println(rs);
                ps.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端:

/**
 * @author : gxd
 * @date : 2022/7/21 9:33
 * 接收端
 */
public class ServerTest2 {
    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、把字节输入流包装成缓冲字符输入流进行消息的接受
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            //5、按照行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意:

本案例实现了多发多收,那么是否可以同时接收多个客户端的消息?

  • 不可以的。
  • 因为服务端现在只有一个线程,只能与一个客户端进行通信。

总结

  1. 本次多发多收是如何实现的?
    • 客户端使用循环反复地发送消息。
    • 服务端使用循环反复地接收消息。
  2. 现在服务端为什么不可以同时接收多个客户端的消息?
    • 目前服务端是单线程的,每次只能处理一个客户端的消息。

TCP通信-同时接收多个客户端消息[重点]

回顾

  1. 之前我们的通信是否可以同时与多个客户端通信,为什么?
    • 不可以的
    • 单线程每次只能处理一个客户端的 Socket 通信
  2. 如何才可以让服务端可以处理多个客户端的通信要求?
    • 引入多线程

同时处理多个客户端消息

JAVA基础加强篇11——网络编程_第17张图片

客户端:

/**
 * @author : gxd
 * @date : 2022/7/21 11:23
 * 客户端
 *
 * 目标:TCP通信-同时接收多个客户端消息
 *  -引入多线程
 */
public class ClientTest1 {
    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、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.println("请说:");
                String rs = sc.nextLine();

                if ("exit".equals(rs)){
                    //5、释放资源
                    System.out.println("离线成功!");
                    socket.close();
                    break;
                }

                //4、发送消息
                ps.println(rs);
                ps.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端:

/**
 * @author : gxd
 * @date : 2022/7/21 11:23
 * 服务端
 */
public class ServerTest2 {
    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() + "上线了!");
                //3、开始创建独立线程处理 socket
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端线程类

/**
 * @author : gxd
 * @date : 2022/7/21 11:26
 * 服务端读取线程类
 */
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、把字节输入流包装成缓冲字符输入流进行消息的接受
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            //5、按照行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!");
        }
    }
}

总结

  1. 本次是如何实现服务端接收多个客户端的消息的?
    • 主线程定义了循环负责接收客户端 Socket 管道连接。
    • 没接收到一个 Socket 通信管道后分配一个独立的线程负责处理它。

TCP通信-使用线程池优化

回顾:

  1. 目前的通信框架存在什么问题?
    • 客户端与服务端的线程模型是:N-N的关系
    • 客户端并发越多,系统瘫痪的越快。
引入线程池处理多个客户端信息

JAVA基础加强篇11——网络编程_第18张图片

使用线程池优化步骤

客户端:

/**
 * @author : gxd
 * @date : 2022/7/21 22:47
 * 目标:使用线程池优化:实现通信。
 * 客户端
 */
public class ClientTest1 {
    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、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.println("请说:");
                String rs = sc.nextLine();

                if ("exit".equals(rs)){
                    //5、释放资源
                    System.out.println("离线成功!");
                    socket.close();
                    break;
                }

                //4、发送消息
                ps.println(rs);
                ps.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端

/**
 * @author : gxd
 * @date : 2022/7/21 22:47
 * 服务端
 */
public class ServerTest2 {

    //使用静态变量记住一个线程池对象
    private static ExecutorService pool = new ThreadPoolExecutor(3,5,
            6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2),
            Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

    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() + "上线了!");
                //任务对象读取消息
                Runnable target = new ServerReaderRunnable(socket);
                pool.execute(target);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端Runnable线程

/**
 * @author : gxd
 * @date : 2022/7/21 22:53
 * 服务端Runnable线程
 */
public class ServerReaderRunnable implements Runnable{
    private Socket socket;

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

    @Override
    public void run() {
        try {
            //3、从 socket 通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //4、把字节输入流包装成缓冲字符输入流进行消息的接受
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            //5、按照行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!");
        }
    }
}

总结

  1. 本次使用线程池的优势在哪里?
    • 服务端可以复用线程处理多个客户端,可避免系统瘫痪。
    • 适合客户端通信时长较短的场景。

TCP通信实战案例-即时通信

思考

  1. 即时通信是什么含义,要实现怎么样的设计?
    • 即时通信,是指一个客户端的消息发出去,其他客户端也可以接收到。
    • 之前我们的消息都是发给服务端的。
    • 即时通信需要进行端口转发的设计思想。

即时通信-端口转发

JAVA基础加强篇11——网络编程_第19张图片

JAVA基础加强篇11——网络编程_第20张图片

TCP实现即时通信步骤

客户端:

/**
 * @author : gxd
 * @date : 2022/7/21 23:20
 * 目标;TCP通信实战案例-即时通信
 * 客户端
 */
public class ClientTest1 {
    public static void main(String[] args) {
        try {
            System.out.println("=========客户端启动========");
            //1、创建 Socket 通信管道请求有服务端的连接
            Socket socket = new Socket("127.0.0.1",7777);

            //创建一个独立的线程专门负责这个客户端的读消息(服务端随时可能转发消息过来!)
            new ClientReaderThread(socket).start();

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

            //3、把低级的字节流包装成打印流
            PrintStream ps = new PrintStream(os);

            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.println("请说:");
                String rs = sc.nextLine();

                if ("exit".equals(rs)){
                    //5、释放资源
                    System.out.println("离线成功!");
                    socket.close();
                    break;
                }

                //4、发送消息
                ps.println(rs);
                ps.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

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、把字节输入流包装成缓冲字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            //5、按照行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println("收到消息:" + msg);
            }

        } catch (Exception e) {
            System.out.println("服务端把你踢出去了!");
        }
    }
}

服务端:

/**
 * @author : gxd
 * @date : 2022/7/21 23:20
 * 服务端
 */
public class ServerTest2 {
    //定义我一个静态的List集合存储当前全部在线的socket管道
    public static List<Socket> allOnlineSockets = new ArrayList<>();

    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() + "上线了!");
                allOnlineSockets.add(socket);//上线完成
                //3、开始创建独立线程处理 socket
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

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、把字节输入流包装成缓冲字符输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            //5、按照行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
                //把这个消息进行端口转发给全部客户端socket管道
                sendMsgToAll(msg,socket);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!");
            ServerTest2.allOnlineSockets.remove(socket);
        }
    }

    /**
     * 把消息发送给全部客户端
     */
    private void sendMsgToAll(String msg,Socket socket) throws Exception {
        for (Socket socket1 : ServerTest2.allOnlineSockets) {
            if (socket != socket1){
                PrintStream ps = new PrintStream(socket1.getOutputStream());
                ps.println(msg);
                ps.flush();
            }
        }
    }
}

总结

  1. 即时通信是什么含义,要实现怎么样的设计?
    • 即时通信,是指一个客户端额消息发出去,其他客户端可以接收到
    • 即时通信需要进行端口转发的设计思想。
    • 服务端需要把在线的 Socket 管道存储起来
    • 一旦收到一个消息要推送给其他管道

TCP通信实战案例-实现BS请求[了解]

回顾

  1. 之前的客户端都是什么样的?
    • 其实就是 CS 架构,客户端需要我们自己开发实现的。
  2. BS 结构是什么样的,需要开发客户端吗?
    • 浏览访问服务器,不需要开发客户端。

实现 BS 开发

JAVA基础加强篇11——网络编程_第21张图片

注意:服务器必须给浏览器响应 HTTP 协议格式的数据,否则浏览器不识别。

HTTP 响应数据的协议格式

HTTP 响应数据的协议格式:就是给浏览器显示的网页信息>

JAVA基础加强篇11——网络编程_第22张图片

TCP通信实战案例-实现BS请求 线程步骤

/**
 * @author : gxd
 * @date : 2022/7/22 15:51
 * 目标:TCP通信实战案例-实现BS请求
 * 服务端
 */
public class BSserverTest1 {
    public static void main(String[] args) {
        try {
            //1、注册端口
            ServerSocket serverSocket = new ServerSocket(8080);
            //2、创建一个循环接收多个客户端的请求
            while (true) {
                Socket socket = serverSocket.accept();
                //3、交给一个独立的线程来处理
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class ServerReaderThread extends Thread{
    private Socket socket;

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

    @Override
    public void run() {
        try {
            //浏览器 已经与本线程建立了 Socket 管道
            //响应消息给浏览器显示
            PrintStream ps = new PrintStream(socket.getOutputStream());
            //必须响应 HTTP 协议格式数据,否则浏览器不认识消息
            ps.println("HTTP/1.1 200 OK");//协议类型和版本 响应成功的消息!
            ps.println("Content-Type:text/html;charset=UTF-8");//响应的数据类型:文本/网页
            ps.println();//必须发送一个空行
            //才可以响应数据回去给浏览器
            ps.println("

小于不干开发之后去开《炸于店》!!!

"
); ps.close(); } catch (Exception e) { e.printStackTrace(); } } }

优化 TCP通信实战案例-实现BS请求 线程池步骤

/**
 * @author : gxd
 * @date : 2022/7/22 15:51
 * 目标:TCP通信实战案例-实现BS请求
 * 服务端
 */
public class BSserverTest1 {
    //使用静态变量记住一个线程池对象
    private static ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        try {
            //1、注册端口
            ServerSocket serverSocket = new ServerSocket(8080);
            //2、创建一个循环接收多个客户端的请求
            while (true) {
                Socket socket = serverSocket.accept();
                //3、交给一个独立的线程来处理
                pool.execute(new ServerReaderRunnable(socket));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * @author : gxd
 * @date : 2022/7/22 16:20
 * 服务端Runnable线程
 */
public class ServerReaderRunnable implements Runnable{
    private Socket socket;

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

    @Override
    public void run() {
        try {
            //浏览器 已经与本线程建立了 Socket 管道
            //响应消息给浏览器显示
            PrintStream ps = new PrintStream(socket.getOutputStream());
            //必须响应 HTTP 协议格式数据,否则浏览器不认识消息
            ps.println("HTTP/1.1 200 OK");//协议类型和版本 响应成功的消息!
            ps.println("Content-Type:text/html;charset=UTF-8");//响应的数据类型:文本/网页
            ps.println();//必须发送一个空行
            //才可以响应数据回去给浏览器
            ps.println("

小于不干开发之后去开《炸于店》!!!

"
); ps.close(); } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "下线了!"); } } }

总结

  1. TCP 通信如何实现 BS 请求网页信息回来呢?
    • 客户端使用浏览器发起请求(不需要开发客户端)
    • 服务端必须按照浏览器的协议规则响应数据。
    • 浏览器使用什么协议规则呢?
    • HTTP 协议(简单了解下)

你可能感兴趣的:(JAVA基础加强篇,java)