首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Java NIO原生服务端和客户端即时通信简易项目

前面把Java NIO的三大核心概念粗略地撸了一遍,趁热打铁写了个即时通讯的案例,把这三个核心概念再梳理一遍。

简单说一下服务器端和客户端的职责。

服务器端的职责,监听端口号,接受客户端的连接请求,接收客户端发送的信息,并回复客户端信息。

客户端的职责,连接服务器,连接成功后,可以通过控制台给服务器端发送信息。

一、服务器端代码

public class NIOServer {

public static void main(String[] args) {

try {

// 打开一个通道channeL

ServerSocketChannel serverChannel = ServerSocketChannel.open();

// 设置通道为非阻塞

serverChannel.configureBlocking(false);

// 绑定端口号

serverChannel.bind(new InetSocketAddress(8080));

// 打开选择器

Selector selector = Selector.open();

// 将通道注册到选择器,关注连接事件

serverChannel.register(selector, SelectionKey.OP_ACCEPT);

System.out.println("服务器端启动成功:" + serverChannel.getLocalAddress());

while (true) {

// 等待事件发生

selector.select();

// 获取注册的事件集合

Set<SelectionKey> selectedKeys = selector.selectedKeys();

// 遍历事件

Iterator<SelectionKey> iter = selectedKeys.iterator();

// 是否还有事件

while (iter.hasNext()) {

// 获取下一个事件

SelectionKey key = iter.next();

// 同意连接后

if (key.isAcceptable()) {

// 连接成功后,关注读事件

register(selector, serverChannel);

}

// 读事件的处理

if (key.isReadable()) {

answerWithEcho(key);

}

// 处理完后移除已处理的Key

iter.remove();

}

}

} catch (IOException e) {

e.printStackTrace();

}

}

// 注册事件

private static void register(Selector selector, ServerSocketChannel serverChannel) {

try{

SocketChannel client = serverChannel.accept();

// 设置通道为非阻塞

client.configureBlocking(false);

// 将通道注册到选择器,关注读事件

client.register(selector, SelectionKey.OP_READ);

System.out.println("有一个客户端成功连接到服务器:" + client.getRemoteAddress());

} catch (IOException e) {

e.printStackTrace();

}

}

// 处理读事件

private static void answerWithEcho(SelectionKey key) {

SocketChannel client = null;

try{

client = (SocketChannel) key.channel();

// 分配缓冲区

ByteBuffer buffer = ByteBuffer.allocate(1024);

// 将客户端发送的消息存储到缓冲区

client.read(buffer);

// 切换到读状态

buffer.flip();

// 打印客户端发送的消息

System.out.println("接收到客户端消息:" + buffer.limit() + ","+ new String(buffer.array()));

// 清空缓冲区,切换到写模式

buffer.clear();

// 在缓冲区写入消息

String str = "Thank you! 消息已收到!";

buffer.put(str.getBytes("utf-8"));

// 从写切换到读模式

buffer.flip();

// 回复客户端

client.write(buffer);

buffer.clear();

}catch(IOException e){

e.printStackTrace();

if(client != null){

try {

client.close();

} catch (IOException ee) {

ee.printStackTrace();

}

}

}

}

}

这段代码是使用Java NIO(非阻塞I/O)库实现的简单服务器端示例。这里使用的是ServerSocketChannel和Selector来处理多个客户端连接而不阻塞主线程。

类和主方法

类定义:public class NIOServer定义了一个公开的类NIOServer。

主方法:public static void main(String[] args)是程序的入口点。

服务器设置和初始化

打开一个服务器通道:

ServerSocketChannel serverChannel = ServerSocketChannel.open(); 创建一个新的ServerSocketChannel实例。

设置为非阻塞模式:

serverChannel.configureBlocking(false); 设置该通道为非阻塞模式,使得服务器可以在没有客户端连接时继续执行其他任务。

serverChannel.bind(new InetSocketAddress(8080)); 将服务器通道绑定到端口8080。

打开选择器:

Selector selector = Selector.open(); 打开一个选择器。选择器用于处理多个通道的I/O事件。

注册通道到选择器:

serverChannel.register(selector, SelectionKey.OP_ACCEPT); 将服务器通道注册到选择器上,并监听接受连接的事件(OP_ACCEPT)。

启动成功的消息:

事件处理循环

循环等待事件:

while (true) { 开始无限循环,等待和处理事件。

selector.select(); 阻塞,直到至少有一个注册的事件发生。

处理选择器中的事件:

Set<SelectionKey> selectedKeys = selector.selectedKeys(); 获取所有已触发的事件的键集。

使用迭代器遍历所有事件键。

处理连接请求:

如果key.isAcceptable()为真,则处理新的连接请求,通过调用register(selector, serverChannel);方法注册新的客户端通道,关注读事件。

处理读事件:

如果key.isReadable()为真,则通过调用answerWithEcho(key);方法处理读事件,即读取数据并回显到客户端。

iter.remove();从集合中移除已处理的事件键,避免重复处理。

辅助方法

private static void register(Selector selector, ServerSocketChannel serverChannel)方法用于接受新的连接并注册到选择器上。

处理读事件并回显:

private static void answerWithEcho(SelectionKey key)方法用于读取客户端发送的数据,并发送一个回应消息。

异常处理

代码中多次调用e.printStackTrace();来打印异常堆栈信息,有助于调试时了解错误原因。

如果客户端通道在处理中出现异常,则关闭该通道。

整体来说,这个示例展示了使用Java NIO实现的服务器如何以非阻塞的方式处理多个网络连接,有效地使用系统资源,并提供实时的数据处理能力。

public class NIOClient {

public static void main(String[] args) {

try {

// 绑定服务器地址

InetSocketAddress serverAddress = new InetSocketAddress("localhost", 8080);

// 打开一个通道

SocketChannel socketChannel = SocketChannel.open();

// 设置为非阻塞

socketChannel.configureBlocking(false);

// 连接服务器

socketChannel.connect(serverAddress);

// 连接未完成时

while (!socketChannel.finishConnect()) {

// 等待连接完成

System.out.println("连接服务器中...");

}

System.out.println("已成功连接服务器。");

try (Scanner scanner = new Scanner(System.in)) {

while (true) {

System.out.println("请输入消息发送给服务器端 (退出请输入:'exit'): ");

String message = scanner.nextLine();

// 如果用户输入 "exit",则退出循环

if ("exit".equalsIgnoreCase(message)) {

System.out.println("关闭连接...");

break;

}

// 分配指定字节的缓冲区

ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());

while (buffer.hasRemaining()) {

// 发数据塞入到通道里

socketChannel.write(buffer);

}

buffer = ByteBuffer.allocate(100);

// 清空缓存,切换到写模式

buffer.clear();

while (socketChannel.read(buffer) == 0) {

// 等待服务器的响应

}

// 由写切换到读

buffer.flip();

byte[] bytes = buffer.array();

// 把字节数组中的数据打印出来

System.out.println("接收到服务器发送的消息: " + new String(bytes, "utf-8"));

}

}

// 完成后关闭通道

socketChannel.close();

System.out.println("连接已关闭.");

} catch (Exception e) {

e.printStackTrace();

}

}

}

这段代码实现了一个简单的NIO(Non-blocking I/O)客户端程序,能够连接到指定的服务器,并与其进行消息的发送和接收。

1、 服务器地址绑定:

InetSocketAddress serverAddress = new InetSocketAddress("localhost", 8080);:这里将服务器地址绑定到本地主机(localhost)的8080端口。

SocketChannel socketChannel = SocketChannel.open();:打开一个SocketChannel通道,用于与服务器进行通信。

3、 非阻塞模式:

socketChannel.configureBlocking(false);:将通道设置为非阻塞模式,这样程序在执行I/O操作时不会被阻塞。

4、 连接服务器:

socketChannel.connect(serverAddress);:尝试连接到指定的服务器。

while (!socketChannel.finishConnect()) { ... }:在连接未完成时,程序会不断检查连接状态,直到成功连接。

用户通过命令行输入消息(Scanner scanner = new Scanner(System.in)),输入内容后会发送给服务器。

ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());:将消息转换为字节数组并包装到ByteBuffer中,准备发送。

socketChannel.write(buffer);:将缓冲区的数据写入通道发送给服务器。

接收服务器的响应后,通过buffer.flip()切换到读模式,读取服务器返回的消息并打印到控制台。

如果用户输入"exit",则退出循环,并关闭与服务器的连接(socketChannel.close();)。

这个程序主要用于演示如何使用Java NIO建立一个非阻塞的客户端,与服务器进行简单的文本通信。通过非阻塞模式,程序可以在等待连接和数据传输时执行其他任务,提高了效率。

启动服务器代码、客户端代码,在客户端的控制台输入信息并按回车键。

客户端控制台打印结果:

服务器控制台打印结果:

三、即时通讯与三大概念

1、客户端与Java NIO 三大概念总结

1) Channel(通道)

客户端用SocketChannel 连接服务器和发送接收数据。

客户端通过 SocketChannel.open() 打开一个通道,并使用 connect(new InetSocketAddress(host, port)) 方法连接到服务器。

一旦连接建立,就通过 SocketChannel 发送数据到服务器或从服务器接收数据。

2) Buffer(缓冲区)

客户端在需要发送数据时,会创建一个 ByteBuffer,将数据写入缓冲区,再通过通道发送给服务器。

从服务器接收数据时,客户端也会使用 ByteBuffer 作为接收数据的容器,之后可以从缓冲区读取数据进行处理。

3)Selector(选择器)

客户端通常不使用,除非需要处理多个并发连接。

2、服务器端与Java NIO 三大核心概念总结

1)Channel(通道)

服务器首先通过 ServerSocketChannel.open() 打开一个 ServerSocketChannel,并绑定到一个特定的端口(例如 8080),通过它来监听客户端连接请求。

服务器使用 accept() 方法等待并接收客户端连接,该方法返回一个 SocketChannel,通过它与客户端进行数据通信。

2)Buffer(缓冲区)

在接收到客户端的连接后,服务器会分配一个 ByteBuffer 来读取客户端发送的数据。

需要给客户端发送数据时,也分配一个ByteBuffer 给客户端发送数据。

3)Selector(选择器)

服务器端使用 Selector监听多个通道的事件,如连接、读写操作。

服务器使用 Selector.open() 打开一个选择器,并将 ServerSocketChannel 和 SocketChannel 注册到选择器上,指定监听的事件(如 OP_ACCEPT 和 OP_READ)。

在服务器的主循环中,使用 selector.select() 方法来等待就绪的通道,然后根据事件类型(连接就绪、数据可读等)进行相应处理。

服务器端使用 ServerSocketChannel 监听连接请求,并通过 Selector 选择就绪的通道来处理客户端的请求。

客户端使用 SocketChannel 连接服务器并发送和接收数据,通过 ByteBuffer 进行数据的读写操作。

Selector 提供了一种高效的方式来管理多个通道,使得在一个线程内可以处理多个网络连接,是服务器必须要使用的。

缓冲区在服务器和客户端都需要使用,在发送和接收数据的时候使用。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/Od6P6cqxID9pFUUjNRRwNDOQ0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券