JavaSE进阶笔记:11 网络编程

181 阅读15分钟

1.网络通信概述

1.1网络通信基本模式:

CSClient-Server客户端(自行开发)-服务端
BSBrowser-Server浏览器 - 服务端

2.网络通信三要素

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

2.1 IP地址

IP全称互联网协议地址,分配给上网设备的唯一标志
常见 IP分类
IPV432bit(4字节,每个字节0-256) ;
例子: 192.168.0.128
IPV6128bit(16字节),号称可以为地球上每一粒沙子编号;
分成八个整数,每个整数用四个16进制位表示,之间用:分开

2.1.1 IP地址基本寻路

dns服务器上找不到,去运营商服务器寻找ip返回

服务器分类
dns服务器dns是多级服务器;
电脑中配置有dns缓存,ipconfig /displaydns查看dns缓存 ;ipconfig /flushdns清理dns缓存
运营商服务器存在所有公开的ip地址,访问部分网站慢,可以将ip地址配置在自己的电脑中,方法自行百度

IP地址基本寻路.png

2.1.2 IP地址形式

地址形式地址用法备注
公网地址
私有地址(局域网使用)192.168开头是常见局域网地址,范围192.168.0.0~192.168.255.255专门为组织机构使用,节约公网IP分配
特殊IP地址本机127.0.0.1或者localhost:称为回送地址,也可称本地回环地址,只会寻找当前所在机器

2.1.3 IP常用命令

命令
ipconfig查看本机IP地址
ping 192.168.0.0(IP地址)检查网络是否连接

2.1.4 IP地址操作类:InetAddress

/**
 * IP地址操作类:InetAddress
 */
public class inetAddressDemo01 {
    public static void main(String[] args) throws IOException {
        //1.获取本机地址对象
        InetAddress localHostIp = InetAddress.getLocalHost();
        System.out.println(localHostIp);
        System.out.println(localHostIp.getHostName());//DESKTOP-HIVV0B9
        System.out.println(localHostIp.getHostAddress());//192.168.1.8
        // 2.获取域名地址对象
        InetAddress bDIp = InetAddress.getByName("www.baidu.com");
        System.out.println(bDIp.getHostName());//www.baidu.com
        System.out.println(bDIp.getHostAddress());//220.181.38.150  服务器地址不止一个
        // 3.获取公网ip地址对象
        InetAddress bDIp2 = InetAddress.getByName("220.181.38.150");
        System.out.println(bDIp2.getHostName());//www.baidu.com
        System.out.println(bDIp2.getHostAddress());//220.181.38.150  服务器地址不止一个
        // 4.判断是否能联通:ping 5s内是否联通
        System.out.println(bDIp.isReachable(5 * 1000));//true
    }
}

2.2 端口

2.2.1 端口号

标识机器上运行的进程(程序),规定为一个16位二进制,范围0~65535;

2.2.2 端口类型

端口类型范围和使用者举例
周知端口0~1023,被预先定义的知名应用占用HTTP 80,FTP 21
注册端口1024~49151,分配给用户进程或某些应用程序Tomcat 8080,MySQL 3306
动态端口49152~65535,称为动态端口,不固定分配

注意:开发程序选择注册端口,同设备不能运行两个端口一样的程序,会报错

2.3 协议

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

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

参考模型概述常见协议
OSI参考模型世界互联协议标准,全球通信规范,过于理想化,未能广泛推广;
TCP/IP参考模型(TCP/IP协议)事实上的国际标准,传输层2个常见协议TCP:传输控制协议:可靠传输协议,效率低;
UDP:用户数据报协议:不可靠

2.3.2 TCP协议特点和通信场景

  • TCP协议,必须双方先建立连接,面向连接的可靠通信协议
  • 传输前,**“三次握手”**建立连接;
    • 1次,客户端向服务端发出连接请求(有需要回复是需要带的id),等待服务器确认
    • 2次,服务器返回响应,回复收到请求(上方的id,和自己确认时需要的id)
    • 3次,客户端发送确认信息,连接建立
  • 连接中,可进行大数据量的传输;
  • 连接、发送数据都需要确认,传输完毕后,**“四次挥手”**释放建立的连接,通信效率低
    • 1次,客户端向服务端发出取消连接请求;
    • 2次,服务器返回响应,表示收到请求,处理当前所有数据
    • 3次,数据处理完,向客户端发出确认取消信息
    • 4次,客户端再次发送确认消息,连接取消
  • 通信场景:
    • 信息安全要求较高,如文件下载,金融等数据通信;

2.3.3 UDP协议

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

3.UDP通信

3.1 UDP协议通信演示

/**
 * 服务端端
 */
public class ServerDemo {
    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 s = new String(buffer,0,len);
        System.out.println(s);

        //获得发送端ip和端口
        String ip = packet.getSocketAddress().toString();
        int port = packet.getPort();
        System.out.println("IP:"+ip+"    PORT:"+port);

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


/**
 * 发送端
 */
public class ClientDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端启动!");

        //1.创建发送端对象,发送端自带默认端口号(可以指定端口)
        DatagramSocket socket = new DatagramSocket(6666);
        //DatagramSocket socket = new DatagramSocket();

        /**
         * DatagramPacket( 数据包对象
         * byte buf[] 封装要发送的数据;字节数组
         * , int length  数据大小
         * ,InetAddress address 服务端主机IP地址
         * , int port)  服务端端口
         */
        // 2.创建一个数据包对象封装数据
        byte[] buffer = "事了拂衣去,深藏身与名".getBytes();
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getLocalHost(), 8888);

        //3.发送数据
        socket.send(packet);
        //释放资源
        socket.close();
    }
}

3.2 使用UDP通信实现(弹幕)多发多收信息

/**
 * 客户端(可多开)
 * 在程序设置中勾选 Allow Parallel run
 */
public class ClientDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端启动!");
        //1.创建发送端对象,发送端自带默认端口号(可以指定端口)
        DatagramSocket socket = new DatagramSocket();
        // 2.创建一个数据包对象封装数据
        String msg = "默认消息";
        byte[] buffer = msg.getBytes();
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getLocalHost(), 8888);

        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("请输入要发送消息:");
            msg = scanner.nextLine();
            if (msg.equals("exit")){
                System.out.println("离线成功");
                socket.close();
                break;
            }
            //写入的消息放入信息包中
            buffer = msg.getBytes();
            packet.setData(buffer,0, buffer.length);
            //3.发送数据
            socket.send(packet);
        }

    }
}


/**
 * 服务端端
 */
public class ServerDemo {
    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("来自IP:"+packet.getAddress()+"中的端口:"+packet.getPort()+"的信息----"+rs);
        }

        //释放资源
        //socket.close();
        /**
         * 服务端启动!
         * 来自IP:/192.168.1.8中的端口:56802的信息----你好
         * 来自IP:/192.168.1.8中的端口:58954的信息----你好,我是01,请多指教!
         * 来自IP:/192.168.1.8中的端口:58954的信息----你叫什么?
         * 来自IP:/192.168.1.8中的端口:56802的信息----我恁爹!
         */
    }
}

3.3 UDP实现广播

  • 使用广播地址:255.255.255.255

  • 发送端数据包目的地写广播地址,指定端口号9999;

  • 本机所在网段的其他主机程序只要匹配端口(9999)成功就可以收到消息;

  • DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("255.255.255.255"), 9999);
    

3.4 UDP组播

  • 使用组播地址:224.0.0.0~239.255.255.255
  • 发送端数据包目的地组播IP224.0.0.1,指定端口号9999;
  • 接收端必须绑定该组播IP,端口对应
  • DatagramSocket子类MulticastSocket可以在接收端绑定组播IP;
/**
 * 客户端(可多开)
 * 在程序设置中勾选 Allow Parallel run
 */
public class ClientDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端启动!");
        //1.创建发送端对象,发送端自带默认端口号(可以指定端口)
        DatagramSocket socket = new DatagramSocket();
        // 2.创建一个数据包对象封装数据
        String msg = "默认消息";
        byte[] buffer = msg.getBytes();
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("224.0.0.1"), 9999);

        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("请输入要发送消息:");
            msg = scanner.nextLine();
            if (msg.equals("exit")){
                System.out.println("离线成功");
                socket.close();
                break;
            }
            //写入的消息放入信息包中
            buffer = msg.getBytes();
            packet.setData(buffer,0, buffer.length);
            //3.发送数据
            socket.send(packet);
        }

    }
}

/**
 * 组播服务端
 * 创建接收端对象MulticastSocket
 * socket.joinGroup 当前接收端加入到一个组播组中,绑定对应的组播消息的组播IP
 */
public class ServerDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动!");

        //1.创建接收端对象MulticastSocket:注册端口(可以指定端口)
        MulticastSocket socket = new MulticastSocket(9999);
        //新增:当前接收端加入到一个组播组中,绑定对应的组播消息的组播IP
        //socket.joinGroup(InetAddress.getByName("224.0.0.1"));
        //新版本的用法,用当前本地的ip接收组播
        socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.0.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("来自IP:"+packet.getAddress().getHostAddress()+"中的端口:"+packet.getPort()+"的信息----"+rs);
        }
    }
}

