Java基础-网络通信

Java基础-网络通信
1.1 基本认识

实现网络编程的三要素

  • IP地址:设备在网络中的地址 唯一的标识
  • 端口: 应用程序在设备中的唯一标识
  • 协议: 数据在网络中传输的规则,常见的协议有 UDP协议和TCP协议

1.IP地址

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

    • image-20240207134435457
  • IP地址形式:

    • 公网地址和私有地址(局域网地址)
    • 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0-192.168.255.255,专门为组织机构内部使用
  • IP常用命令

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

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

    • 计算机 依据域名(https://baidu.com)通过 DNS域名服务器,查询IP地址;然后访问对应的服务器IP地址,服务器返回数据展示在浏览器上。

InetAddress 的使用

  • 此类表示Internet协议 (IP地址)

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 InetAddressDemo1 {
    public static void main(String[] args) throws Exception{
        //1、获取本主机IP对象
        InetAddress ip = InetAddress.getLocalHost();
        //获取IP地址的主机名
        System.out.println(ip.getHostName());
        //获取Ip地址
        System.out.println(ip.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("112.80.248.74");
        System.out.println(ip3.getHostName());
        System.out.println(ip3.getHostAddress());
        //指定毫秒内连通IP对应主机,连通返回true
        System.out.println(ip3.isReachable(3000));
    }
}

运行结果:

DESKTOP-6O6MLG4
192.168.1.7
www.baidu.com
39.156.66.14
112.80.248.74
112.80.248.74
false

2.端口号

  • 端口号:标识 正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是 0~65535

端口类型

  • 周知端口:0~1023 ,被预先定义的知名应用占用(如 HTTP 占用80 , FTP 21)

  • 注册端口:1024~49151,分配给用户进程或某些应用程序(如 Tomcat 8080, MySQL 3306),我们自己开发的程序选择注册端口,且一个设备中不能出现两个一样端口号的程序,否则出错

  • 动态端口:49152~65535,之所以称为 动态端口,是因为它一般不固定分配某种进程,而是动态分配

3.通信协议

  • 连接和通信数据的规则称为 网络通信协议

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

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

传输层的2个常见协议

  • TCP (Transmission Control Protocol):传输控制协议
  • UDP(User Datagram Protocol):用户数据报协议

TCP协议特点

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

TCP协议通信场景

  • 对信息安全要求较高,例如:文件下载、金融等数据通信

UDP协议特点

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

UDP协议通信场景

  • 语音通信,视频会话等

网络通信基本模式

  • 常见的通信模式有两种形式:Client-Server(CS),Browser-Server(BS)
  • CS 客户端-服务器
    • 客户端(需要程序员开发实现,用户需要安装客户端),服务器(需要程序员开发实现)
  • BS 浏览器-服务器
    • 浏览器(不需要程序员开发实现,用户需要安装浏览器),服务器(需要程序员开发实现)
1.2 UDP通信

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

  • 构造器
    • public DatagramSocket() 创建发送端 Socket对象,系统会随机分配一个端口号
    • public DatagramSocket(int port) 创建接收端 Socket对象,并指定端口号
  • 成员方法
    • public void send(DatagramPacket dp) 发送数据包
    • public void receive(DatagramPacket p) 接收数据包

DatagramPacket :数据包对象(韭菜盘子)

  • 构造器
    • public DatagramPacket(byte[] buf ,int length ,InetAddress address ,int port) 创建发送端 数据包对象
      • buf :要发送的内容,字节数组
      • length :要发送内容的字节长度
      • address:接收端的IP地址长度
      • port:接收端的端口号
    • public DatagramPacket(byte[] buf ,int length) 创建接收端 数据包对象
      • buf :用来存储接收的内容
      • length:能够接收内容的长度
  • 常用方法
    • public int getLength() 获得实际接收到的字节个数

UDP实例 单开单收

/**
 *  实现发送端,发送数据  
 *  客户端对象随机端口号,发送数据包四种参数
 */
public class ClientDataGramSocketDemo {
    public static void main(String[] args) throws Exception{
        //1.创建发送方对象:随机分配一个端口号,空
        DatagramSocket client = new DatagramSocket();   //可以自定义端口888888
        //2.创建发送的数据包对象:(韭菜盘子)
        /**
         * public DatagramPacket(byte buf[], int offset, int length,
         *                           InetAddress address, int port)
         */
        byte[] buffer = "我喜欢你~~~~".getBytes();
        //本主机
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);
        //3.发送数据包
        client.send(packet);
        //4.关闭
        client.close();
    }
}
/**
 *  实现服务端对象 接收数据
 *  服务端对象:指定一个端口号
 *  服务端接收数据包对象:指定接收数据包与大小
 */
public class ServerDatagramSocketDemo {
    public static void main(String[] args) throws Exception{
        //1.创建服务端对象:服务端一定要 指定端口号 !!!
        DatagramSocket server = new DatagramSocket(8888);
        //2.创建服务端的数据包对象:(韭菜盘子)
        /**
         public DatagramPacket(byte buf[], int length)
         */
        byte[] buffer = new byte[1024*64]; // udp最大64B
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
        //3.等待接收数据包
        server.receive(packet);
        //4.输出
        int len = packet.getLength();   //获取接收的数据包大小
        System.out.println(new String(buffer,0,len));
        //5.获取发送方的ip和端口
        SocketAddress ClientIp = packet.getSocketAddress();
        System.out.println("发送端ip:"+ClientIp);
        int port = packet.getPort();
        System.out.println("发送端口:"+port);
        server.close();
    }
}

运行结果:

我喜欢你~~~~
发送端ip:/192.168.1.7:63536
发送端口:63536

UDP通信实例

  • 需要服务器先运营,再客户端
  • 允许多开,多收

UDP的接收端为什么可以接收很多发送端的消息

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

UDP案例 持续发送与接收消息 多开(一个电脑 只能间断性的运行多次)多发

//发送端
public class ClientDataGramSocketDemo {
    public static void main(String[] args) throws Exception{
        //1.创建发送方对象:随机分配一个端口号,空
        DatagramSocket client = new DatagramSocket();   //可以自定义端口888888
        Scanner sc = new Scanner(System.in);
        //2.创建发送的数据包对象:(韭菜盘子)
        /**
         * public DatagramPacket(byte buf[], int offset, int length,
         *                           InetAddress address, int port)
         */
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();
            if (Objects.equals("exit",msg)){
                //4.关闭
                System.out.println("离线成功~~~");
                client.close();
                break;
            }
            byte[] buffer = msg.getBytes();
            //本主机
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);
            //3.发送数据包
            client.send(packet);
        }
    }
}
//接收端
public class ServerDatagramSocketDemo {
    public static void main(String[] args) throws Exception{
        //1.创建服务端对象:服务端一定要 指定端口号 !!!
        DatagramSocket server = new DatagramSocket(8888);
        //2.创建服务端的数据包对象:(韭菜盘子)
        /**
         public DatagramPacket(byte buf[], int length)
         */
        byte[] buffer = new byte[1024*64]; // udp最大64B
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
        while (true) {
            //3.等待接收数据包
            server.receive(packet);
            //4.输出
            int len = packet.getLength();   //获取接收的数据包大小
            System.out.println("收到发送方ip:"+packet.getSocketAddress()+",端口:"+packet.getPort()+
                    ",发来消息:"+new String(buffer,0,len));
        }
        //server.close();
    }
}

