Java中的SocketChannel发送数据,但它不';无法到达目的地应用程序
使用NIO库在Java中创建一个简单的聊天服务器让我很痛苦。不知是否有人能帮我。 我使用SocketChannel和Selector在一个线程中处理多个客户机。问题是:我能够接受新的连接并获取数据,但当我尝试发回数据时,SocketChannel根本不起作用。在write()方法中,它返回一个整数,该整数的大小与我传递给它的数据的大小相同,但客户端从未收到该数据。奇怪的是,当我关闭服务器应用程序时,客户端收到了数据。这就像socketchannel维护一个缓冲区,只有在我关闭应用程序时才会刷新它 这里有更多的细节,给你更多的信息帮助。我正在处理这段代码中的事件:Java中的SocketChannel发送数据,但它不';无法到达目的地应用程序,java,sockets,nio,Java,Sockets,Nio,使用NIO库在Java中创建一个简单的聊天服务器让我很痛苦。不知是否有人能帮我。 我使用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();
}
}
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;
}
这段代码只处理希望连接到聊天室的新客户端。因此,客户机与服务器建立TCP连接,一旦被接受,它将按照简单的文本协议向服务器发送数据,通知其id并请求注册到服务器。我在processIncomingDataFromNewClient(key)方法中处理这个问题。我还在一个类似于哈希表的数据结构中保存客户机及其连接的映射。我ḿ 这样做是因为我需要从连接中恢复客户端Id,从客户端Id中恢复连接。
但问题本身存在于方法processIncomingDataFromNewClient(key)中。在那里,我只需读取客户端发送给我的数据,对其进行验证,如果没有问题,我会向客户端发送一条消息,告知它已连接到聊天服务器。
下面是一段类似的代码:
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();
}
}
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;
}
因此,它告诉我们它发送了数据,但是聊天客户端一直在阻塞,在recv()方法中等待。因此,数据永远无法到达它。但是,当我关闭服务器应用程序时,所有数据都显示在客户机中。我想知道为什么
重要的是,客户端使用C语言,服务器使用JAVA语言,我在同一台机器上运行这两种语言,windows下virtualbox中的Ubuntu客户机。我也在windows主机和linuxes主机下运行,并不断遇到相同的奇怪问题
我很抱歉这个问题太长了,但我已经在很多地方搜索了答案,找到了很多教程和问题,包括在StackOverflow这里,但找不到合理的解释。我真的不喜欢这个JavaNIO,我看到很多人也在抱怨它。我在想,如果我用C语言写的话,它会容易得多
所以,如果有人能帮助我,甚至讨论一下这个行为,那就太好了!:-)
提前谢谢大家,
佩特森试试看
System.out.println("LOG: " + bytesRead + " bytes lidos de " + connectionOrigin);
buf.flip();
String msg = new String(buf.array(),0,bytesRead);
试一试
在accept块上,在非阻塞之后,尝试设置节点延迟,这样操作系统就不会等到自己的缓冲区已满才发送数据
socket.setTcpNoDelay(true);
在accept块上,在非阻塞之后,尝试设置节点延迟,这样操作系统就不会等到自己的缓冲区已满才发送数据
socket.setTcpNoDelay(true);
谢谢,但我知道没有这种方法。我在api中查找了类似的内容,但没有找到任何内容。在论文中,write方法应该立即发送字节。@akarnokd这是正确的。NIO中没有流缓冲,也没有
flush()
方法。@Peterson你解决过这个问题吗?注意:如果read()
返回-1,你应该关闭频道。谢谢,但我知道没有这种方法。我在api中查找了类似的内容,但没有找到任何内容。在论文中,write方法应该立即发送字节。@akarnokd这是正确的。NIO中没有流缓冲,也没有flush()
方法。@Peterson你解决过这个问题吗?注意:如果read()
返回-1,您应该关闭频道。我关闭了,但它不起作用。此外,用于发送消息的缓冲区与用于接收消息的缓冲区不同。但是谢谢你的回复。我做了,但它不起作用。此外,用于发送消息的缓冲区与用于接收消息的缓冲区不同。但是谢谢你的回复。这不是tcpNoDelay
所做的,也不能解决这个问题。这不是tcpNoDelay
所做的,也不能解决这个问题。