4.TPC通信

注意:Java中使用java.net.Socket类实现通信,底层即是使用TCP协议;

4.1 TCP服务端用的代表类

  • ServerSocket类,注册端口;
  • 调用accept()方法阻塞,等待接收客户端连接,得到Socket对象;

4.2 TCP通信基本原理

  • 客户端怎么发,服务端怎么接;
  • 客户端没有消息,服务端阻塞等待;
  • Socket一方关闭或者异常,对方Socket也会失效或者出错;
/**
 * Socket 网络编程
 *  客户端
 */
public class TCPClientDemo {
    public static void main(String[] args) {
        System.out.println("客户端启动!");
        try {
            //1.创建Socket通信管道请求服务端的连接
            Socket socket = new Socket("127.0.0.1", 9999);
            // 2.从socket通信管道中得到一个字节输出流
            OutputStream os = socket.getOutputStream();
            // 3.把低级字节流包装成打印流
            PrintStream ps = new PrintStream(os);
            //4.发送消息
            /**
             * 注意:消息按行读取 ps.print没有出现换行符,会报错Connection reset
             */
            //ps.print("TCP客户端,请求与你对接,是否同意。");
            ps.println("TCP客户端,请求与你对接,是否同意。");
            ps.flush();

            //关闭资源,不推荐
            //socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


/**
 * Socket 网络编程
 *  服务端
 */
public class TCPServerDemo {
    public static void main(String[] args) {
        System.out.println("服务端启动!");
        try {
            // 1.注册端口
            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){
                System.out.println(socket.getRemoteSocketAddress()+"  说了: "+msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.3 实现服务端接收多个客户端消息

  • 主线程定义循环负责接收客户端Socket管道连接;
  • 每接收一个Socket通信管道后,分配一个独立子线程负责处理;
/**
 * Socket 网络编程
 *  服务端
 * 单线程 只能处理一个客户端信息
 *     Socket socket = serverSocket.accept();
 */
public class TCPServerDemo {
    public static void main(String[] args) {
        try {
            System.out.println("服务端启动!");
            // 1.注册端口
            ServerSocket serverSocket = new ServerSocket(9999);
            while (true) {
                // 2.每接收一个客户端的Socket连接请求,交给一个独立子线程负责读取消息
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+"上线了");
                //创建子线程
                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));
            // 5.按行读取
            String msg;
            while ((msg = br.readLine())!= null){
                System.out.println(socket.getRemoteSocketAddress()+"  说了: "+msg);
            }
        } catch (IOException e) {
            System.out.println(socket.getRemoteSocketAddress()+"下线了");
            e.printStackTrace();
        }
    }
}


/**
 * Socket 网络编程
 *  客户端
 */
public class TCPClientDemo {
    public static void main(String[] args) {
        System.out.println("客户端启动!");
        try {
            //1.创建Socket通信管道请求服务端的连接
            Socket socket = new Socket("127.0.0.1", 9999);
            // 2.从socket通信管道中得到一个字节输出流
            OutputStream os = socket.getOutputStream();
            // 3.把低级字节流包装成打印流
            PrintStream ps = new PrintStream(os);
            //4.发送消息
            /**
             * 注意:消息按行读取 ps.print没有出现换行符,会报错Connection reset
             */
            //ps.print("TCP客户端,请求与你对接,是否同意。");
            Scanner scanner = new Scanner(System.in);
            while (true) {
                System.out.println("请输入要发送消息:");
                String msg = scanner.nextLine();
                if (msg.equals("exit")){
                    System.out.println("离线成功");
                    socket.close();
                    break;
                }
                ps.println(msg);
                ps.flush();
            }

            //关闭资源,不推荐
            //socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.4 线程池优化

上述代码存在问题:客户端过多,子线程过多;

4.4.1 使用线程池优势

  • 服务端可以复用线程,处理多个客户端,可以避免系统瘫痪;
  • 适合客户端通信时长较短的场景;
/**
 * Socket 网络编程
 *  客户端同上述
 */

/**
 * Socket 网络编程
 *  服务端 线程池优化
 */
public class TCPServerDemo {
    //使用静态变量定义一个线程池对象;最多处理5个,排队2个,第八个报错丢弃
    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(9999);
            while (true) {
                // 2.每接收一个客户端的Socket连接请求,交给一个独立子线程负责读取消息
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+"上线了");
                //创建任务
                ServerReaderRunnable runnable = new ServerReaderRunnable(socket);
                //任务送进线程池
                pool.execute(runnable);
            }

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


/**
 * 实现Runnable 将每个Socket连接请求打成任务
 */
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 (IOException e) {
            System.out.println(socket.getRemoteSocketAddress()+"下线了");
            e.printStackTrace();
        }
    }
}

4.5即时通信

4.5.1 即时通信含义,怎么设计

  • 即时通信,一个客户端消息发出去,其他客户端可以接收;
  • 即时通信需要进行端口转发的设计思想;
  • 服务端需要把在线的Socket管道存储起来;
  • 一旦收到消息,推送其他管道;
/**
 * 即时通信
 *  服务端 线程池优化
 */
public class TCPServerSMSDemo {
    //使用静态变量定义一个线程池对象;最多处理5个,排队2个,第八个报错丢弃
    private static ExecutorService pool = new ThreadPoolExecutor(3,5,6
            , TimeUnit.SECONDS,new ArrayBlockingQueue<>(2)
            , Executors.defaultThreadFactory()
            ,new ThreadPoolExecutor.AbortPolicy());

    //静态定义List集合存储当前全部在线管道
    public static List<Socket> allOnlineSockets = new ArrayList<>();

    public static void main(String[] args) {
        try {
            System.out.println("服务端启动!");
            // 1.注册端口
            ServerSocket serverSocket = new ServerSocket(9999);
            while (true) {
                // 2.每接收一个客户端的Socket连接请求,交给一个独立子线程负责读取消息
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+"上线了");
                //上线管道存入allOnlineSockets
                allOnlineSockets.add(socket);
                //创建任务
                ServerReaderRunnable runnable = new ServerReaderRunnable(socket);
                //任务送进线程池
                pool.execute(runnable);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
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);
                //消息转发给所有端口
                sendMessageToAll(msg);
            }
        } catch (IOException e) {
            System.out.println(socket.getRemoteSocketAddress()+"下线了");
            TCPServerSMSDemo.allOnlineSockets.remove(socket);
        }
    }

    private void sendMessageToAll(String msg) {
        for (Socket socket : TCPServerSMSDemo.allOnlineSockets) {
            try {
                PrintStream ps = new PrintStream(socket.getOutputStream());
                ps.println(msg);
                ps.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


/**
 * 即时通信
 *  客户端
 */
public class TCPClientSMSDemo {
    public static void main(String[] args) {
        System.out.println("客户端启动!");
        try {
            //1.创建Socket通信管道请求服务端的连接
            Socket socket = new Socket("127.0.0.1", 9999);
            //创建独立线程读消息
            new ClientReaderThread(socket).start();

            // 2.从socket通信管道中得到一个字节输出流
            OutputStream os = socket.getOutputStream();
            // 3.把低级字节流包装成打印流
            PrintStream ps = new PrintStream(os);
            //4.发送消息
            /**
             * 注意:消息按行读取 ps.print没有出现换行符,会报错Connection reset
             */
            //ps.print("TCP客户端,请求与你对接,是否同意。");
            Scanner scanner = new Scanner(System.in);
            while (true) {
                System.out.println("请输入要发送消息:");
                String msg = scanner.nextLine();
                if (msg.equals("exit")){
                    System.out.println("离线成功");
                    socket.close();
                    break;
                }
                ps.println(msg);
                ps.flush();
            }

            //关闭资源,不推荐
            //socket.close();
        } catch (IOException 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 (IOException e) {
            System.out.println("服务端把你踢出去了!!!");
            e.printStackTrace();
        }
    }
}

5.BS系统

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

5.1 TCP如何实现BS请求网页信息

  • 客户使用浏览器发起请求;
  • 服务端必须按照浏览器协议规则HTTP响应数据;
/**
 * TCP实现BS请求网页信息
 */
public class BSServerDemo {
    //使用静态变量定义一个线程池对象;最多处理5个,排队2个,第八个报错丢弃
    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 {
            ServerSocket serverSocket = new ServerSocket(8080);
            while (true){
                Socket socket = serverSocket.accept();
                ServerReaderRunnable runnable = new ServerReaderRunnable(socket);
                pool.execute(runnable);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class ServerReaderRunnable implements Runnable{
    private Socket socket;

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

    @Override
    public void run() {
        try {
            PrintStream ps = new PrintStream(socket.getOutputStream());
            ps.println("HTTP/1.1 200 ok");//协议类型和版本 状态码 状态码描述
            ps.println("Content-Type:text/html;charset=UTF-8");//响应的数据类型:文本/网页
            ps.println();//必须发送一个空行
            ps.println("<a href=\"http://www.runoob.com\">这是一个链接使用了 href 属性</a>");
            ps.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

RecordDate:2021/08/21