JAVA NIO实现简单聊天室功能
作者:肥牛火锅 发布时间:2023-05-01 10:32:49
标签:JAVA,NIO,聊天室
本文实例为大家分享了JAVA NIO实现简单聊天室功能的具体代码,供大家参考,具体内容如下
服务端
初始化一个ServerSocketChannel,绑定端口,然后使用Selector监听accept事件。
当有accept发生时,表示有客户端连接进来了,获取客户端的SocketChannel,然后注册其read事件;用来接收客户端发送的消息。
package chatroom;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* 服务端
*
* @author wenei
* @date 2021-07-20 20:36
*/
public class Server {
private static final Logger log = Logger.getLogger(Server.class.getName());
private int port;
private List<SocketChannel> clientChannelList = new ArrayList<>();
public Server(int port) {
this.port = port;
}
public void start() throws IOException {
// 初始化服务端channel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
// 新建Selector
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
final int selectCount = selector.select();
if (selectCount <= 0) {
continue;
}
final Set<SelectionKey> selectionKeys = selector.selectedKeys();
final Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
final SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
// 当有accept事件时,将新的连接加入Selector
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel accept = serverChannel.accept();
accept.configureBlocking(false);
clientChannelList.add(accept);
accept.register(selector, SelectionKey.OP_READ);
log.log(Level.INFO, "新连接 " + accept);
} else if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
log.log(Level.INFO, "可读连接 " + socketChannel);
ByteBuffer buffer = ByteBuffer.allocate(60);
try {
/**
* 当客户端非正常退出时,read抛出异常,属于被动性关闭;
* 当客户端正常返回时,返回-1,但也是readable信号,所以需要处理
*/
final int read = socketChannel.read(buffer);
if (read == -1) {
log.log(Level.INFO, "连接主动关闭:" + socketChannel);
clientChannelList.remove(socketChannel);
socketChannel.close();
continue;
}
} catch (IOException e) {
log.log(Level.INFO, "连接被动关闭:" + socketChannel);
clientChannelList.remove(socketChannel);
socketChannel.close();
continue;
}
buffer.flip();
byte[] bytes = new byte[60];
int index = 0;
while (buffer.hasRemaining()) {
bytes[index++] = buffer.get();
}
bytes[index] = '\0';
log.log(Level.INFO, "接受数据: " + new String(bytes, StandardCharsets.UTF_8).trim());
// 广播
clientChannelList.forEach(channel -> {
if (channel != socketChannel) {
buffer.flip();
try {
channel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
});
// buffer.clear();
}
}
}
}
public static void main(String[] args) throws IOException {
new Server(10022).start();
}
}
客户端
使用主线程获取键盘输入,然后传给服务端。
使用子线程接收服务端发送的信息并显示。
package chatroom;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
/**
* 客户端
*
* @author wenei
* @date 2021-07-21 9:14
*/
public class Client {
/**
* 客户端接收信息线程
*/
static class ClientReceiveThread implements Runnable {
/**
* 客户端socket
*/
private SocketChannel socketChannel;
public ClientReceiveThread(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
@Override
public void run() {
try {
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ);
while (true) {
final int selectCount = selector.select(100);
if (Thread.currentThread().isInterrupted()) {
System.out.println("连接关闭");
socketChannel.close();
return;
}
if (selectCount <= 0) {
continue;
}
final Set<SelectionKey> selectionKeys = selector.selectedKeys();
final Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
final SelectionKey key = iterator.next();
iterator.remove();
if (key.isReadable()) {
ByteBuffer recvBuffer = ByteBuffer.allocate(60);
socketChannel.read(recvBuffer);
recvBuffer.flip();
byte[] bytes = new byte[60];
int index = 0;
while (recvBuffer.hasRemaining()) {
bytes[index++] = recvBuffer.get();
}
bytes[index] = '\0';
System.out.println("接受数据: " + new String(bytes, StandardCharsets.UTF_8).trim());
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private int port;
public Client(int port) {
this.port = port;
}
public void start() throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(port));
socketChannel.configureBlocking(false);
Scanner scanner = new Scanner(System.in);
ByteBuffer buffer = ByteBuffer.allocate(60);
Thread thread = new Thread(new ClientReceiveThread(socketChannel));
thread.start();
while (true) {
String data = scanner.nextLine();
if (data.equals("exit")) {
break;
}
System.out.println("输入数据:" + data);
buffer.put(data.getBytes(StandardCharsets.UTF_8));
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
thread.interrupt();
}
public static void main(String[] args) throws IOException {
new Client(10022).start();
}
}
来源:https://blog.csdn.net/qq_43621091/article/details/118959991


猜你喜欢
- 我们知道,Maven 是通过仓库对依赖进行管理的,当 Maven 项目需要某个依赖时,只要其 POM 中声明了依赖的坐标信息,Maven 就
- Kotlin的对象表达式与Java中的匿名内部类的主要区别:匿名内部类只能指定一个父类型,但对象表达式可以指定0~N个肤类型。一、对象表达式
- 0. Iochttps://docs.spring.io/spring-framework/docs/current/spring-fram
- 实现了一个有趣的小东西:使用自定义View绘图,一边画线,画出的线条渐渐变淡,直到消失。效果如下图所示:用属性动画或者渐变填充(Shader
- 常用的对数组进行的操作1、求数组中最大值,最小值思路:假设下标为0的元素是最大值,遍历数组,依次跟max进行比较,如果有元素比这个max还大
- 当我们要实现丰富的图文混排效果的时候,我们一般会使用webview,这是一个功能十分强大的的控件,来看看官方的解释:A View that
- 本文实例为大家分享了Java实现简易计算器的具体代码,供大家参考,具体内容如下程序的运行环境为Windows10 ,编译环境为IDEA。计算
- 一、管理网络状态使用网络进行数据通信前,需要先获取网络状态。使用ConnectivityManager获取网络状态步骤:1.获取Connec
- C# double.ToString()的用法C# 中 double 类型的数据,有时需要格式化显示为字符串(保留N位有效数字或者是保留N位
- C#判断数据类型的简单示例代码:int i = 5; Console
- Spring中BeanFactory FactoryBean和ObjectFactory的三种的区别引言关于FactoryBean 和 Be
- 语法糖(Syntactic sugar)是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中
- 在Spring 5 Webflux中,配置CORS,可以通过自定义WebFilter实现:注:此种写法需真实跨域访问,监控header中才会
- 本文实例讲述了Android亮屏和熄屏控制实现方法。分享给大家供大家参考,具体如下:一、概述我们的Android应用程序很多需要和亮屏和熄屏
- 如何实现首先 * 是属于web这块的,那我们需要引入springboot web模块,具体版本在parent中<dependency&
- 本文实例讲述了C#使用Word中的内置对话框的方法,分享给大家供大家参考。具体实现方法如下:使用 Microsoft Office Word
- 现在很多app的支付、输入密码功能,都已经开始使用自定义数字键盘,不仅更加方便、其效果着实精致。下面带着大家学习下,如何 * 微信的数字键盘,
- 1 前言在项目开发中,异步化处理是非常常见的解决问题的手段,异步化处理除了使用线程池之外,还可以使用 CompletableFut
- Java8 LocalDateTime与timestamp转换将timestamp转为LocalDateTimepublic LocalDa
- JDK提供的流继承了四大类:InputStream(字节输入流)、OutputStream(字节输出流)、Reader(字符输入流)、Wri