02_Java语音进阶||day11_网络编程

第一章 网络编程入门

1.1 软件结构


  • 两种架构各有优势,但是无论哪种架构,都离不开网络的支持“网络编程”就是在一定的协议下,实现两台计算机的通信的程序

1.2 网络编程三要素_网络通信协议(规则)


  • 协议:计算机网络通信必须遵守的规则。

1.3 网络通信协议分类

  • 消耗资源小,通信效率高
  • 可能会出现数据的丢失


  • 三次握手
    • 第一次:客户端服务器端发出连接请求
    • 第二次:服务器端客户端回送一个响应
    • 第三次:客户端再次向服务器端发送确认信息,确认连接

1.4 网络编程三要素_IP地址

  1. IP地址:指互联网协议地址(Internet protecol Address),俗称IP。
    • IP地址用来给一个网络中的计算机设备做唯一的编号。


2. 注意事项:
1. 常用命令:
* 查看本机IP地址:ipconfig
* 检查网络是否连通:ping 222.198.34.77
2. 特殊的IP地址
* 本机IP地址:127.0.0.1,localhost
3. IP就是电脑的唯一标识

1.5 网络编程三要素_端口号


  1. 注意事项:
    1. 1024之前的端口号我们不能使用,已经被系统分配给已知的网络软件了
    2. 网络软件的端口号不能重复。
  2. 常用的端口号
    1. 80端口:网络端口
    2. 数据库
      • MySQL:3306
      • oracle:1521
    3. Tomcat服务器:8080

第二章 TCP通信程序

2.1 TCP通信的概述(上)


  1. 注意事项:
    1. TCP通信:面向连接的通信,客户端和服务器端必须经过3次握手,建立逻辑连接,才能通信(安全)
    2. 客户端和服务器端建立逻辑连接包含一个IO对象
    3. 通信数据不止是字符,所以是字节流对象【重点】
    4. 客户端和服务器端进行一个数据交互,需要4个IO流对象

2.2 TCP通信的概述(下)

  1. 服务器端明确的两件事:
    1. 多个客户端同时和服务器器端交互,服务器端必须明确和那个客户端进行的交互
      • 服务器端有个方法,accept【!】:服务器端获取请求的客户端对象
          Socket s1 = server.accept();//指定一台客户端
          Socket s2 = server.accept();//指定另一台客户端
      
    2. 多个客户端同时和服务器端交互,就需要使用多个IO流对象
      • 服务器端使用客户端的流和客户端交互【重点】

2.3 TCP通信的客户端代码实现

  1. TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据
    • 表示客户端的类:
      • java.net.Socket:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
      • 套接字:包含了IP地址和端口号的网络单位(相当于电脑)
  2. 构造方法:
    • Socket(String host, int port):创建一个流套接字并将其连接到指定主机上的指定端口号。
    1. 参数:
      • String host:服务器主机的名称/服务器的IP地址
      • int port:服务器的端口号
  3. 成员方法:
    • OutputStream getOutputStream() 返回此套接字的输出流。
    • InputStream getInputStream() 返回此套接字的输入流。
    • void close() 关闭此套接字。
  4. 实现步骤【重点】:
    1. 【新】创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
      • 可以随机指定端口号,但是想访问某个服务器需要和该服务器同步
    2. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
    3. 使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
    4. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
    5. 使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
    6. 【新】释放资源(Socket)
  5. 注意:
    1. 客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
    2. 当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过3次握手建立连接通路
      • 这时如果服务器没有启动,那么就会抛出异常ConnectException: Connection refused: connect
      • 如果服务器已经启动,那么就可以进行交互了
    public static void main(String[] args) throws IOException {
        //1. 创建一个客户端==对象Socket==,==构造方法绑定服务器的IP地址和端口号==
        Socket socket = new Socket("127.0.0.1", 8888);
        //2. 使用==Socket对象中的方法getOutputStream==()获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //3. 使用==网络字节输出流OutputStream对象中的方法write==,给服务器发送数据
        os.write("你好服务器".getBytes());

        //还没有建立服务器——这里只创建了客户端
        //4. 使用==Socket对象中的方法getInputStream==()获取网络字节输入流InputStream对象
        //5. 使用==网络字节输入流InputStream对象中的方法read==,读取服务器回写的数据
        //6. 释放资源(Socket)
        socket.close();
    }

2.4 TCP通信的服务器端代码实现

  1. TCP通信的服务器端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据
    • 表示服务器的类:
      • java.net.ServerSocket:此类实现服务器套接字。
  2. 构造方法:
    • ServerSocket(int port) 创建绑定到特定端口的服务器套接字。
  3. 服务器端必须明确一件事情,必须的知道是哪个客户端请求的服务器
    • 所以可以使用accept方法获取到请求的客户端对象Socket
    • 成员方法:
      • Socket accept() 侦听并接受到此套接字的连接。
  4. 服务器的实现步骤:
    1. 【新】创建服务器ServerSocket对象和系统要指定的端口号
      • 和系统要指定的端口号
    2. 【新】使用ServerSocket对象中的++方法accept++,获取到请求的客户端对象Socket
      • 返回的就是一个socket
    3. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
    4. 使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
    5. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
    6. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写数据
      • 回写数据
    7. 【新】释放资源(Socket,ServerSocket)
  5. 注意:
    1. 先启动服务器端
    2. 服务器端代码启动后一直运行
    //定义一个服务器端:TCPServer.java
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class TCPServer {
        public static void main(String[] args) throws IOException {
            //1. 创建服务器ServerSocket对象和系统要指定的端口号
            ServerSocket server = new ServerSocket(8888);
            //2. 使用ServerSocket对象中的++方法accept++,获取到请求的客户端对象Socket
            Socket socket = server.accept();
            //3. 使用==Socket对象中的方法getInputStream==()获取网络字节输入流InputStream对象
            InputStream is = socket.getInputStream();
            //4. 使用网络字节输入流InputStream对象中的==方法read==,读取客户端发送的数据
            byte[] bytes = new byte[1024];
            int len = is.read(bytes);   //表示读取一次就够了,因为我们只传了一行文字
            System.out.println(new String(bytes, 0, len));
            //5. 使用==Socket对象中的方法getOutputStream==()获取网络字节输出流OutputStream对象
            OutputStream os = socket.getOutputStream();
            //6. 使用网络字节输出流OutputStream对象中的==方法write==,给客户端回写数据
            os.write("收到谢谢".getBytes());
            //7. 释放资源(==Socket,ServerSocket==)
            socket.close();
            server.close();
        }
    }
    
    //定义一个客户端:TCPClient.java
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    
    public class TCPClient {
        public static void main(String[] args) throws IOException {
            //1. 创建一个客户端==对象Socket==,==构造方法绑定服务器的IP地址和端口号==
            Socket socket = new Socket("127.0.0.1", 8888);
            //2. 使用==Socket对象中的方法getOutputStream==()获取网络字节输出流OutputStream对象
            OutputStream os = socket.getOutputStream();
            //3. 使用==网络字节输出流OutputStream对象中的方法write==,给服务器发送数据
            os.write("你好服务器".getBytes());
    
            //这时建立了服务器
            //4. 使用==Socket对象中的方法getInputStream==()获取网络字节输入流InputStream对象
            InputStream is = socket.getInputStream();
            //5. 使用==网络字节输入流InputStream对象中的方法read==,读取服务器回写的数据
            byte[] bytes = new byte[1024];
            int len = is.read(bytes);   //因为只需要接收一句话,所以读取一次就行了
            System.out.println(new String(bytes, 0, len));
            //6. 释放资源(Socket)
            socket.close();
        }
    }
    
    //客户端的结果:
    收到谢谢
    //服务器端的结果:
    你好服务器

第三章 综合案例

3.1文件上传案例


3.2 综合案例_文件上传案例的客户端

  1. 文件上传案例的客户端:读取本地文件,上传到服务器,读取服务器回写的数据
    • 明确:
      • 数据源:F:\picture\1.jpg
      • 目的地:服务器
  2. 实现步骤:
    1. 创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
    2. 创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
      • IP–>字符
      • 端口号–>整型
    3. 使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象
    4. 使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
    5. 使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
    6. 使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象
    7. 使用网络字节输入流InputStream对象中的方法read读取服务回写的数据
    8. 释放资源(FileInputStream,Socket)
    public class TCPClient {
        public static void main(String[] args) throws IOException {
            //1. 创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
            FileInputStream fis = new FileInputStream("F:\\picture\\1.jpg");
            //2. 创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
            Socket socket = new Socket("127.0.0.1", 8888);
            //3. 使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象
            OutputStream os = socket.getOutputStream();
            //4. 使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
            byte[] bytes = new byte[1024];
            int len = 0;
            while((len = fis.read(bytes)) != -1){
                //5. 使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
                os.write(bytes, 0, len);
            }
            //6. 使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象
            InputStream is = socket.getInputStream();
            //7. 使用网络字节输入流InputStream对象中的方法read读取服务回写的数据
            while ((len = is.read(bytes)) != -1){
                System.out.println(new String(bytes, 0, len));
            }
            //8. 释放资源(FileInputStream,Socket)
            fis.close();
            socket.close();
        }
    }

3.3 综合案例_文件上传案例的服务器端

  1. 文件上传案例服务器端:读取客户端上传的文件,保存到服务器的硬盘,给客户端回写"上传成功"
    • 明确:
      • 数据源:客户端上传的文件
      • 目的地:服务器的硬盘 E:\picture\1.jpg
  2. 实现步骤:
    1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
      • 端口号–>整数
    2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
    3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
    4. 判断E:\picture文件夹是否存在,不存在则创建
          File file = new File("E:\\picture");
          if(!file.exists()){ //是否存在
              file.mkdirs();  //创建一个文件
          }
      
    5. 创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
          FileOutputStream fos = new FileOutputStream(file + "\\1.jpg");
      
      • 注:开始位置加文件分隔符
    6. 使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
    7. 使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
    8. 使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
    9. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写"上传成功"
    10. 释放资源(FileOutputStream,Socket,ServerSocket)
    public class TCPServer {
        public static void main(String[] args) throws IOException {
            //1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
            ServerSocket server = new ServerSocket(8888);
            //2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
            Socket socket = server.accept();
            //3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
            InputStream is = socket.getInputStream();
            //4. 判断E:\\picture文件夹是否存在,不存在则创建
            File file = new File("E:\\picture");
            if(!file.exists()){ //是否存在
                file.mkdirs();  //创建一个文件
            }
            //5. 创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
            FileOutputStream fos = new FileOutputStream(file + "\\1.jpg");
            //6. 使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
            byte[] bytes = new byte[1024];
            int len = 0;
            while((len = is.read(bytes)) != -1){
                //7. 使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
                fos.write(bytes, 0, len);
            }
            /*//8. 使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
            OutputStream os = socket.getOutputStream();
            //9. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写"上传成功"
            os.write("上传成功".getBytes());*/
            //8,9合成一个
            socket.getOutputStream().write("上传成功".getBytes());
            //10. 释放资源(FileOutputStream,Socket,ServerSocket)
            fos.close();
            socket.close();
            server.close();
        }
    }
  • 注:++问题:程序运行成功,但是执行完不能停止问题++

3.4 综合案例_文件上传案例阻塞问题

  1. 上述问题不能停止–>阻塞
  2. 客户端:本地字节输入流中
    1. fis.read(bytes):读取本地文件,结束标记是读取到-1结束
    2. while循环不会读取到-1
    3. 那么也不会把结束标记写给服务器
  3. 服务器端:网络字节输入流中
    1. is.read():读取客户端上传的文件,永远也读取不到文件的结束标记
    2. read方法进入到阻塞状态,一直死循环等待结束标记
    3. 8,9,10代码就不会执行,也不会给客户端回写上传成功
  4. 客户端:网络字节输入流中
    1. is.read():读取不到服务器端回写的数据,进入到阻塞状态
  5. 解决方法【重点】:–解决因无法上传结束标记而导致无法停止的阻塞状态
    1. 上传完文件,给服务器写一个结束标记
    2. void shutdownOutput():禁用此套接字的输出流。(java.net包中,Socket类的方法)
      • 对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。
    3. 写在–>客户端:本地字节输入流–>后面
        //改3.2客户端代码:在本地字节输入流后面
        //4. 使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = fis.read(bytes)) != -1){
            //5. 使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
            os.write(bytes, 0, len);
        }
        
        socket.shutdownOutput();
    
    //3.2完整代码
    public static void main(String[] args) throws IOException {
        //1. 创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("F:\\picture\\1.jpg");
        //2. 创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1", 8888);
        //3. 使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //4. 使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = fis.read(bytes)) != -1){
            //5. 使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
            os.write(bytes, 0, len);
        }
        /*5. ==解决方法【重点】==:--解决因无法上传结束标记而导致无法停止的阻塞状态
            1. 上传完文件,给服务器写一个结束标记
            2. void ==shutdownOutput==():禁用此套接字的输出流。(java.net包中,socket类的方法)
                * 对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。
            3. 写在-->客户端:本地字节输入流-->后面
        */
        socket.shutdownOutput();

        //6. 使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //7. 使用网络字节输入流InputStream对象中的方法read读取服务回写的数据
        while ((len = is.read(bytes)) != -1){
            System.out.println(new String(bytes, 0, len));
        }
        //8. 释放资源(FileInputStream,Socket)
        fis.close();
        socket.close();
    }

3.5 综合案例_文件上传案例优化(文件命名&循环接收&多线程提高效率)

  1. 解决问题一:文件命名
    • 每次运行一次,同名文件就被覆盖
    1. 自定义一个文件的命名规则:防止同名的文件被覆盖
      • 规则:域名+毫秒值+随机数
        /*
            自定义一个文件的命名规则:防止同名的文件被覆盖
            规则:域名+毫秒值+随机数
        */
        String fileName = "oop" + System.currentTimeMillis() + new Random().nextInt(999999) + ".jpg";
        //5. 创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
        FileOutputStream fos = new FileOutputStream(file + "\\" + fileName);
    
  2. 解决问题二:服务器用一次就关闭了
    1. 让服务器一直处于监听状态(死循环accept方法)
      • 有一个客户端上传文件,就保存一个文件
    2. 所以服务器就不用关闭了
      • 不用写:server.close();
        public static void main(String[] args) throws IOException {
            //1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
            ServerSocket server = new ServerSocket(8888);
    
            /*
            让服务器一直处于监听状态(死循环accept方法)
                * 有一个客户端上传文件,就保存一个文件
             */
            while (true){
                //2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
                Socket socket = server.accept();
                //3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
                InputStream is = socket.getInputStream();
                //4. 判断E:\\picture文件夹是否存在,不存在则创建
                File file = new File("E:\\picture");
                if(!file.exists()){ //是否存在
                    file.mkdirs();  //创建一个文件
                }
    
                /*
                    自定义一个文件的命名规则:防止同名的文件被覆盖
                    规则:域名+毫秒值+随机数
                */
                String fileName = "oop" + System.currentTimeMillis() + new Random().nextInt(999999) + ".jpg";
                //5. 创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
                FileOutputStream fos = new FileOutputStream(file + "\\" + fileName);
                //6. 使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
                byte[] bytes = new byte[1024];
                int len = 0;
                while((len = is.read(bytes)) != -1){
                    //7. 使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
                    fos.write(bytes, 0, len);
                }
                /*//8. 使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
                OutputStream os = socket.getOutputStream();
                //9. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写"上传成功"
                os.write("上传成功".getBytes());*/
                //8,9合成一个
                socket.getOutputStream().write("上传成功".getBytes());
                //10. 释放资源(FileOutputStream,Socket,ServerSocket)
                fos.close();
                socket.close();
            }
    
            //服务器就不用关闭
            //server.close();
        }
    
  3. 解决问题三:使用多线程来提高效率
    1. 使用多线程技术,提高程序的效率
      • 有一个客户端上传文件,就开启一个线程,完成文件的上传while
    2. 注:实现Runnable接口,重写run方法时候【重点】
      • 接口中的run方法没有声明抛出异常
      • 所以子类重写方法是也不能声明抛出异常
      • 需要用到try…catch
        • catch中写:IOException
    3. 还可以继续优化,将两个关闭写到finally中(自愿)
        public static void main(String[] args) throws IOException {
            //1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
            ServerSocket server = new ServerSocket(8888);
    
            /*
            让服务器一直处于监听状态(死循环accept方法)
                * 有一个客户端上传文件,就保存一个文件
             */
            while (true){
                //2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
                Socket socket = server.accept();
    
                /*
                    使用多线程技术,提高程序的效率
                    有一个客户端上传文件,就开启一个线程,完成文件的上传
                 */
                new Thread(new Runnable() {
                    //完成文件的上传
                    @Override
                    public void run() {
                        try {
                            //3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
                            InputStream is = socket.getInputStream();
                            //4. 判断E:\\picture文件夹是否存在,不存在则创建
                            File file = new File("E:\\picture");
                            if(!file.exists()){ //是否存在
                                file.mkdirs();  //创建一个文件
                            }
    
                            /*
                                自定义一个文件的命名规则:防止同名的文件被覆盖
                                规则:域名+毫秒值+随机数
                            */
                            String fileName = "oop" + System.currentTimeMillis() + new Random().nextInt(999999) + ".jpg";
                            //5. 创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
                            FileOutputStream fos = new FileOutputStream(file + "\\" + fileName);
                            //6. 使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
                            byte[] bytes = new byte[1024];
                            int len = 0;
                            while((len = is.read(bytes)) != -1){
                                //7. 使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
                                fos.write(bytes, 0, len);
                            }
                            /*//8. 使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
                            OutputStream os = socket.getOutputStream();
                            //9. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写"上传成功"
                            os.write("上传成功".getBytes());*/
                            //8,9合成一个
                            socket.getOutputStream().write("上传成功".getBytes());
                            //10. 释放资源(FileOutputStream,Socket,ServerSocket)
                            fos.close();
                            socket.close();
                        }catch (IOException e){
                            System.out.println(e);
                        }
                    }
                }).start();
    
    
    
            }
    
            //服务器就不用关闭
            //server.close();
        }
    

3.6 模拟BS服务器分析

    //定义了一个服务器(默认状态)
    public static void main(String[] args) throws IOException {
        //1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
        ServerSocket server = new ServerSocket(8080);
        //2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象(浏览器)
        Socket socket = server.accept();
        //3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //4. 使用网络字节输入流InputStream对象中的方法read,读取客户端的请求信息
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = is.read(bytes)) != -1){
            System.out.println(new String(bytes, 0, len));
        }

    }
    
    //网页访问http://127.0.0.1:8080/day11/web/index.html是回显
    GET /day11/web/index.html HTTP/1.1
    Host: 127.0.0.1:8080
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3754.400 QQBrowser/10.5.3991.400
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9

3.7 模拟BS服务器代码实现

  1. 3.6中网络字节输入流注释掉(注释部分用来读取客户端的请求信息,如:路径),改为如下(2,3,4,5,6)步骤:
        原来的:
        //1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
        ServerSocket server = new ServerSocket(8080);
        //2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象(浏览器)
        Socket socket = server.accept();
        //3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
    
  2. 获取回写内容的第一行中的路径,步骤:
    1. 把is网络字节输入流对象(InputStream),转换为字符缓冲输入流(BufferedReader)
      • InputStreamReader是字节流通向字符流的桥梁
    2. 把客户端请求信息的第一行读取出来 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/index.html HTTP/1.1
      • 用到BufferedReader特有的方法,readLine–>读取一行,返回类型字符串
    3. 把读取的信息进行切割,只要中间部分 /Dark-horse-teaching/src/cn/javaadvance/day11/web/index.html
      • 用String中的split方法,用空格来切割–>返回的是字符串数组
    4. 把路径前面的/去掉,进行截取 Dark-horse-teaching/src/cn/javaadvance/day11/web/index.html
      • 用String中的substring方法,从1开始截取保留后面的
        //获取回写内容的第一行中的路径步骤:
        //1. 把is网络字节输入流对象(InputStream),转换为字符缓冲输入流(BufferedReader)
        //InputStreamReader是字节流通向字符流的桥梁
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        //2. 把客户端请求信息的第一行读取出来 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/index.html HTTP/1.1
        //用到BufferedReader特有的方法,readLine-->读取一行,返回类型字符串
        String line = br.readLine();
        //3. 把读取的信息进行切割,只要中间部分 /Dark-horse-teaching/src/cn/javaadvance/day11/web/index.html
        //用String中的split方法,用空格来切割-->返回的是字符串数组
        String[] arr = line.split(" ");
        //4. 把路径前面的/去掉,进行截取 Dark-horse-teaching/src/cn/javaadvance/day11/web/index.html
        //用String中的substring方法,从1开始截取保留后面的
        String htmlpath = arr[1].substring(1);  //这里出来的就是html的路径了
    
  3. 根据路径读取本地文件,步骤:
    1. 创建一个本地字节输入流,构造方法中绑定要读取的html路径
    2. 使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
        // 根据路径读取本地文件,步骤:
        //1. 创建一个本地字节输入流,构造方法中绑定要读取的html路径
        FileInputStream fis = new FileInputStream(htmlpath);
        //2. 使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
    
  4. 写入HTTP协议响应头,固定写法(这里加上就行,后面会讲):
        os.write("HTTP/1.1 200 OK\r\n".getBytes());
        os.write("Content-Type:text/html\r\n".getBytes());
        // 必须要写入空行,否则浏览器不解析
        os.write("\r\n".getBytes());
    
  5. 一读一写复制文件,把服务器端读取的html文件回写到客户端(页面上)
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = fis.read(bytes)) != -1){
            os.write(bytes, 0, len);
        }
    
  6. 释放资源
        fis.close();
        socket.close();
        server.close();
    
    //定义了一个服务器:TCPServer.java
    public static void main(String[] args) throws IOException {
        //1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
        ServerSocket server = new ServerSocket(8080);
        //2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象(浏览器)
        Socket socket = server.accept();
        //3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //4. 使用网络字节输入流InputStream对象中的方法read,读取客户端的请求信息
        /*byte[] bytes = new byte[1024];
        int len = 0;
        while((len = is.read(bytes)) != -1){
            System.out.println(new String(bytes, 0, len));
        }*/

        //获取回写内容的第一行中的路径步骤:
        //1. 把is网络字节输入流对象(InputStream),转换为字符缓冲输入流(BufferedReader)
        //InputStreamReader是字节流通向字符流的桥梁
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        //2. 把客户端请求信息的第一行读取出来 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/index.html HTTP/1.1
        //用到BufferedReader特有的方法,readLine-->读取一行,返回类型字符串
        String line = br.readLine();
        //3. 把读取的信息进行切割,只要中间部分 /Dark-horse-teaching/src/cn/javaadvance/day11/web/index.html
        //用String中的split方法,用空格来切割-->返回的是字符串数组
        String[] arr = line.split(" ");
        //4. 把路径前面的/去掉,进行截取 Dark-horse-teaching/src/cn/javaadvance/day11/web/index.html
        //用String中的substring方法,从1开始截取保留后面的
        String htmlpath = arr[1].substring(1);  //这里出来的就是html的路径了

        // 根据路径读取本地文件,步骤:
        //1. 创建一个本地字节输入流,构造方法中绑定要读取的html路径
        FileInputStream fis = new FileInputStream(htmlpath);
        //2. 使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();

        // 写入HTTP协议响应头,固定写法
        os.write("HTTP/1.1 200 OK\r\n".getBytes());
        os.write("Content-Type:text/html\r\n".getBytes());
        // 必须要写入空行,否则浏览器不解析
        os.write("\r\n".getBytes());

        //一读一写复制文件,把服务器端读取的html文件回写到客户端(页面上)
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = fis.read(bytes)) != -1){
            os.write(bytes, 0, len);
        }

        //释放资源
        fis.close();
        socket.close();
        server.close();
    }
  • 结果:

