1.服务器端代码:
package net.qiujuer.lesson.sample.server;
import net.qiujuer.lesson.sample.server.handle.ClientHandler;
import net.qiujuer.library.clink.utils.CloseUtils;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TCPServer implements ClientHandler.ClientHandlerCallback {
private final int port;
private final File cachePath;
private final ExecutorService forwardingThreadPoolExecutor;
private ClientListener listener;
private List<ClientHandler> clientHandlerList = new ArrayList<>();
private Selector selector;
private ServerSocketChannel server;
public TCPServer(int port, File cachePath) {
this.port = port;
this.cachePath = cachePath;
// 转发线程池
this.forwardingThreadPoolExecutor = Executors.newSingleThreadExecutor();
}
public boolean start() {
try {
selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
// 设置为非阻塞
server.configureBlocking(false);
// 绑定本地端口
server.socket().bind(new InetSocketAddress(port));
// 注册客户端连接到达监听
server.register(selector, SelectionKey.OP_ACCEPT);
this.server = server;
System.out.println("服务器信息:" + server.getLocalAddress().toString());
// 启动客户端监听
ClientListener listener = this.listener = new ClientListener();
listener.start();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
public void stop() {
if (listener != null) {
listener.exit();
}
CloseUtils.close(server);
CloseUtils.close(selector);
synchronized (TCPServer.this) {
for (ClientHandler clientHandler : clientHandlerList) {
clientHandler.exit();
}
clientHandlerList.clear();
}
// 停止线程池
forwardingThreadPoolExecutor.shutdownNow();
}
public synchronized void broadcast(String str) {
for (ClientHandler clientHandler : clientHandlerList) {
clientHandler.send(str);
}
}
@Override
public synchronized void onSelfClosed(ClientHandler handler) {
clientHandlerList.remove(handler);
}
@Override
public void onNewMessageArrived(final ClientHandler handler, final String msg) {
// 异步提交转发任务
forwardingThreadPoolExecutor.execute(() -> {
synchronized (TCPServer.this) {
for (ClientHandler clientHandler : clientHandlerList) {
if (clientHandler.equals(handler)) {
// 跳过自己
continue;
}
// 对其他客户端发送消息
clientHandler.send(msg);
}
}
});
}
private class ClientListener extends Thread {
private boolean done = false;
@Override
public void run() {
super.run();
Selector selector = TCPServer.this.selector;
System.out.println("服务器准备就绪~");
// 等待客户端连接
do {
// 得到客户端
try {
if (selector.select() == 0) {
if (done) {
break;
}
continue;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
if (done) {
break;
}
SelectionKey key = iterator.next();
iterator.remove();
// 检查当前Key的状态是否是我们关注的
// 客户端到达状态
if (key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 非阻塞状态拿到客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
try {
// 客户端构建异步线程
ClientHandler clientHandler = new ClientHandler(socketChannel,
TCPServer.this, cachePath);
// 添加同步处理
synchronized (TCPServer.this) {
clientHandlerList.add(clientHandler);
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户端连接异常:" + e.getMessage());
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
} while (!done);
System.out.println("服务器已关闭!");
}
void exit() {
done = true;
// 唤醒当前的阻塞
selector.wakeup();
}
}
}
package net.qiujuer.lesson.sample.server.handle;
import net.qiujuer.lesson.sample.foo.Foo;
import net.qiujuer.library.clink.core.Connector;
import net.qiujuer.library.clink.core.Packet;
import net.qiujuer.library.clink.core.ReceivePacket;
import net.qiujuer.library.clink.utils.CloseUtils;
import java.io.File;
import java.io.IOException;
import java.nio.channels.SocketChannel;
public class ClientHandler extends Connector {
private final File cachePath;
private final ClientHandlerCallback clientHandlerCallback;
private final String clientInfo;
public ClientHandler(SocketChannel socketChannel, ClientHandlerCallback clientHandlerCallback, File cachePath) throws IOException {
this.clientHandlerCallback = clientHandlerCallback;
this.clientInfo = socketChannel.getRemoteAddress().toString();
this.cachePath = cachePath;
System.out.println("新客户端连接:" + clientInfo);
setup(socketChannel);
}
public void exit() {
CloseUtils.close(this);
System.out.println("客户端已退出:" + clientInfo);
}
@Override
public void onChannelClosed(SocketChannel channel) {
super.onChannelClosed(channel);
exitBySelf();
}
@Override
protected File createNewReceiveFile() {
return Foo.createRandomTemp(cachePath);
}
@Override
protected void onReceivedPacket(ReceivePacket packet) {
super.onReceivedPacket(packet);
if (packet.type() == Packet.TYPE_MEMORY_STRING) {
String string = (String) packet.entity();
System.out.println(key.toString() + ":" + string);
clientHandlerCallback.onNewMessageArrived(this, string);
}
}
private void exitBySelf() {
exit();
clientHandlerCallback.onSelfClosed(this);
}
public interface ClientHandlerCallback {
// 自身关闭通知
void onSelfClosed(ClientHandler handler);
// 收到消息时通知
void onNewMessageArrived(ClientHandler handler, String msg);
}
}
2.客户端代码:
package net.qiujuer.lesson.sample.client;
import net.qiujuer.lesson.sample.client.bean.ServerInfo;
import net.qiujuer.lesson.sample.foo.Foo;
import net.qiujuer.library.clink.box.FileSendPacket;
import net.qiujuer.library.clink.core.IoContext;
import net.qiujuer.library.clink.impl.IoSelectorProvider;
import java.io.*;
public class Client {
public static void main(String[] args) throws IOException {
File cachePath = Foo.getCacheDir("client");
IoContext.setup()
.ioProvider(new IoSelectorProvider())
.start();
ServerInfo info = UDPSearcher.searchServer(10000);
System.out.println("Server:" + info);
if (info != null) {
TCPClient tcpClient = null;
try {
tcpClient = TCPClient.startWith(info, cachePath);
if (tcpClient == null) {
return;
}
write(tcpClient);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (tcpClient != null) {
tcpClient.exit();
}
}
}
IoContext.close();
}
private static void write(TCPClient tcpClient) throws IOException {
// 构建键盘输入流
InputStream in = System.in;
BufferedReader input = new BufferedReader(new InputStreamReader(in));
do {
// 键盘读取一行
String str = input.readLine();
if ("00bye00".equalsIgnoreCase(str)) {
break;
}
// --f url
if (str.startsWith("--f")) {
String[] array = str.split(" ");
if (array.length >= 2) {
String filePath = array[1];
File file = new File(filePath);
if (file.exists() && file.isFile()) {
FileSendPacket packet = new FileSendPacket(file);
tcpClient.send(packet);
continue;
}
}
}
// 发送字符串
tcpClient.send(str);
} while (true);
}
}
package net.qiujuer.lesson.sample.client;
import net.qiujuer.lesson.sample.client.bean.ServerInfo;
import net.qiujuer.lesson.sample.foo.Foo;
import net.qiujuer.library.clink.core.Connector;
import net.qiujuer.library.clink.core.Packet;
import net.qiujuer.library.clink.core.ReceivePacket;
import net.qiujuer.library.clink.utils.CloseUtils;
import java.io.File;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
public class TCPClient extends Connector {
private final File cachePath;
public TCPClient(SocketChannel socketChannel, File cachePath) throws IOException {
this.cachePath = cachePath;
setup(socketChannel);
}
public void exit() {
CloseUtils.close(this);
}
@Override
public void onChannelClosed(SocketChannel channel) {
super.onChannelClosed(channel);
System.out.println("连接已关闭,无法读取数据!");
}
@Override
protected File createNewReceiveFile() {
return Foo.createRandomTemp(cachePath);
}
@Override
protected void onReceivedPacket(ReceivePacket packet) {
super.onReceivedPacket(packet);
if (packet.type() == Packet.TYPE_MEMORY_STRING) {
String string = (String) packet.entity();
System.out.println(key.toString() + ":" + string);
}
}
public static TCPClient startWith(ServerInfo info, File cachePath) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
// 连接本地,端口2000;超时时间3000ms
socketChannel.connect(new InetSocketAddress(Inet4Address.getByName(info.getAddress()), info.getPort()));
System.out.println("已发起服务器连接,并进入后续流程~");
System.out.println("客户端信息:" + socketChannel.getLocalAddress().toString());
System.out.println("服务器信息:" + socketChannel.getRemoteAddress().toString());
try {
return new TCPClient(socketChannel, cachePath);
} catch (Exception e) {
System.out.println("连接异常");
CloseUtils.close(socketChannel);
}
return null;
}
}
最终演示效果如下: