mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
commit
f527c7af39
@ -2,12 +2,20 @@ package cn.hutool.socket.nio;
|
|||||||
|
|
||||||
import cn.hutool.core.io.IORuntimeException;
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.thread.ThreadFactoryBuilder;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
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.channels.SocketChannel;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NIO客户端
|
* NIO客户端
|
||||||
@ -15,88 +23,160 @@ import java.nio.channels.SocketChannel;
|
|||||||
* @author looly
|
* @author looly
|
||||||
* @since 4.4.5
|
* @since 4.4.5
|
||||||
*/
|
*/
|
||||||
public class NioClient implements Closeable {
|
public abstract class NioClient implements Closeable {
|
||||||
|
|
||||||
private SocketChannel channel;
|
private Selector selector;
|
||||||
|
private SocketChannel channel;
|
||||||
|
private ExecutorService executorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param host 服务器地址
|
||||||
|
* @param port 端口
|
||||||
|
*/
|
||||||
|
public NioClient(String host, int port) {
|
||||||
|
init(new InetSocketAddress(host, port));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param address 服务器地址
|
||||||
|
*/
|
||||||
|
public NioClient(InetSocketAddress address) {
|
||||||
|
init(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*
|
||||||
|
* @param address 地址和端口
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public NioClient init(InetSocketAddress address) {
|
||||||
|
try {
|
||||||
|
//创建一个SocketChannel对象,配置成非阻塞模式
|
||||||
|
this.channel = SocketChannel.open();
|
||||||
|
channel.configureBlocking(false);
|
||||||
|
|
||||||
|
//创建一个选择器,并把SocketChannel交给selector对象
|
||||||
|
this.selector = Selector.open();
|
||||||
|
channel.register(selector, SelectionKey.OP_CONNECT);
|
||||||
|
|
||||||
|
//发起建立连接的请求,这里会立即返回,当连接建立完成后,SocketChannel就会被选取出来
|
||||||
|
channel.connect(address);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IORuntimeException(e);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 检查连接是否建立完成
|
||||||
*
|
|
||||||
* @param host 服务器地址
|
|
||||||
* @param port 端口
|
|
||||||
*/
|
*/
|
||||||
public NioClient(String host, int port) {
|
public boolean waitConnect() throws IOException {
|
||||||
init(new InetSocketAddress(host, port));
|
boolean isConnect = false;
|
||||||
}
|
while (0 != this.selector.select()) {
|
||||||
|
final Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
|
||||||
/**
|
while (keyIter.hasNext()) {
|
||||||
* 构造
|
//连接建立完成
|
||||||
*
|
SelectionKey key = keyIter.next();
|
||||||
* @param address 服务器地址
|
if (key.isConnectable()) {
|
||||||
*/
|
if (this.channel.finishConnect()) {
|
||||||
public NioClient(InetSocketAddress address) {
|
this.channel.register(selector, SelectionKey.OP_READ);
|
||||||
init(address);
|
isConnect = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
keyIter.remove();
|
||||||
* 初始化
|
break;
|
||||||
*
|
}
|
||||||
* @param address 地址和端口
|
if (isConnect) {
|
||||||
* @return this
|
break;
|
||||||
*/
|
}
|
||||||
public NioClient init(InetSocketAddress address) {
|
|
||||||
try {
|
|
||||||
this.channel = SocketChannel.open(address);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IORuntimeException(e);
|
|
||||||
}
|
}
|
||||||
return this;
|
return isConnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理读事件<br>
|
* 开始监听
|
||||||
* 当收到读取准备就绪的信号后,回调此方法,用户可读取从客户端传世来的消息
|
*/
|
||||||
*
|
public void listen() {
|
||||||
* @param buffer 服务端数据存储缓存
|
this.executorService = Executors.newSingleThreadExecutor(r -> {
|
||||||
* @return this
|
final Thread thread = Executors.defaultThreadFactory().newThread(r);
|
||||||
*/
|
thread.setName("nio-client-listen");
|
||||||
public NioClient read(ByteBuffer buffer) {
|
return thread;
|
||||||
try {
|
});
|
||||||
this.channel.read(buffer);
|
this.executorService.execute(() -> {
|
||||||
} catch (IOException e) {
|
try {
|
||||||
throw new IORuntimeException(e);
|
doListen();
|
||||||
}
|
} catch (IOException e) {
|
||||||
return this;
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始监听
|
||||||
|
*
|
||||||
|
* @throws IOException IO异常
|
||||||
|
*/
|
||||||
|
private void doListen() throws IOException {
|
||||||
|
while (0 != this.selector.select()) {
|
||||||
|
// 返回已选择键的集合
|
||||||
|
final Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
|
||||||
|
while (keyIter.hasNext()) {
|
||||||
|
handle(keyIter.next());
|
||||||
|
keyIter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理SelectionKey
|
||||||
|
*
|
||||||
|
* @param key SelectionKey
|
||||||
|
*/
|
||||||
|
private void handle(SelectionKey key) throws IOException {
|
||||||
|
// 读事件就绪
|
||||||
|
if (key.isReadable()) {
|
||||||
|
final SocketChannel socketChannel = (SocketChannel) key.channel();
|
||||||
|
read(socketChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理读事件<br>
|
||||||
|
* 当收到读取准备就绪的信号后,回调此方法,用户可读取从客户端传出来的消息
|
||||||
|
*
|
||||||
|
* @param socketChannel SocketChannel
|
||||||
|
*/
|
||||||
|
protected abstract void read(SocketChannel socketChannel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现写逻辑<br>
|
||||||
|
* 当收到写出准备就绪的信号后,回调此方法,用户可向客户端发送消息
|
||||||
|
*
|
||||||
|
* @param datas 发送的数据
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public NioClient write(ByteBuffer... datas) {
|
||||||
|
try {
|
||||||
|
this.channel.write(datas);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IORuntimeException(e);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeListen() {
|
||||||
|
this.executorService.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 实现写逻辑<br>
|
public void close() {
|
||||||
* 当收到写出准备就绪的信号后,回调此方法,用户可向客户端发送消息
|
IoUtil.close(this.selector);
|
||||||
*
|
IoUtil.close(this.channel);
|
||||||
* @param datas 发送的数据
|
closeListen();
|
||||||
* @return this
|
}
|
||||||
*/
|
|
||||||
public NioClient write(ByteBuffer... datas) {
|
|
||||||
try {
|
|
||||||
this.channel.write(datas);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IORuntimeException(e);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取SocketChannel
|
|
||||||
*
|
|
||||||
* @return SocketChannel
|
|
||||||
* @since 5.3.10
|
|
||||||
*/
|
|
||||||
public SocketChannel getChannel() {
|
|
||||||
return this.channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
IoUtil.close(this.channel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ public abstract class NioServer implements Closeable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理读事件<br>
|
* 处理读事件<br>
|
||||||
* 当收到读取准备就绪的信号后,回调此方法,用户可读取从客户端传世来的消息
|
* 当收到读取准备就绪的信号后,回调此方法,用户可读取从客户端传出来的消息
|
||||||
*
|
*
|
||||||
* @param socketChannel SocketChannel
|
* @param socketChannel SocketChannel
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
package cn.hutool.socket;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.socket.nio.NioClient;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
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.Charset;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class NioClientTest {
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void main(String[] args) {
|
||||||
|
NioClient client = new NioClient("127.0.0.1", 8080) {
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
protected void read(SocketChannel sc) {
|
||||||
|
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
|
||||||
|
//从channel读数据到缓冲区
|
||||||
|
int readBytes = sc.read(readBuffer);
|
||||||
|
if (readBytes > 0){
|
||||||
|
//Flips this buffer. The limit is set to the current position and then
|
||||||
|
// the position is set to zero,就是表示要从起始位置开始读取数据
|
||||||
|
readBuffer.flip();
|
||||||
|
//eturns the number of elements between the current position and the limit.
|
||||||
|
// 要读取的字节长度
|
||||||
|
byte[] bytes = new byte[readBuffer.remaining()];
|
||||||
|
//将缓冲区的数据读到bytes数组
|
||||||
|
readBuffer.get(bytes);
|
||||||
|
String body = new String(bytes, "UTF-8");
|
||||||
|
System.out.println("the read client receive message: " + body);
|
||||||
|
}else if(readBytes < 0){
|
||||||
|
sc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (client.waitConnect()) {
|
||||||
|
client.listen();
|
||||||
|
}
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap("client 发生到 server".getBytes());
|
||||||
|
client.write(buffer);
|
||||||
|
buffer = ByteBuffer.wrap("client 再次发生到 server".getBytes());
|
||||||
|
client.write(buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在控制台向服务器端发送数据
|
||||||
|
*/
|
||||||
|
System.out.println("请在下方畅所欲言");
|
||||||
|
Scanner scanner = new Scanner(System.in);
|
||||||
|
while (scanner.hasNextLine()) {
|
||||||
|
String request = scanner.nextLine();
|
||||||
|
if (request != null && request.trim().length() > 0) {
|
||||||
|
client.write(
|
||||||
|
Charset.forName("UTF-8")
|
||||||
|
.encode("测试client" + ": " + request));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package cn.hutool.socket;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.socket.nio.NioServer;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
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.util.Set;
|
||||||
|
|
||||||
|
public class NioServerTest {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
NioServer server = new NioServer(8080) {
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
protected void read(SocketChannel sc) {
|
||||||
|
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
|
||||||
|
//从channel读数据到缓冲区
|
||||||
|
int readBytes = sc.read(readBuffer);
|
||||||
|
if (readBytes > 0){
|
||||||
|
//Flips this buffer. The limit is set to the current position and then
|
||||||
|
// the position is set to zero,就是表示要从起始位置开始读取数据
|
||||||
|
readBuffer.flip();
|
||||||
|
//eturns the number of elements between the current position and the limit.
|
||||||
|
// 要读取的字节长度
|
||||||
|
byte[] bytes = new byte[readBuffer.remaining()];
|
||||||
|
//将缓冲区的数据读到bytes数组
|
||||||
|
readBuffer.get(bytes);
|
||||||
|
String body = new String(bytes, "UTF-8");
|
||||||
|
System.out.println("the read server receive message: " + body);
|
||||||
|
doWrite(sc, body);
|
||||||
|
}else if(readBytes < 0){
|
||||||
|
sc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
protected void write(SocketChannel sc) {
|
||||||
|
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
|
||||||
|
//从channel读数据到缓冲区
|
||||||
|
int readBytes = sc.read(readBuffer);
|
||||||
|
if (readBytes > 0){
|
||||||
|
//Flips this buffer. The limit is set to the current position and then
|
||||||
|
// the position is set to zero,就是表示要从起始位置开始读取数据
|
||||||
|
readBuffer.flip();
|
||||||
|
//eturns the number of elements between the current position and the limit.
|
||||||
|
// 要读取的字节长度
|
||||||
|
byte[] bytes = new byte[readBuffer.remaining()];
|
||||||
|
//将缓冲区的数据读到bytes数组
|
||||||
|
readBuffer.get(bytes);
|
||||||
|
String body = new String(bytes, "UTF-8");
|
||||||
|
System.out.println("the write server receive message: " + body);
|
||||||
|
doWrite(sc, body);
|
||||||
|
}else if(readBytes < 0){
|
||||||
|
sc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
server.listen();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void doWrite(SocketChannel channel, String response) throws IOException {
|
||||||
|
response = "我们已收到消息:"+response;
|
||||||
|
if(!StrUtil.isBlank(response)){
|
||||||
|
byte [] bytes = response.getBytes();
|
||||||
|
//分配一个bytes的length长度的ByteBuffer
|
||||||
|
ByteBuffer write = ByteBuffer.allocate(bytes.length);
|
||||||
|
//将返回数据写入缓冲区
|
||||||
|
write.put(bytes);
|
||||||
|
write.flip();
|
||||||
|
//将缓冲数据写入渠道,返回给客户端
|
||||||
|
channel.write(write);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user