Я очень страдаю от создания простого ChatServer на Java с использованием библиотек NIO. Интересно, кто-нибудь может мне помочь?
Я делаю это с помощью SocketChannel и Selector для обработки нескольких клиентов в одном потоке. Проблема в том, что я могу принимать новые соединения и получать их данные, но когда я пытаюсь отправить данные обратно, SocketChannel просто не работает. В методе write () он возвращает целое число того же размера, что и данные, которые я ему передаю, но клиент никогда не получает эти данные. Странно, когда я закрываю приложение сервера, клиент получает данные. Это похоже на то, что socketchannel поддерживает буфер, и он сбрасывается только при закрытии приложения.
Вот некоторые подробности, чтобы дать вам больше информации, чтобы помочь. Я обрабатываю события в этом фрагменте кода:
private void run() throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
// Set it to non-blocking, so we can use select
ssc.configureBlocking( false );
// Get the Socket connected to this channel, and bind it
// to the listening port
this.serverSocket = ssc.socket();
InetSocketAddress isa = new InetSocketAddress( this.port );
serverSocket.bind( isa );
// Create a new Selector for selecting
this.masterSelector = Selector.open();
// Register the ServerSocketChannel, so we can
// listen for incoming connections
ssc.register( masterSelector, SelectionKey.OP_ACCEPT );
while (true) {
// See if we've had any activity -- either
// an incoming connection, or incoming data on an
// existing connection
int num = masterSelector.select();
// If we don't have any activity, loop around and wait
// again
if (num == 0) {
continue;
}
// Get the keys corresponding to the activity
// that has been detected, and process them
// one by one
Set keys = masterSelector.selectedKeys();
Iterator it = keys.iterator();
while (it.hasNext()) {
// Get a key representing one of bits of I/O
// activity
SelectionKey key = (SelectionKey)it.next();
// What kind of activity is it?
if ((key.readyOps() & SelectionKey.OP_ACCEPT) ==
SelectionKey.OP_ACCEPT) {
// Aceita a conexão
Socket s = serverSocket.accept();
System.out.println( "LOG: Conexao TCP aceita de " + s.getInetAddress() + ":" + s.getPort() );
// Make sure to make it non-blocking, so we can
// use a selector on it.
SocketChannel sc = s.getChannel();
sc.configureBlocking( false );
// Registra a conexao no seletor, apenas para leitura
sc.register( masterSelector, SelectionKey.OP_READ );
} else if ( key.isReadable() ) {
SocketChannel sc = null;
// It's incoming data on a connection, so
// process it
sc = (SocketChannel)key.channel();
// Verifica se a conexão corresponde a um cliente já existente
if((clientsMap.getClient(key)) != null){
boolean closedConnection = !processIncomingClientData(key);
if(closedConnection){
int id = clientsMap.getClient(key);
closeClient(id);
}
} else {
boolean clientAccepted = processIncomingDataFromNewClient(key);
if(!clientAccepted){
// Se o cliente não foi aceito, sua conexão é simplesmente fechada
sc.socket().close();
sc.close();
key.cancel();
}
}
}
}
// We remove the selected keys, because we've dealt
// with them.
keys.clear();
}
}
Этот фрагмент кода просто обрабатывает новых клиентов, которые хотят подключиться к чату. Итак, клиент устанавливает TCP-соединение с сервером и, как только его принимают, он отправляет данные на сервер, следуя простому текстовому протоколу, сообщая свой идентификатор и запрашивая регистрацию на сервере. Я обращаюсь с этим в методе processIncomingDataFromNewClient (ключ). Я также храню карту клиентов и их соединений в структуре данных, аналогичной хеш-таблице. Я делаю это, потому что мне нужно восстановить идентификатор клиента из соединения и соединение из идентификатора клиента. Это может быть показано в: clientsMap.getClient (ключ).
Но сама проблема заключается в методе processIncomingDataFromNewClient (ключ). Там я просто читаю данные, отправленные мне клиентом, проверяю их и, если все в порядке, отправляю сообщение обратно клиенту, чтобы сообщить, что он подключен к серверу чата.
Вот похожий фрагмент кода:
private boolean processIncomingDataFromNewClient(SelectionKey key){
SocketChannel sc = (SocketChannel) key.channel();
String connectionOrigin = sc.socket().getInetAddress() + ":" + sc.socket().getPort();
int id = 0; //id of the client
buf.clear();
int bytesRead = 0;
try {
bytesRead = sc.read(buf);
if(bytesRead<=0){
System.out.println("Conexão fechada pelo: " + connectionOrigin);
return false;
}
System.out.println("LOG: " + bytesRead + " bytes lidos de " + connectionOrigin);
String msg = new String(buf.array(),0,bytesRead);
// Do validations with the client sent me here
// gets the client id
}catch (Exception e) {
e.printStackTrace();
System.out.println("LOG: Oops. Cliente não conhece o protocolo. Fechando a conexão: " + connectionOrigin);
System.out.println("LOG: Primeiros 10 caracteres enviados pelo cliente: " + msg);
return false;
}
}
} catch (IOException e) {
System.out.println("LOG: Erro ao ler dados da conexao: " + connectionOrigin);
System.out.println("LOG: "+ e.getLocalizedMessage());
System.out.println("LOG: Fechando a conexão...");
return false;
}
// If it gets to here, the protocol is ok and we can add the client
boolean inserted = clientsMap.addClient(key, id);
if(!inserted){
System.out.println("LOG: Não foi possível adicionar o cliente. Ou ele já está conectado ou já têm clientes demais. Id: " + id);
System.out.println("LOG: Fechando a conexão: " + connectionOrigin);
return false;
}
System.out.println("LOG: Novo cliente conectado! Enviando mesnsagem de confirmação. Id: " + id + " Conexao: " + connectionOrigin);
/* Here is the error */
sendMessage(id, "Servidor pet: connection accepted");
System.out.println("LOG: Novo cliente conectado! Id: " + id + " Conexao: " + connectionOrigin);
return true;
}
И, наконец, метод sendMessage (клавиша SelectionKey) выглядит так:
private void sendMessage(int destId, String msg) {
Charset charset = Charset.forName("ISO-8859-1");
CharBuffer charBuffer = CharBuffer.wrap(msg, 0, msg.length());
ByteBuffer bf = charset.encode(charBuffer);
//bf.flip();
int bytesSent = 0;
SelectionKey key = clientsMap.getClient(destId);
SocketChannel sc = (SocketChannel) key.channel();
try {
/
int total_bytes_sent = 0;
while(total_bytes_sent < msg.length()){
bytesSent = sc.write(bf);
total_bytes_sent += bytesSent;
}
System.out.println("LOG: Bytes enviados para o cliente " + destId + ": "+ total_bytes_sent + " Tamanho da mensagem: " + msg.length());
} catch (IOException e) {
System.out.println("LOG: Erro ao mandar mensagem para: " + destId);
System.out.println("LOG: " + e.getLocalizedMessage());
}
}
Итак, сервер отправляет сообщение при печати примерно так:
LOG: Bytes sent to the client: 28 Size of the message: 28
Итак, он сообщает, что отправил данные, но клиент чата продолжает блокировать, ожидая в методе recv (). Таким образом, данные никогда не попадают в него. Когда я закрываю приложение сервера, все данные появляются в клиенте. Интересно, почему.
Важно сказать, что клиент находится на C, а сервер JAVA, и я использую оба на одной машине - гостя Ubuntu в virtualbox под windows. Я также запускаю как под хостом Windows, так и под Linux, и продолжаю сталкиваться с одной и той же странной проблемой.
Я прошу прощения за большую длину этого вопроса, но я уже искал много мест для ответа, нашел много учебников и вопросов, в том числе здесь, в StackOverflow, но не нашел разумного объяснения. Мне действительно не нравится этот Java NIO, и я видел множество людей, жалующихся на это тоже. Я думаю, что если бы я сделал это в C, было бы намного проще: -D
Так что, если бы кто-то мог мне помочь и даже обсудить это поведение, было бы здорово! : -)
Заранее всем спасибо,
Петерсон