2016-10-22 10 views
2

친구들! 저는 Java NIO에 익숙하지 않고 현재 비 차단 채팅 앱을 만들고자합니다. 클라이언트는 문제없이 서버에 연결합니다. 클라이언트는 서버에 메시지 또는 몇 개의 메시지를 쓰지 만 서버는 소켓 연결이 클라이언트 코드에서 종료 될 때만 메시지 읽기를 시작하므로 모든 메시지에 대해 클라이언트 코드에서 SocketChannel (또는 Socket 만)을 만들어 닫아야합니다. -이게 나에게 맞는 것 같지 않아. 나는 간단한 Java I/O와 NIO Selector로 클라이언트 측을 시도했다. 같은 문제 - SocketChannel 또는 Socket이 클라이언트에서 닫힌 경우에만 서버가 읽기 시작합니다. 누군가는 저에게 비 연결 연결을하기의 적당한 방법을 이야기하거나 저에게 나의 논리에있는 과실을 보여 주실 수 있습니까? 고맙습니다!Java NIO 서버/클라이언트 채팅 앱 - 소켓을 닫음으로써 데이터를 전송하는 것

public class NIOServer implements Runnable { 

@Override 
public void run() { 
    try { 
     runServer(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 

private void runServer() throws IOException { 
    ServerSocketChannel server = ServerSocketChannel.open(); 
    server.socket().bind(new InetSocketAddress(8080)); 
    server.configureBlocking(false); 
    Selector selector = Selector.open(); 
    server.register(selector, SelectionKey.OP_ACCEPT); 

     while(true) { 
      int readyChannels = selector.selectNow(); 
      if(readyChannels==0){ 
       continue; 
      } 
      System.out.println("Ready channels: "+readyChannels); 

      Set<SelectionKey> selectionKeys = selector.selectedKeys(); 
      Iterator<SelectionKey> keyIterator = selectionKeys.iterator(); 

      while(keyIterator.hasNext()) { 
       SelectionKey key = keyIterator.next(); 
       keyIterator.remove(); 

       if(key.isAcceptable()){ 
        ServerSocketChannel acceptableServer = (ServerSocketChannel)key.channel(); 
        SocketChannel client = server.accept(); 
        if(client!=null){ 
         System.out.println("Client accepted!"); 
         client.configureBlocking(false); 
         SelectionKey selectionKey = client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE); 
        } 
       } 
       if (key.isReadable()) { 
        read(key); 
       } 

       /*if(key.isConnectable()){ 
        System.out.println("connectable"); 
       } 
       if(key.isWritable()){ 
        //System.out.println("writable"); 
       }*/ 
      } 

     } 
} 

public void read(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel)key.channel(); 
    channel.configureBlocking(false); 
    ByteBuffer buffer = ByteBuffer.allocate(100); 
    buffer.clear(); 
    int bytesRead = channel.read(buffer); 

    while(bytesRead>0){ 
     System.out.println("Read bytes: "+ bytesRead); 
     bytesRead=channel.read(buffer); 
     if(bytesRead==-1){ 
      channel.close(); 
      key.cancel(); 
     } 
     buffer.flip(); 
     while(buffer.hasRemaining()){ 
      System.out.print((char)buffer.get()); 
     } 
    } 


    //key.cancel(); 
    //channel.close(); 

} 

}

클라이언트 NIO Selector가 :

서버 코드

public class NIOSelectorClient implements Runnable{ 
private Selector selector; 

@Override 
public void run() { 
    try { 
     startClient(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 

public void startClient() throws IOException { 
    SocketChannel socketChannel= openConnection(); 
    selector = Selector.open(); 
    socketChannel.register(selector,SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE); 
    while(!Thread.interrupted()) { 
     int readyChannels = selector.selectNow(); 
     if(readyChannels==0) { 
      continue; 
     } 

     Set<SelectionKey> keySet = selector.selectedKeys(); 
     Iterator<SelectionKey> keyIterator = keySet.iterator(); 

     while(keyIterator.hasNext()) { 
      SelectionKey currentKey = keyIterator.next(); 
      keyIterator.remove(); 

      if(!currentKey.isValid()) { 
       continue; 
      } 
      if(currentKey.isConnectable()) { 
       System.out.println("I'm connected to the server!"); 
       handleConnectable(currentKey); 
      } 
      if(currentKey.isWritable()){ 
       handleWritable(currentKey); 
      } 
     } 
    } 
} 

private void handleWritable(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel)key.channel(); 
    ByteBuffer buffer = ByteBuffer.allocate(100); 
    Scanner scanner = new Scanner(System.in); 
    System.out.println("Enter message to server: "); 
    String output = scanner.nextLine(); 
    buffer.put(output.getBytes()); 
    buffer.flip(); 
    //while(buffer.hasRemaining()) { 
     channel.write(buffer); 
    //} 
    System.out.println("Message send"); 
    buffer.clear(); 
    channel.close(); 
    key.cancel(); 
} 

private void handleConnectable(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel) key.channel(); 
    if(channel.isConnectionPending()) { 
     channel.finishConnect(); 
    } 
    channel.configureBlocking(false); 
    channel.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ); 
} 

private static SocketChannel openConnection() throws IOException { 
    SocketChannel socketChannel = SocketChannel.open(); 
    socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080)); 
    socketChannel.configureBlocking(false); 
    while(!socketChannel.finishConnect()) { 
     System.out.println("waiting connection...."); 
    } 
    return socketChannel; 
} 

}