发送端

请说:
hello
请说:
exit1
请说:
exit
离线成功~~~

接收端

收到发送方ip:/192.168.1.7:63581,端口:63581,发来消息:hello
收到发送方ip:/192.168.1.7:63581,端口:63581,发来消息:exit1

UDP的三种通信方式

  • 单播:单台主机与单台主机之间的通信
  • 广播:当前主机与所在网络中的所有主机通信
  • 组播:当前主机与选定的一组主机的通信

UDP如何实现广播?

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

实现 (上一个实例,仅修改数据包对象

    //本主机
    //广播
    //255.255.255.255,发送同一个局域网内的其他主机
    DatagramPacket packet = new DatagramPacket(buffer,buffer.length,
                                               InetAddress.getByName("255.255.255.255"),9999);

UDP如何实现组播?

  • 使用组播地址:224.0.0.0~239.255.255.255
  • 具体操作:
    • 发送端的数据包 的 目的地址 是组播IP(例如:224.0.1.1 , 端口号 :9999)
    • 接收端 必须 绑定该组播IP (224.0.1.1),端口还要对应发送端的目的端口 9999,这样即可接收该组播消息
    • DatagramSocket的子类 MulticastSocket可以在接收端绑定组播 IP
//224.0.0.0~239.255.255.255 之间
DatagramPacket packet = new DatagramPacket(buffer,buffer.length,
        InetAddress.getByName("224.0.1.1"),9999);
public class ServerDatagramSocketDemo {
    public static void main(String[] args) throws Exception{
        //1.创建服务端对象:服务端一定要 指定端口号 !!!
        //DatagramSocket 子类 Multicast
        MulticastSocket server = new MulticastSocket(9999);
        //将当前接收端加入到一个组播组中,绑定对应的组播消息的组播ip !!!!!!
        server.joinGroup(InetAddress.getByName("224.0.1.1"));
//        public void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf
//        server.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.1.1"),9999),
//                NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));
        //2.创建服务端的数据包对象:(韭菜盘子)
        /**
         public DatagramPacket(byte buf[], int length)
         */
        byte[] buffer = new byte[1024*64]; // udp最大64B
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
        while (true) {
            //3.等待接收数据包
            server.receive(packet);
            //4.输出
            int len = packet.getLength();   //获取接收的数据包大小
            System.out.println("收到发送方ip:"+packet.getSocketAddress()+",端口:"+packet.getPort()+
                    ",发来消息:"+new String(buffer,0,len));
        }
        //server.close();
    }
}
1.3 TCP通信

客户端 - 服务端

image-20240210161615541

注意:

  • Java中 只要使用java.net.Socket类实现通信,底层即是使用了TCP协议
  • 服务端 通过 SocketServer注册服务端(端口)server,server 通过 accept() 获取 Socket对象
1.3.1 单发 单收

发送端(客户端)

客户端发送消息

  • 创建客户端的Socket对象,请求与服务器的连接
  • 使用 socket 对象调用 getOutputStream() 方法得到字节输出流
  • 使用字节输出流完成数据的发送 (使用打印流 printStream)
  • 释放资源:关闭 socket 管道(输出流)

Socket 类

  • 构造器
    • public Socket(String host ,int port) 创建发送端的 Socket 对象与服务端连接,参数为服务端程序的 ip 和端口
  • 成员方法
    • OutputStream getOutputStream() 获得字节输出流对象
    • InputStream getInputStream() 获得字节输入流对象

服务器实现接收消息

  • 创建 ServerSocket 对象,注册服务器端口
  • 调用 ServerSocket 对象的 accept()方法,等待客户端的连接,并得到Socket管道对象
  • 通过 获取的 Socket 对象调用 getInputStream() 方法得到字节流输入、完成数据的接收 (使用 转换流 —> 字符流 —> 缓冲流 )
  • 释放资源:关闭 socket 管道(输入流)

ServerSocket 类

  • 构造器

    • public ServerSocket (int port) 注册服务端端口
  • 成员方法

    • public Socket accept() 等待接收客户端的Socket通信连接,连接成功返回Socket对象与客户端建立端到端通信
//客户端
public class ClientSocketTcpDemo {
    public static void main(String[] args) {
        try {
            //1.创建 socket 管道!!!!
            /**    public Socket(String host, int port)
             */
            Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),9999);
            //2.socket管道中,创建字节输出流
            OutputStream os = socket.getOutputStream();
            //3.低级字节输出流 转为 打印流
            //public PrintStream(OutputStream out)
            PrintStream  ps = new PrintStream(os);
            //4.发送消息
//            ps.print("Tcp连接成功,你好,今晚约吗?");
//            Connection reset  , 这里没有换行符而服务端是按行接收
//            Tcp 当一方挂了,另一方也会挂
            ps.println("Tcp连接成功,你好,今晚约吗?");
            ps.flush();
            //关闭资源
            //socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//服务端
public class ServerSocketDemo {
    public static void main(String[] args) {
        try {
            //1.注册端口!!! 使用ServerSocket !!!!
            //指定一致的服务端端口号
            ServerSocket serverSocket = new ServerSocket(9999);
            //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){
                //获取远端ip地址
                System.out.println(socket.getRemoteSocketAddress()+"发送:"+msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服务端

/192.168.1.7:55243发送:Tcp连接成功,你好,今晚约吗?

小结

  • TCP通信 服务端 用的代表类

    • ServerSocket类,注册端口
    • 调用 accept() 方法 阻塞等待接收客户端连接,得到Socket对象
  • TCP通信的基本原理?

    • 客户端怎么发,服务端就应该怎么收
    • 客户端如果没有消息,服务端会进入阻塞等待
    • Socket一方关闭或者出现异常、对方Socket也会失效或者出错 java.net.SocketException: Connection reset
1.3.2 多发 多收

与之前UDP 持续发送与接收消息 类似

  • 多发多收
    • 客户端使用循环反复地发送消息
    • 服务端使用循环反复地接收消息
//客户端
public class ClientSocketTcpDemo {
    public static void main(String[] args) {
        try {
            //1.创建 socket 管道!!!!
            /**    public Socket(String host, int port)
             */
            Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),9999);
            //2.socket管道中,创建字节输出流
            OutputStream os = socket.getOutputStream();
            //3.低级字节输出流 转为 打印流
            //public PrintStream(OutputStream out)
            PrintStream  ps = new PrintStream(os);
            Scanner sc = new Scanner(System.in);
            while (true) {
                //4.发送消息
//            ps.print("Tcp连接成功,你好,今晚约吗?");
//            Connection reset  , 这里没有换行符而服务端是按行接收 ,
//            Tcp 当一方挂了,另一方也会挂
                System.out.println("请说:");
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }
            //socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//服务端
public class ServerSocketDemo {
    public static void main(String[] args) {
        try {
            //1.注册端口!!! 使用ServerSocket
            //指定一致的服务端端口号
            ServerSocket serverSocket = new ServerSocket(9999);
            //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){
                //获取远端ip地址
                System.out.println(socket.getRemoteSocketAddress()+"发送:"+msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 现在服务端 为什么 不可以同时接收多个客户端地消息
    • 目前服务端是单线程的 , 每次只能处理一个客户端的消息

解决接收多个发送端消息问题? (真正的多发)

  • 方式一:
    • 1 主线程定义 循环负责接收客户端Socket管道 连接
    • 2 每接收到一个Socket通信管道后 分配一个独立的线程负责 处理它
//客户端
public class ClientSocketTcpDemo {
    public static void main(String[] args) {
        try {
            //1.创建 socket 管道!!!!
            Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),9999);
            //2.socket管道中,创建字节输出流
            OutputStream os = socket.getOutputStream();
            //3.低级字节输出流 转为 打印流
            //public PrintStream(OutputStream out)
            PrintStream  ps = new PrintStream(os);
            Scanner sc = new Scanner(System.in);
            while (true) {
                //4.发送消息
                System.out.println("请说:");
                String msg = sc.nextLine();
                //输入exit,关闭socket管道
                if ("exit".equals(msg)){
                    break;
                }else {
                    ps.println(msg);
                    ps.flush();
                }
            }
            //socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//服务端主线程 
public class ServerSocketDemo {
    public static void main(String[] args) {
        try {
            //1.注册端口!!! 使用ServerSocket
            //指定一致的服务端端口号
            ServerSocket serverSocket = new ServerSocket(9999);
            while (true) {
                //2.调用accept() , 等待接收客户端Socket连接请求 ,建立Socket通信管道
                Socket socket = serverSocket.accept();
                new ServerReaderThread(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//socket对象接收消息 的 独立线程
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));
            System.out.println(socket.getRemoteSocketAddress()+"已上线!!!欢迎!!!");
            //5.按行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                //获取远端ip地址
                System.out.println(socket.getRemoteSocketAddress()+"发送:"+msg);
            }
        } catch (Exception e) {
            //发送端异常!!!!
            //当发送方socket管道断开,结束接收
            System.out.println(socket.getRemoteSocketAddress() + "已下线~~~~");
        }
    }
}

同时运行 三个不同的客户端 发送消息

服务端的 运行结果

image-20240210172425166
  • 方式二
    • 引入线程池 处理多个客户端消息
image-20240210172843461

线程池使用:

客户端不用修改

//服务端 主线程
public class ServerSocketDemo {
    //创建线程池
    public static Executor executor = new ThreadPoolExecutor(3,5,3, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
    public static void main(String[] args) {
        try {
            //1.注册端口!!! 使用ServerSocket
            //指定一致的服务端端口号
            ServerSocket serverSocket = new ServerSocket(9999);
            while (true) {
                //2.调用accept() , 等待接收客户端Socket连接请求 ,建立Socket通信管道
                Socket socket = serverSocket.accept();
                //3.创建 任务类对象
                Runnable target = new ServerSocketRunnable(socket);
                executor.execute(target);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//服务端接收消息 的 独立线程
public class ServerSocketRunnable implements Runnable{
    private Socket socket;
    public ServerSocketRunnable(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            //3.通过Socket通信管道 得到 一个字节输入流
            InputStream is = socket.getInputStream();
            //4.低级输入流 转为 缓冲字节输入流
            //通过转换流 , 将字节流变为字符流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            System.out.println(socket.getRemoteSocketAddress()+"已上线!!!欢迎!!!");
            //5.按行读取消息
            String msg;
            while ((msg = br.readLine()) != null){
                //获取远端ip地址
                System.out.println(socket.getRemoteSocketAddress()+"发送:"+msg);
            }
        } catch (IOException e) {
            //发送端异常!!!!
            //当发送方socket管道断开,结束接收
            System.out.println(socket.getRemoteSocketAddress() + "已下线~~~~");
        }
    }
}

使用线程池的优势在哪里

  • 服务端可以服用线程处理多个客户端,可以避免系统瘫痪
  • 适合客户端通信时长较短的场景
1.4 即时通信(重点)

即时通信是什么含义,要实现怎样的设计

  • 即时通信:一个客户端的消息发出去,其他客户端可以接收到

  • 之前 消息都是发给服务端

  • 即时通信 需要进行 端口转发 的设计思想

image-20240210174934339
/**
 *  重点 !!!!!
 *  目标 : 学会使用 socket类 ,实现即时通信 多发多收
 *         客户端发送消息(输出) ,服务器转交(输入和输出) , 所有客户端接收消息(输入)
 */
public class ClientSocketTcpDemo {
    public static void main(String[] args) {
        try {
            //1.创建 socket 管道!!!!
            Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),9999);
            //创建 一个独立的线程 专门负责客户端的 接收 消息(服务器随时可能转发过来)
            new ClientReaderThread(socket).start();
            //2.socket管道中,创建字节输出流,发送消息
            OutputStream os = socket.getOutputStream();
            //3.低级字节输出流 转为 打印流
            PrintStream  ps = new PrintStream(os);
            Scanner sc = new Scanner(System.in);
            while (true) {
                //4.发送消息
                System.out.println("请说:");
                String msg = sc.nextLine();
                //输入exit,关闭socket管道
                if ("exit".equals(msg)){
                    break;
                }else {
                    ps.println(msg);
                    ps.flush();
                }
            }
            //socket.close();
        } 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){
                //获取远端ip地址
                System.out.println("收到"+socket.getRemoteSocketAddress()+"发送:"+msg);
            }
        } catch (Exception e) {
            //服务端异常!!!!
            //当服务端socket管道断开,结束接收
            System.out.println("服务端把你退出群聊~~~~");
        }
    }
}
/**
 *  重点 !!!
 *  目标:实现服务端 网络通信 多个发送端的多发多收  !!!
 *  注意!!! 主线程 根据socket管道创建子线程
 *      子线程启动后,接收消息
 *   服务端,接收发送端消息,转发给所有的发送端
 */
public class ServerSocketDemo {
    //创建 一个静态的Socket集合
    public static List<Socket> allSocketList = new ArrayList<>();
    public static void main(String[] args) {
        try {
            //1.注册端口!!! 使用ServerSocket
            //指定一致的服务端端口号
            ServerSocket serverSocket = new ServerSocket(9999);
            while (true) {
                //2.调用accept() , 等待接收客户端Socket连接请求 ,建立Socket通信管道
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+"已上线!!!欢迎!!!"); //上线
                //添加到 Socket集合
                ServerSocketDemo.allSocketList.add(socket);
                //子线程 启动(服务端 接收和发送消息)
                new ServerReaderThread(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

/**
 *   服务端,接收发送端的Socket管道
 *   再向所有的Socket对象 发送消息
 */
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){
                //获取远端ip地址
                //服务器 接收(输入)
                System.out.println(socket.getRemoteSocketAddress()+"发送:"+msg);
                //每一行的消息,服务器再(输出)到所有管道
                sendMsgToAll(msg);
            }
        } catch (Exception e) {
            //发送端异常!!!!
            //当发送方socket管道断开,结束接收
            System.out.println(socket.getRemoteSocketAddress() + "已下线~~~~");
            ServerSocketDemo.allSocketList.remove(socket);      //移除
        }
    }
    /**
     * 向所有Socket管道发送(输出)消息
     * @param msg        消息
     * @throws Exception
     */
    private void sendMsgToAll(String msg) throws Exception{
        for (Socket socket : ServerSocketDemo.allSocketList) {
            PrintStream ps = new PrintStream(socket.getOutputStream());
            ps.println(msg);
            ps.flush();
        }
    }
}

之前 都是 CS通信

1.5 BS开发

image-20240210181517785

/**
 *  目标 : 模拟 BS模式
 *         不需要考虑开发浏览器(发送端), 只考虑服务器输出
 */
public class ServerDemo{
    public static Executor pool = new ThreadPoolExecutor(3,5,3, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        try {
            //1.注册端口
            ServerSocket serverSocket = new ServerSocket(9999);

            //2.线程池处理 接收多个客户端请求
            while(true){
                Socket socket = serverSocket.accept();
                //3.交给一个独立线程来处理
                pool.execute(new ServerSocketRunnable(socket));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 *   注意: 响应HTTP协议格式!!!!
 */
public class ServerSocketRunnable implements Runnable{
    private Socket socket;
    public ServerSocketRunnable(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("<span style='color:red;font-size:90px'>《最牛的149期》</span>");
            ps.close();
        } catch (Exception e) {
            //发送端异常!!!!
            //当发送方socket管道断开,结束接收
            System.out.println(socket.getRemoteSocketAddress() + "已下线~~~~");
        }
    }
}
image-20240210183116916

Http协议格式

image-20240210183357034

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Java Platform指的是Java编程语言所基于的一套平台,包括Java虚拟机(JVM)、Java类库(Java API)等,可以在不同的操作系统和硬件平台上运行Java程序。Java平台的主要特点是跨平台性,即同一个Java程序可以在不同的操作系统和硬件平台上运行,而不需要对程序进行修改。Java平台还提供了丰富的类库,方便开发者进行快速开发。 ### 回答2: Java平台是由Java编程语言Java虚拟机(JVM)组成的一种计算平台。Java平台的设计目标是实现“一次编写,到处运行”的理念,即开发者只需编写一次代码,就可以在不同的操作系统和设备上运行。 Java平台的核心是Java虚拟机,它提供了一个解释和执行Java字节码的环境。Java虚拟机使得Java程序可以在不同的操作系统上运行,这使得Java成为了一种跨平台的编程语言Java平台还包括Java类库,这是一组既包含基础功能又提供高级功能的软件库。Java类库包括了各种类和接口,用于处理文件、网络通信、图形界面、数据库等常见任务。开发者可以通过使用Java类库来减少重复劳动,并加快开发流程。 通过Java平台和Java编程语言开发者可以实现各种不同类型的应用程序。Java平台被广泛应用于桌面应用、企业级应用和移动应用开发。它是世界上最广泛使用的编程平台之一。 Java平台的特点包括强大的安全性、可靠性和可移植性。Java通过提供丰富的安全机制,如类加载器和安全管理器,来确保应用程序的安全性。它还具有垃圾回收机制,以自动释放不再使用的内存资源。此外,Java平台的应用程序可以在不同的操作系统上运行,这使得它具有很高的可移植性。 总而言之,Java平台是一种跨平台的计算平台,提供了Java编程语言Java虚拟机和Java类库等核心组件,用于开发各种类型的应用程序。它具有强大的安全性、可靠性和可移植性,因此广泛应用于各个领域的软件开发。 ### 回答3: Java平台是一种计算机编程语言和计算机平台的组合,这个平台提供了一个运行Java程序的环境和工具的集合。Java平台的核心是Java虚拟机(Java Virtual Machine,简称JVM),它负责将Java源代码编译成可在不同操作系统上运行的字节码。通过JVM,在不同的操作系统上可以统一运行Java程序。 Java平台的主要特点之一是它的跨平台性。由于Java程序是在JVM上运行的,而不是在特定操作系统上,所以能够在不同操作系统上统一运行。只要操作系统上有安装相应的Java虚拟机,就可以运行Java程序,不需要针对不同的操作系统进行代码修改。 Java平台还提供了丰富的库和工具,以帮助开发人员快速而高效地开发各种应用程序。其中包括标准库(Java Standard Library)提供了大量常用的类和方法,用于处理输入输出、网络通信、图形界面等常见任务;还有开发工具(例如编译器、调试器、集成开发环境等)和第三方类库,可以提供更广泛的功能和更便捷的开发体验。 Java平台广泛应用于各个领域的软件开发,尤其在企业级应用开发和大型系统中得到了广泛应用。它具备良好的跨平台性和稳定性,可以在不同的系统上部署和运行,并且具有强大的性能和安全性。同时,Java语言本身也具备简单易学、面向对象、类型安全等特点,使得开发人员可以高效地进行开发Java平台的不断发展和更新,也使得它逐渐成为了一种全球范围内使用最广泛的编程语言开发平台之一。在移动应用开发、大数据处理、云计算等领域,Java平台都发挥着重要的作用,并持续为开发人员提供更多的技术和工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值