package serverUtils;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;

public class Console {

public void start() throws IOException {
ConnectionManager connectionManager = new ConnectionManager(6789);

connectionManager.initialize();

Selector selector = Selector.open();
connectionManager.registerSelector(selector);

while (true) {
int readyChannelsCount = selector.select();
if (readyChannelsCount == 0) {
continue;
}
Iterator<SelectionKey> selectedKeysIterator = selector.selectedKeys().iterator();
while (selectedKeysIterator.hasNext()) {
SelectionKey key = selectedKeysIterator.next();
selectedKeysIterator.remove();
connectionManager.processQuery(selector, key);
}
}
}
}

package serverUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;

public class ConnectionManager {
private static final String ACK_MESSAGE_PREFIX = “<%s>Ack”;
private static final int BUFFER_SIZE = 1024 * 1024;

ServerSocketChannel serverSocketChannel;
int port;

public ConnectionManager(int port) {
this.port = port;
}

public void initialize() throws IOException {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
System.out.println(“Server started on port " + port);
System.out.println(”============ * ============");
}

public void registerSelector(Selector selector) throws ClosedChannelException {
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}

public void processQuery(Selector selector, SelectionKey key) throws IOException {
if (!key.isValid()) {
return;
}
if (key.isAcceptable()) {
acceptConnection(key, selector);
} else if (key.isReadable()) {
handleRead(key);
}
}
private void acceptConnection(SelectionKey key, Selector selector) throws IOException {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel clientSocketChannel = serverSocketChannel.accept();
clientSocketChannel.configureBlocking(false);
clientSocketChannel.register(selector, SelectionKey.OP_READ);

System.out.println(“Accepted connection from " + clientSocketChannel.getRemoteAddress());
}

private void handleRead(SelectionKey key) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
SocketChannel clientSocketChannel = (SocketChannel) key.channel();
int bytesRead;
while ((bytesRead = clientSocketChannel.read(buffer)) > 0) {
buffer.flip();

// проверяем наличие данных в буфере
if (buffer.remaining() < 8) {
continue;
}

// читаем длину имени файла в байтах
int nameLength = buffer.getInt();

// проверяем наличие данных в буфере
if (buffer.remaining() < nameLength + 4) {
buffer.position(buffer.position() - 4);
buffer.compact();
continue;
}

// читаем имя файла
byte[] nameBytes = new byte[nameLength];
buffer.get(nameBytes);
String fileName = new String(nameBytes, StandardCharsets.UTF_8);

// читаем длину файла в байтах
int fileLength = buffer.getInt();

// проверяем наличие данных в буфере
if (buffer.remaining() < fileLength + 8) {
buffer.position(buffer.position() - 8);
buffer.compact();
continue;
}

// читаем содержимое файла
byte[] fileContent = new byte[fileLength];
buffer.get(fileContent, 0, fileLength);

// проверяем разделитель
long separator = buffer.getLong();
if (separator != 0) {
throw new RuntimeException(“Invalid separator”);
}

// очищаем буфер
buffer.clear();

// сохраняем файл
saveFile(fileName, fileContent);

// отправляем подтверждение о сохранении файла
sendAck(clientSocketChannel, fileName);
}

if (bytesRead == -1) {
System.out.println(“Closed connection with " + clientSocketChannel.getRemoteAddress());
clientSocketChannel.close();
key.cancel();
System.out.println(”============ * ============”);
}
}


private void saveFile(String filename, byte[] fileContent) throws IOException {
File file = new File(“files\” + filename);

System.out.println("Saving file " + filename);

try (FileOutputStream outputStream = new FileOutputStream(file)) {
outputStream.write(fileContent);
}
System.out.println(“File " + filename + " saved successfully”);
}

private void sendAck(SocketChannel clientSocketChannel, String filename) throws IOException {
String ackMessage = String.format(ACK_MESSAGE_PREFIX, filename);
byte[] ackMessageBytes = ackMessage.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(ackMessageBytes.length + Integer.BYTES);
buffer.putInt(ackMessageBytes.length);
buffer.put(ackMessageBytes);
buffer.flip();

while (buffer.hasRemaining()) {
clientSocketChannel.write(buffer);
}

System.out.println("Sent acknowledgement for " + filename);
buffer.clear(); // Очищаем буфер перед чтением новых данных
}
}

Есть приложение на java. В серверную часть нужно добавить возможность обработки нескольких клиентов. Например, если один из клиентов остановился на передаче файла где-то на половине, и чтобы не крутиться в холостом цикле, в это время необходимо обрабатывать запросы других клиентов. Для достижения данного условия необходимо создать контекст соединения и связать его с конкретным соединением (Selection Key) и получать его через метод attachment. В этом же контексте можно хранить название файла, текущий состояние буффера, этап передачи файла. Использовать многопоточность нельзя