3.8 3.7中服务器执行后,页面图片不显示问题

  1. 原因:
    1. 浏览器解析服务器回写的html页面,页面中如果有图片,那么浏览器就会单独的开启一个线程,读取服务器的图片
      • 我们就的让服务器一直处于监听状态,客户端请求一次,服务器就回写一次
      • 同3.5问题三
          1. 使用多线程技术,提高程序的效率
              * 有一个客户端上传文件,就开启一个线程,完成文件的上传while
          2. 注:实现Runnable接口,重写run方法时候【重点】
              * 接口中的run方法没有声明抛出异常
              * 所以子类重写方法是也不能声明抛出异常
              * 需要用到try...catch
                  * catch中写:IOException
      
    //定义一个服务器:TCPServerThread.java
    public static void main(String[] args) throws IOException {
        //1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
        ServerSocket server = new ServerSocket(8080);

        /*
            浏览器解析服务器回写的html页面,页面中如果有图片,那么浏览器就会单独的开启一个线程,读取服务器的图片
            我们就的让服务器一直处于监听状态
         */
        while (true){
            //2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象(浏览器)
            Socket socket = server.accept();

            /*
                使用多线程技术,提高程序的效率
                客户端请求一次,服务器就回写一次
             */
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
                        InputStream is = socket.getInputStream();

                        //获取回写内容的第一行中的路径步骤:
                        //1. 把is网络字节输入流对象(InputStream),转换为字符缓冲输入流(BufferedReader)
                        //InputStreamReader是字节流通向字符流的桥梁
                        BufferedReader br = new BufferedReader(new InputStreamReader(is));
                        //2. 把客户端请求信息的第一行读取出来
                        //用到BufferedReader特有的方法,readLine-->读取一行,返回类型字符串
                        String line = br.readLine();
                        //System.out.println(line); //有一张图片就请求一次(演示)
                        //3. 把读取的信息进行切割,只要中间部分
                        //用String中的split方法,用空格来切割-->返回的是字符串数组
                        String[] arr = line.split(" ");
                        //4. 把路径前面的/去掉,进行截取
                        //用String中的substring方法,从1开始截取保留后面的
                        String htmlpath = arr[1].substring(1);  //这里出来的就是html的路径了

                        // 根据路径读取本地文件,步骤:
                        //1. 创建一个本地字节输入流,构造方法中绑定要读取的html路径
                        FileInputStream fis = new FileInputStream(htmlpath);
                        //2. 使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
                        OutputStream os = socket.getOutputStream();

                        // 写入HTTP协议响应头,固定写法
                        os.write("HTTP/1.1 200 OK\r\n".getBytes());
                        os.write("Content-Type:text/html\r\n".getBytes());
                        // 必须要写入空行,否则浏览器不解析
                        os.write("\r\n".getBytes());

                        //一读一写复制文件,把服务器端读取的html文件回写到客户端(页面上)
                        byte[] bytes = new byte[1024];
                        int len = 0;
                        while((len = fis.read(bytes)) != -1){
                            os.write(bytes, 0, len);
                        }

                        //释放资源
                        fis.close();
                        socket.close();
                    }catch (IOException e){
                        System.out.println(e);
                    }
                }
            }).start();

        }


        //服务器就不用关闭
        //server.close();
    }
  • 结果:


2. 客户端请求信息演示(有一个图片请求一次)
* 在readLine读取一行的方法后面输出line
结果: GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/index.html HTTP/1.1 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/img/logo2.png HTTP/1.1 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/img/header.jpg HTTP/1.1 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/img/1.jpg HTTP/1.1 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/img/title2.jpg HTTP/1.1 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/img/small03.jpg HTTP/1.1 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/img/middle01.jpg HTTP/1.1 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/img/big01.jpg HTTP/1.1 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/img/ad.jpg HTTP/1.1 GET /Dark-horse-teaching/src/cn/javaadvance/day11/web/img/footer.jpg HTTP/1.1
* 除了第一次是请求客户端信息外,后面每有一个图片就请求一次

发布了42 篇原创文章 · 获赞 6 · 访问量 1128

猜你喜欢

转载自blog.csdn.net/qq_40572023/article/details/104830615