Java网络编程 -- BIO 阻塞式网络编程

时间:2024-01-25 10:32:43

阻塞IO的含义

阻塞(blocking)IO :阻塞是指结果返回之前,线程会被挂起,函数只有在得到结果之后(或超时)才会返回

非阻塞(non-blocking)IO :非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回

同步(synchronous)IO :应用阻塞在发送或接受数据的状态,直至数据成功传输(或返回失败),简单来说就是必须一件一件事做,等前一件做完了才能做下一件事

异步(asynchronous)IO :应用发送或接受数据后立即返回,实际处理这个调用的程序在完成后,通过状态、通知和回调来通知调用者

阻塞和非阻塞是获取资源的方式,同步和异步是程序如何处理资源的逻辑。

BIO网络编程

首先我们来看一段最基础的Java网络编程代码示例:

服务器端代码示例:

public class BIOServerV1 {

  public static void main(String[] args) throws Exception {
    ServerSocket serverSocket = new ServerSocket(8080);
    System.out.println("服务器启动成功");
    while (!serverSocket.isClosed()) {
      Socket request = serverSocket.accept(); // 阻塞
      System.out.println("收到新连接:" + request.toString());
      try {
        InputStream inputStream = request.getInputStream(); // 获取数据流
        BufferedReader bufferedReader =
            new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
        String message;
        while ((message = bufferedReader.readLine()) != null) {
          if (message.length() == 0) {
            break;
          }
          System.out.println("消息内容为:" + message);
        }
        System.out.println("收到数据,来自:" + request.toString());
      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        request.close();
      }
    }

    serverSocket.close();
  }
}

客户端代码示例:

public class BIOClient {

  public static void main(String[] args) throws IOException {
    Socket socket = new Socket("localhost", 8080);
    OutputStream outputStream = socket.getOutputStream();

    Scanner scanner = new Scanner(System.in);
    System.out.println("请输入:");
    String message = scanner.nextLine();
    outputStream.write(message.getBytes(Charset.forName("UTF-8")));
    scanner.close();
    socket.close();
  }
}

这个版本服务器端的代码同一时刻只能支持一个网络连接,在建立连接之后服务端线程会被阻塞,只有在已建立连接的客户端处理完数据关闭连接之后,后续的连接请求才能一个一个的处理,而为了能并发的处理多个请求我们在下一个版本中加入多线程的代码。

public class BIOServerV2 {

  private static ExecutorService executorService = Executors.newCachedThreadPool();

  public static void main(String[] args) throws IOException {
    ServerSocket serverSocket = new ServerSocket(8080);
    System.out.println("服务器启动成功");
    while (!serverSocket.isClosed()) {
      Socket request = serverSocket.accept();
      System.out.println("收到新连接:" + request.toString());

      // 多线程接收多个连接
      executorService.submit(
          () -> {
            try {
              InputStream inputStream = request.getInputStream();
              BufferedReader bufferedReader =
                  new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
              String message;
              while ((message = bufferedReader.readLine()) != null) {
                if (message.length() == 0) {
                  break;
                }
                System.out.println("消息内容为:" + message);
              }
              System.out.println("收到数据,来自:" + request.toString());
            } catch (IOException e) {
              e.printStackTrace();
            } finally {
              try {
                request.close();
              } catch (IOException e) {
                e.printStackTrace();
              }
            }
          });
    }
    serverSocket.close();
  }
}

这个版本的代码在加入多线程后可以并发的处理多个连接,但是它只能处理Java客户端的连接不能处理浏览器端的连接,而为了能与浏览器端交互我们需要了解HTTP协议的内容。

HTTP协议

HTTP协议请求数据包解析

img

HTTP协议响应数据包解析

img

HTTP协议响应状态码:

状态码 描述
1xx 临时响应,表示临时响应并需要请求者继续执行操作的状态码
2xx 成功,表示成功处理了请求的状态码
3xx 重定向,表示要完成请求,需要进一步操作。通常,这些状态码用来重定向
4xx 请求错误,这些状态码表示请求可能出错,妨碍了服务器的处理
5xx 服务器错误,这些状态码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错

BIO网络编程处理浏览器请求

在了解了HTTP协议的内容之后我们就可以依据HTTP协议的内容编写程序来处理浏览器请求。在之前多线程版本的代码之上我们需要对数据根据HTTP协议的内容进行处理,代码示例如下:

public class BIOServerV3 {

  private static ExecutorService executorService = Executors.newCachedThreadPool();

  public static void main(String[] args) throws IOException {
    ServerSocket serverSocket = new ServerSocket(8080);
    System.out.println("服务器启动成功");
    while (!serverSocket.isClosed()) {
      Socket request = serverSocket.accept();
      System.out.println("收到新连接:" + request.toString());

      // 多线程接收多个连接
      executorService.submit(
          () -> {
            try {
              InputStream inputStream = request.getInputStream();
              BufferedReader bufferedReader =
                  new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
              String message;
              while ((message = bufferedReader.readLine()) != null) {
                if (message.length() == 0) {
                  break;
                }
                // 拿到消息后可以解析消息拿到请求方法,请求数据等内容
                System.out.println("消息内容为:" + message);
              }
              System.out.println("收到数据,来自:" + request.toString());

              // 根据HTTP协议响应数据包返回数据给浏览器
              OutputStream outputStream = request.getOutputStream();
              outputStream.write("HTTP/1.1 200 OK\r\n".getBytes());
              outputStream.write("Content-Length: 11\r\n\r\n".getBytes());
              outputStream.write("Hello World".getBytes());
              outputStream.flush();

            } catch (IOException e) {
              e.printStackTrace();
            } finally {
              try {
                request.close();
              } catch (IOException e) {
                e.printStackTrace();
              }
            }
          });
    }
    serverSocket.close();
  }
}

以上就是Java BIO网络编程的基本内容,对于BIO来说一个请求对应一个线程,上下文切换占用的资源很重,同时由于大量并发情况下,其他接入的消息,只能一直等待,而目前对于性能,响应速度等的却要求越老越高,BIO网络编程使用的已经越来越少。使用的比较多的是Java NIO网络编程,该部分内容我们将在下一部分继续。