Java 网络编程之TCP(三):基于NIO实现服务端,BIO实现客户端

server/2024/11/14 21:47:44/

前面的文章,我们讲述了BIO的概念,以及编程模型,由于BIO中服务器端的一些阻塞的点,导致服务端对于每一个客户端连接,都要开辟一个线程来处理,导致资源浪费,效率低。

为此,Linux 内核系统调用开始支持NIO(non-blocking IO),非阻塞IO,将之前BIO中的一些阻塞点,改为非阻塞,体现在Java API中就是:

服务端:
服务器等待客户端连接的accept方法不阻塞;java api中accept
服务器读取客户端数据不阻塞阻塞;java api中read

Java NIO编程中,主要涉及以下三个主要概念:

1.Channel :IO操作的联结,代表硬件,文件,网络套接字的连接,对应于BIO中的Socket; Channel需要与Buffer结合使用

2.Buffer:用于数据操作的缓冲区,就是一块内存,提供了一些操作,方便使用;

3.Selector:选择器,就是Linux 内核中的IO多路复用器,为了提高网络IO编程的效率,常用的有select, poll, epoll, 可以参考Linux对应系统调用

这三个概念,我们在后面的编程模型都会涉及。

下面我们先基于Channel和Buffer实现一个简单的服务端,用之前的BIO实现一个客户端;

Channel和Buffer对应的API的返回值含义,我都会在代码中注释清楚:

需求:

服务端:基于NIO,可以非阻塞的接收客户端连接,对客户端采用轮询接收数据

客户端:基于BIO,连接服务端,并发送数据

服务端代码:

java">import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;/*** 基于NIO中的Channel和Buffer实现服务端,对客户端采用轮询** @author freddy*/
class NIOServer {public static void main(String[] args) throws IOException {ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false); // 非阻塞serverSocketChannel.bind(new InetSocketAddress(9090));List<SocketChannel> clients = new LinkedList<>();while (true) {try {Thread.sleep(3000); // 为了方便测试观察} catch (InterruptedException e) {throw new RuntimeException(e);}// accept非阻塞, 没有连接时,返回nullSocketChannel client = serverSocketChannel.accept();if (client != null) {// 设置client非阻塞client.configureBlocking(false);clients.add(client);}// 遍历处理所有的client,看有没有数据可以读取Iterator<SocketChannel> iterator = clients.iterator();ByteBuffer buffer = ByteBuffer.allocate(1024); // 共用bufferSystem.out.println("clients size :" + clients.size());while (iterator.hasNext()) {SocketChannel clientSocket = iterator.next();try {int len = clientSocket.read(buffer); // read()返回:>0:读取到数据 0:没读到数据 -1:连接关闭if (len > 0) {// 读取到数据后,进行打印buffer.flip();byte[] bytes = new byte[buffer.limit()];System.out.println(clientSocket + "read data len:" + bytes.length);buffer.get(bytes);System.out.println(clientSocket + " data: " + new String(bytes));} else if (len == 0) {System.out.println(clientSocket + " no data");} else if (len == -1) {// 连接关闭iterator.remove();System.out.println(clientSocket + " close, remove");}buffer.clear();} catch (IOException exception) {iterator.remove();System.out.println(clientSocket + " disconnect, remove");}}}}
}

客户端代码:

java">import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;/*** 基于BIO的TCP网络通信的客户端,接收控制台输入的数据,然后通过字节流发送给服务端** @author freddy*/
class ChatClient {public static void main(String[] args) throws IOException {// 连接serverSocket serverSocket = new Socket("localhost", 9090);System.out.println("client connected to server");// 读取用户在控制台上的输入,并发送给服务器new Thread(new ClientThread(serverSocket)).start();// 接收服务端发送过来的数据try (InputStream serverSocketInputStream = serverSocket.getInputStream();) {byte[] buffer = new byte[1024];int len;while ((len = serverSocketInputStream.read(buffer)) != -1) {String data = new String(buffer, 0, len);System.out.println("client receive data from server" + serverSocketInputStream + " data size:" + len + ": " + data);}}}
}class ClientThread implements Runnable {private Socket serverSocket;public ClientThread(Socket serverSocket) {this.serverSocket = serverSocket;}@Overridepublic void run() {// 读取用户在控制台上的输入,并发送给服务器InputStream in = System.in;byte[] buffer = new byte[1024];int len;try (OutputStream outputStream = serverSocket.getOutputStream();) {// read操作阻塞,直到有数据可读,由于后面还要接收服务端转发过来的数据,这两个操作都是阻塞的,所以需要两个线程while ((len = in.read(buffer)) != -1) {String data = new String(buffer, 0, len);System.out.println("client receive data from console" + in + " : " + new String(buffer, 0, len));if ("exit\n".equals(data)) {// 模拟客户端关闭连接System.out.println("client close :" + serverSocket);// 这里跳出循环后,try-with-resources 会自动关闭outputStreambreak;}// 发送数据给服务器端outputStream.write(new String(buffer, 0, len).getBytes()); // 此时buffer中是有换行符}} catch (IOException e) {throw new RuntimeException(e);}}
}

测试:

先开启服务端,再开启两个客户端发送数据,服务端接受连接后,会打印当前接受到的客户端总数,然后轮询接收数据后打印;

当客户端发送exit后,客户端会关闭连接,服务端会识别到,去除该客户端;

当客户端发进程异常关闭后,客户端会断开连接,服务端会识别到,去除该客户端;

测试日志:

客户端1和2,正常发送数据

图1

客户端1发送exit后,关闭连接

客户端2断开连接

可以直接在Idea中关闭客户端2的程序,或者用nc命令模拟,Ctrl+C关闭nc

clients size :1
java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:17914] no data
clients size :1
java.nio.channels.SocketChannel[connected local=/127.0.0.1:9090 remote=/127.0.0.1:17914] disconnect, remove
clients size :0
clients size :0

我们在上面的图1中,可以看到,客户端短期内发送的两次内容,是在服务端一次性读到的;这个就是粘包、拆包现象的一种,后面我们会看。


http://www.ppmy.cn/server/25532.html

相关文章

盲人乘坐公共交通的新篇章:科技赋能,独立出行不再是梦

在我们日常生活中&#xff0c;盲人乘坐公共交通这一话题一直牵动着社会大众的心。近日&#xff0c;一位盲人朋友成功地独自完成了一次公交之旅&#xff0c;他的顺利出行背后&#xff0c;离不开一款名叫蝙蝠避障具有实时避障与拍照识别功能的辅助应用的鼎力支持。 清晨的…

Linux学习_09-Linux的用户管理

账号和用户组 系统管理员的主要工作就是管理账号。我们先来了解一下linux系统是如何识别每个用户的。 用户标识符&#xff1a;UID和GID linux的用户至少有2个ID&#xff0c;也就是UID用户ID和GID用户组ID。虽然登录的时候输入的是用户名&#xff0c;但其实系统识别的是这个两…

pnpm install报错 Value of “this“ must be of type URLSearchParams

执行pnpm install的时候就报错Value of “this” must be of type URLSearchParams 由于之前执行没有出现过这个问题&#xff0c;最近在使用vue3所以使用了高版本的node&#xff0c;怀疑是node版本的问题。 解决&#xff1a; 检查node版本 node -v当前使用的是20.11.0的 修改…

Grafana – unable to login “User already exists”

The Issue When trying to log into Grafana Web UI using an OIDC provider, in my case, Dex. The login would fail due to the error “User already exists”, after some time. This happened for any users given access via the OIDC. The Cause This looks to happ…

Jmeter接口测试

Jmeter 核心组件执行优先级&#xff1a;测试计划>>>线程组>>>配置元件>>>前置处理器>>>定时器>>>取样器>>>后置处理器>>>断言>>>监听器 逻辑控制器结合取样器实现一些复杂的逻辑配置元件配置信息&am…

C# 异步编程

异步编程是一种思路异步相当于对线程池的封装await相当于让另一个线程来干这个事 常见概念已经有多线程了&#xff0c;为何还要异步多线程与异步是不同的概念多线程与异步的适用场景不同*多线程**异步* 什么是异步任务(Task)包含了异步任务的各种状态的一个引用类型对于异步任务…

Rabbitmq消息应答,持久化,权重分配(7)

消息应答 概览 消息应答机制是 RabbitMQ 中确保消息处理的可靠性和一致性的重要机制之一。当消费者从队列中接收到消息并处理完成后&#xff0c;通常需要向 RabbitMQ 发送一个明确的消息应答&#xff0c;以告知 RabbitMQ 消息已经被处理&#xff0c;并可以安全地从队列中移除…

基于Spring Boot的校园博客系统设计与实现

基于Spring Boot的校园博客系统设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 系统功能界面图&#xff0c;在系统首页可以查看首页、文…