JAVA NIO實(shí)現(xiàn)簡(jiǎn)單聊天室功能
本文實(shí)例為大家分享了JAVA NIO實(shí)現(xiàn)簡(jiǎn)單聊天室功能的具體代碼,供大家參考,具體內(nèi)容如下
服務(wù)端
初始化一個(gè)ServerSocketChannel,綁定端口,然后使用Selector監(jiān)聽accept事件。
當(dāng)有accept發(fā)生時(shí),表示有客戶端連接進(jìn)來(lái)了,獲取客戶端的SocketChannel,然后注冊(cè)其read事件;用來(lái)接收客戶端發(fā)送的消息。
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;
/**
* 服務(wù)端
*
* @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 {
// 初始化服務(wù)端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()) {
// 當(dāng)有accept事件時(shí),將新的連接加入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 {
/**
* 當(dāng)客戶端非正常退出時(shí),read拋出異常,屬于被動(dòng)性關(guān)閉;
* 當(dāng)客戶端正常返回時(shí),返回-1,但也是readable信號(hào),所以需要處理
*/
final int read = socketChannel.read(buffer);
if (read == -1) {
log.log(Level.INFO, "連接主動(dòng)關(guān)閉:" + socketChannel);
clientChannelList.remove(socketChannel);
socketChannel.close();
continue;
}
} catch (IOException e) {
log.log(Level.INFO, "連接被動(dòng)關(guān)閉:" + 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, "接受數(shù)據(jù): " + 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();
}
}
客戶端
使用主線程獲取鍵盤輸入,然后傳給服務(wù)端。
使用子線程接收服務(wù)端發(fā)送的信息并顯示。
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("連接關(guān)閉");
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("接受數(shù)據(jù): " + 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("輸入數(shù)據(jù):" + 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();
}
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot 在測(cè)試時(shí)如何指定包的掃描范圍
這篇文章主要介紹了SpringBoot 在測(cè)試時(shí)如何指定包的掃描范圍,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
SpringBoot加載應(yīng)用事件監(jiān)聽器代碼實(shí)例
這篇文章主要介紹了SpringBoot加載應(yīng)用事件監(jiān)聽器代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
Java中調(diào)用SQL Server存儲(chǔ)過(guò)程詳解
這篇文章主要介紹了Java中調(diào)用SQL Server存儲(chǔ)過(guò)程詳解,本文講解了使用不帶參數(shù)的存儲(chǔ)過(guò)程、使用帶有輸入?yún)?shù)的存儲(chǔ)過(guò)程、使用帶有輸出參數(shù)的存儲(chǔ)過(guò)程、使用帶有返回狀態(tài)的存儲(chǔ)過(guò)程、使用帶有更新計(jì)數(shù)的存儲(chǔ)過(guò)程等操作實(shí)例,需要的朋友可以參考下2015-01-01
k8s+springboot+CronJob定時(shí)任務(wù)部署實(shí)現(xiàn)
本文主要介紹了k8s+springboot+CronJob定時(shí)任務(wù)部署實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
Java?離線中文語(yǔ)音文字識(shí)別功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java?離線中文語(yǔ)音文字識(shí)別,本次使用springboot?+maven實(shí)現(xiàn),官方demo為springboot+gradle,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
java啟動(dòng)jar包修改JVM默認(rèn)內(nèi)存問(wèn)題
這篇文章主要介紹了java啟動(dòng)jar包修改JVM默認(rèn)內(nèi)存問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02

