网络通信面试实战
Socket 工作原理
Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口,其实就是一个门面模式,将底层复杂的通信操作给封装起来对外提供接口。
简单来说就是 Socket 把 TPC/IP 协议给封装了起来,我们的程序进行网络通信都是通过 Socket 来完成的!
也就是说当两台设备进行通信时,是通过 Socket 进行通信的,接下来通过 Java 代码来了解一下如何通过 Socket 进行网络通信:
服务端:
public class Server {
public static void main(String[] args) {
int port = 1234; // 服务器监听的端口号
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("服务器启动,等待客户端连接...");
// 等待客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("客户端已连接:" + clientSocket.getInetAddress().getHostAddress());
// 获取输入流
InputStream input = clientSocket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
// 读取客户端发送的消息
String received = reader.readLine();
System.out.println("接收到消息: " + received);
// 获取输出流
OutputStream output = clientSocket.getOutputStream();
PrintWriter writer = new PrintWriter(output, true);
// 回显客户端发送的消息
writer.println("服务器回显: " + received);
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端:
public class Client {
public static void main(String[] args) {
String serverAddress = "localhost"; // 服务器地址
int port = 1234; // 服务器监听的端口号
try (Socket socket = new Socket(serverAddress, port)) {
System.out.println("连接到服务器...");
// 获取输出流
OutputStream output = socket.getOutputStream();
PrintWriter writer = new PrintWriter(output, true);
// 向服务器发送消息
writer.println("Hello, Server!");
// 获取输入流
InputStream input = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
// 读取服务器回显的消息
String response = reader.readLine();
System.out.println("接收到服务器的回显: " + response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
BIO、NIO和AIO
面试中问到网络相关的内容,其中 BIO、NIO 的内容肯定是必问的,AIO 可以了解一下,一定要清楚 BIO 和 NIO 中通信的流程
我也画了两张图,可以记下这两张图
- AIO:
从 Java.1.7 开始,Java 提供了 AIO(异步IO),Java 的 AIO 也被称为 “NIO.2”
Java AIO 采用订阅-通知
模式,应用程序向操作系统注册 IO 监听,之后继续做自己的事情,当操作系统发生 IO 事件并且已经准备好数据时,主动通知应用程序,应用程序再进行相关处理
(Linux 平台没有这种异步 IO 技术,而是使用 epoll 对异步 IO 进行模拟)
- BIO:
BIO 即同步阻塞 IO,服务端实现模式为一个连接对应一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理
BIO简单工作流程:
- 服务器端启动一个 ServerSocket,用于接收客户端的连接
- 客户端启动 Socket 与服务器建立连接,默认情况下服务器端需要对每个客户端建立一个线程与之通讯(并且与每一个可u后端有一个对应的 Socket)
- 客户端发出请求后, 先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝
- 如果服务端有对应线程处理
- 客户端进行读取,则线程会被阻塞直到完成读取
- 客户端进行写入,则线程会被阻塞直到完成写入
使用 BIO 通信的流程图如下:
BIO存在问题:
- 当并发量较大时,需要创建大量线程来处理连接,比较占用系统资源
- 连接建立之后,如果当前线程暂时没有数据可读,则线程会阻塞在 Read 操作上,造成线程资源浪费
- NIO:
从 Java1.4 开始,Java 提供了 NIO,NIO 即 “Non-blocking IO”(同步非阻塞IO)
NIO 的几个核心概念:
- Channel、Buffer:BIO是基于字节流或者字符流的进行操作,而NIO 是基于
缓冲区
和通道
进行操作的,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中 - Selector:选择器用于监听多个通道的事件(如,连接打开,数据到达),因此,单个线程可以监听多个数据通道,极大提升了单机的并发能力
当 Channel 上的 IO 事件未到达时,线程会在 select 方法被挂起,让出 CPU 资源,直到监听到 Channel 有 IO 事件发生,才会进行相应的处理
- NIO和BIO有什么区别?
- NIO是以
块
的方式处理数据,BIO是以字节流或者字符流
的形式去处理数据。 - NIO是通过
缓存区和通道
的方式处理数据,BIO是通过InputStream和OutputStream流
的方式处理数据。 - NIO的通道是双向的,BIO流的方向只能是单向的。
- NIO采用的多路复用的同步非阻塞IO模型,BIO采用的是普通的同步阻塞IO模型。
- NIO的效率比BIO要高,NIO适用于网络IO,BIO适用于文件IO。
NIO如何实现了同步非阻塞?
通过 Selector 和 Channel 来进行实现,一个线程使用一个 Selector 监听多个 Channel 上的 IO 事件,通过配置监听的通道Channel为非阻塞,那么当Channel上的IO事件还未到达时,线程会在select方法被挂起,让出CPU资源。直到监听到Channel有IO事件发生时,才会进行相应的响应和处理。
使用 NIO 通信的流程图如下: