Socket
说起NIO就不得不说Socket这个一个抽象类门面类,它对TCP/IP协议进行了封装,对于我们来说只需要调用其提供的接口就可以实现。比如,我们创建了一个监听本地8080端口的socket服务端,这时用浏览器请求,则可以看到下面的打印信息。
ServerSocket serverSocket = new ServerSocket(8080);
while (true){
Socket socket = serverSocket.accept();
InputStream input = socket.getInputStream();
// 读取客户端发送的数据
byte[] buffer = new byte[1024];
int len = input.read(buffer);
String request = new String(buffer, 0, len);
System.out.println(request);
OutputStream output = socket.getOutputStream();
String response = "Hello, client!";
output.write(response.getBytes());
output.flush();
}
GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
sec-ch-ua: "Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
...
由上可以判断出,scoket封装的为TCP/IP协议而上层的HTTP或自定义的应用协议并未实现。它作为一个最基础的网络封装,http和ws都可以用它去进行实现,可自由实现长短连接,满足不同需求。
BIO与NIO概念
BIO
说起NIO就又不得不说说BIO(Blocking I/O),即阻塞IO,它的核心思想为:服务器其中一个服务线程阻塞等待客户端的连接,就像上述代码一样,serverSocket.accept();一直在等待客户端的连接。当这样的客户端与服务端连接成功后,我们可以通过上述代码看到input.read(buffer);主服务线程又在等待数据传输完毕,才能进行下一步的操作。这就是BIO的基本实现逻辑。
如果说我们想改变这样的一对一的处理模式,可以在处理任务时丢到一个线程池中去做,比如读数据时,把读的这个操作直接放入到一个线程池的线程去做处理,并且保留这个scoket连接达到可以实现长连接的效果。
NIO
与BIO相比,N就在于它为非阻塞。它是面向缓冲的而不是面向流。说的明白点就是,有一块地方放了些外部的过来的数据,你只需要检查那块地方有没有放着东西,有就拿走处理,没有就可以释放掉IO资源。 NIO的主要构成为Selector、Channel、Buffer 它们之间的关系为Selector可以选择多个Channel,然后从Buffer中读取想要的数据。我们可以通过发布订阅的模式去理解NIO,因为对比BIO来说NIO就相当于是把原本的确定连接、读写数据、关闭连接、保持连接这些流程全部分开,原本为一个人所做全流程,现在可以让多个人也就是多个线程去做每个流程的具体实现。比如服务端存在一个线程负责接收所有的连接请求,而其余的事情则交由其他线程处理,这样就能提高服务端的最大连接数量,也相应提高了并发量。若把读或写分离,我们则可以应对专门的需求去调整数据的传输。资源是有限的,如何调度、调度的策略是代码的所求之道。下面为NIO的JAVA实现示例:
服务端
public class Server {
public static void main(String[] args) throws IOException {
// 创建Selector
Selector selector = Selector.open();
// 创建ServerSocketChannel并注册到Selector上
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 事件循环
while (true) {
// 阻塞等待事件发生
selector.select();
// 获取发生事件的SelectionKey集合
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
// 遍历SelectionKey集合
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove();
// 根据事件的类型进行相应的处理
if (key.isAcceptable()) {
// 处理连接请求
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 处理读事件
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = socketChannel.read(buffer);
if (len > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
System.out.println(new String(bytes));
}
buffer.flip();
socketChannel.write(buffer);
}
}
}
}
}
客户端
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8080);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 发送消息给服务器
Scanner scanner = new Scanner(System.in);
boolean flag = true;
while (flag){
String sendData = scanner.nextLine();
if (sendData.equals("kill")){
flag = false;
}
out.println(sendData);
// 接收来自服务器的响应并打印
String response = in.readLine();
System.out.println("Received from server: " + response);
}
// 关闭客户端套接字
socket.close();
}
}