그리고 이것은 비 NIO cliet입니다 :

public class NIOClient { 
public static void main(String[] args) throws IOException { 
    Socket socket = new Socket("127.0.0.1", 8080); 
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 
    while(socket.isConnected()) { 
     //synchronized (socket) { 
      writeMessage(socket,writer); 
      //readServerMessage(socket); 
     //} 
    } 

} 

public static void writeMessage(Socket socket, BufferedWriter writer) throws IOException { 
    Scanner scanner = new Scanner(System.in); 
    System.out.println("Enter message: "); 
    String output = "Client 1: " + scanner.nextLine(); 
    writer.write(output); 
    writer.flush(); 
    //writer.close(); 
} 

public static void readServerMessage(Socket socket) throws IOException { 

} 

}

+0

TCP_NODELAY를 활성화 해보십시오 :'socketChannel.setOption (StandardSocketOptions.TCP_NODELAY, true);' 이렇게하면 TCP가 쓰기 버퍼에 데이터를 사용할 수있는 즉시 전송하게됩니다. – Malt

+0

댓글을 주셔서 감사합니다! 나는이 옵션을 true로 설정하려고 시도했지만 여전히 같은 방식으로 작동한다. 간단한 IO 소켓에서는 이것을 시도했지만 NIO SocketChannel에서는 다른 아이디어가 있는가? :) – bulibas

답변

1

코드는 NIO 실수의 일반적인 뗏목을 앓고 : 당신은 수면없이 선택하는

public class NIOServer implements Runnable { 

private void runServer() throws IOException { 
    ServerSocketChannel server = ServerSocketChannel.open(); 
    server.socket().bind(new InetSocketAddress(8080)); 
    server.configureBlocking(false); 
    Selector selector = Selector.open(); 
    server.register(selector, SelectionKey.OP_ACCEPT); 

     while(true) { 
      int readyChannels = selector.selectNow(); 

. 준비된 채널이 없으면이 루프는 CPU를 연기합니다. 제한 시간을 사용하십시오. 짧은 시간도 사용하십시오.

     SelectionKey selectionKey = client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE); 

이미 무언가를 작성하고 반환 값이없는 경우 OP_WRITE에 등록하지 않아야합니다.

public void read(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel)key.channel(); 
    channel.configureBlocking(false); 

채널은 이미 비 차단 모드입니다. 당신이 그것을 받아 들였을 때 거기에 놓으십시오. 그것이 비 차단 모드에 있지 않으면 당신은 그것을 선택할 수 없습니다. 풀다.

ByteBuffer buffer = ByteBuffer.allocate(100); 
    buffer.clear(); 

버퍼가 이미 선택되어 있습니다. 방금 만들었습니다. 풀다.

int bytesRead = channel.read(buffer); 

    while(bytesRead>0){ 
     System.out.println("Read bytes: "+ bytesRead); 
     bytesRead=channel.read(buffer); 
     if(bytesRead==-1){ 
      channel.close(); 
      key.cancel(); 

채널을 닫으면 키가 취소됩니다. 둘 다 필요하지 않습니다. 취소를 제거하십시오.

//key.cancel(); 
    //channel.close(); 

제거. 미래의 독자를 혼란스럽게하기 위해 주위에 거짓 코드를 두지 마십시오. NIO Selector가

클라이언트 :

public class NIOSelectorClient implements Runnable{ 
private Selector selector; 

public void startClient() throws IOException { 
    SocketChannel socketChannel= openConnection(); 
    selector = Selector.open(); 
    socketChannel.register(selector,SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE); 

위를 참조하십시오.

while(!Thread.interrupted()) { 
     int readyChannels = selector.selectNow(); 

위 참조.

  if(!currentKey.isValid()) { 
       continue; 
      } 

매우 좋지만 아래에있는 다른 하나보다 먼저이 테스트가 필요합니다. 이전 핸들러가 채널을 닫았거나 키를 취소했을 수 있기 때문에 currentKey.isValid() && currentKey.isReadable()입니다. 동일한 서버 코드에 적용됩니다.

  if(currentKey.isConnectable()) { 
       System.out.println("I'm connected to the server!"); 
       handleConnectable(currentKey); 
      } 
      if(currentKey.isWritable()){ 
       handleWritable(currentKey); 
      } 

isReadable()을 클라이언트에서 처리하지 마십시오. 당신은 어떤 입력을 기대하지 않습니까?

private void handleWritable(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel)key.channel(); 
    ByteBuffer buffer = ByteBuffer.allocate(100); 
    Scanner scanner = new Scanner(System.in); 
    System.out.println("Enter message to server: "); 
    String output = scanner.nextLine(); 

여기에 몇 가지 입력을 입력하는 사용자가 기다리는 모든 SocketChannels 포함한 전체 클라이언트를 차단하고 있습니다. 이것은 매우 열악한 디자인입니다.

buffer.clear(); 

이 것은 필요하지 않습니다. 버퍼를 로컬 변수로 해제하려고합니다. 너는 끝냈어.

channel.close(); 

채널을 한 번 작성하면 종료됩니까? 왜?

key.cancel(); 

채널을 닫으면 키가 취소됩니다. 둘 다 필요하지 않습니다. 너는 이것을 필요로하지 않는다. 풀다.

private void handleConnectable(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel) key.channel(); 
    if(channel.isConnectionPending()) { 
     channel.finishConnect(); 

finishConnect()

는이 방법에 더 아무것도하지 않고해야하는 경우 false를 반환 할 수 있습니다.

channel.configureBlocking(false); 

채널이 이미 차단 모드입니다. 그렇지 않으면 여기에 오지 못했습니다. 풀다.

channel.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ); 
} 

위 OP_WRITE를 참조하십시오.

private static SocketChannel openConnection() throws IOException { 
    SocketChannel socketChannel = SocketChannel.open(); 
    socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080)); 
    socketChannel.configureBlocking(false); 
    while(!socketChannel.finishConnect()) { 
     System.out.println("waiting connection...."); 
    } 

이 루프를 제거하십시오. 이것이 OP_CONNECT의 목적입니다. 당신은 개를 키우고 짖고 있습니다. 연결이 완료 될 때까지 여기서 나가지 않으려면 차단 모드로하십시오. 대신 CPU를 그냥 흡연.

public class NIOClient { 
public static void main(String[] args) throws IOException { 
    Socket socket = new Socket("127.0.0.1", 8080); 
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 
    while(socket.isConnected()) { 

소켓이 연결되어

는 그리고 이것은 비 NIO cliet입니다. 당신이 그것을 만들 때 당신은 그것을 연결했습니다. 그것은 그 길을 유지합니다. isConnected()은 피어 연결 끊기에 대한 유효한 테스트가 아닙니다.