티스토리 뷰
1. 버퍼의 사용
지난 시간에 Java NIO에서 버퍼가 어떻게 사용되는지와 더불어 Direct 버퍼와 Non-Direct Buffer에 대해서 알아보았다. 이번 시간에는 버퍼를 Java에서 어떻게 사용 할 수 있는지 알아보도록 하자.
버퍼는 Java.nio에 클래스로 소속되어 있다. 그리고 해당 클래스는 우리가 주고받게 될 데이터 양식을 기반으로 두고 있다. 상위에 Buffer 클래스가 존재하고 하위 서브클래스로 데이터 양식에 맞는 클래스가 존재하게 된다.
버퍼 종류 |
설명 |
ByteBuffer |
바이트 타입의 데이터를 받는 버퍼 |
CharBuffer |
문자열 타입의 데이터를 받는 버퍼 |
ShortBuffer |
Short형 타입의 데이터를 받는 버퍼 |
IntBuffer |
정수형 타입의 데이터를 받는 버퍼 |
LongBuffer |
Long형 타입의 데이터를 받는 버퍼 |
FloatBuffer |
Float형 타입의 데이터를 받는 버퍼 |
DoubleBuffer |
Double형 타입의 데이터를 받는 버퍼 |
버퍼는 데이터 나열 이외에도 총 4가지의 중요한 정보를 관리한다. 버퍼의 타입에 관계없이 상위 버퍼에서 아래의 4가지 정볼르 가지게 된다.
- 위치 (Position)
버퍼에서 읽거나 쓸 다음 위치를 의미한다. 즉 버퍼를 읽을 때 시작되는 지점을 의미하고 시작점은 0이다.
public final int position()
public final Buffer position(int newPosition)
- 용량 (Capacity)
버퍼가 보유할 수 있는 최대 요소의 수이다. 한번 설정이 되면 변경이 불가능 하기 떄문에 메소드를 통해 읽기만 가능하다.
public final int capacity()
- 한도 (Limit)
버퍼에서 접근할 수 있는 데이터의 마지막을 의미한다. 위에 있는 용량이 한도보다 더 크다면 한도 뒤에 데이터를 읽거나 쓸 수 없으므로 실질적 사용 용량은 이 한도로 결정 된다.
public final int limit()
public final Buffer limit(int newLimit)
- 표시 (Mark)
버퍼에서 클라이언트에 제한된 인덱스를 의미한다. 만약 위치가 표시 이하로 설정된다면 표시는 삭제된다.
public final Buffer mark() : 현재 위치에 표시를 설정
public final Buffer reset() : 호출하면 현재 위치가 표시된 위치로 설정
2. 버퍼 활용
- 버퍼 할당 (allocate)
Java에서 버퍼를 선언하는 방법은 다음과 같다. 버퍼를 사용하려는 데이터 타입을 활용해 선언하면 된다. Byte Buffer를 사이즈 100으로 선언하게 된다면 아래와 같다.
ByteBuffer bytebuffer = ByteBuffer.allocate(100);
- 버퍼 접근 (array)
버퍼의 접근은 배열 인자로 접근 가능하다. array() 메소드를 이용하게 되는데, 버퍼의 내부 데이터를 그대로 노출시킨다.
byte[] data1 = buffer1.array();
- 직접 할당 (allocateDirect)
앞서 배운 Direct 버퍼를 생성하기 위해서는 allocateDirect 메소드를 사용해야 한다.
ByteBuffer buffer = ByteBuffer.allocateDirect(100);
- 랩핑 (Wrapping)
이미 출력하고자 하는 데이터의 배열을 가지고 있다면 버퍼를 새로 만들고 채우는 것 보다 배열을 버퍼로 감싸면 된다.
byte[] data = "Some data".getBytes("UTF-8");
ByteBuffer buffer1 = ByteBuffer.wrap(data);
- 위치 및 한도 초기화 (clear)
위치(Position)를 0으로 설정하고 한도(Limit)를 용량으로 설정하여 초기화 하는 기능을 수행한다.
public final Buffer clear()
- 위치 재설정 (rewind)
위치(Position)를 0으로 설정하지만 한도는 변경하지 않는다.
public final Buffer rewind()
- 한도 재설정 (filp)
한도를 현재 위치로 설정하고 위치는 0으로 초기와 한다.
public final Buffer flip()
3. 버퍼의 데이터 읽고 쓰기
- Put
put() 메소드를 활용하여 버퍼에 데이터를 넣을 수 있다. 데이터를 넣는 방법은 2가지가 있는데, 버퍼 중 데이터를 넣는 위치를 지정하는 방법과 상대적 위치에 데이터를 넣는 2가지 방법이 있다.
- 특정 위치에 데이터 넣기 : public abstract byte put(int index, byte a);
- Position이 가르키고 있는 위치에 넣기 : public abstract byte put(byte a);
- Get
get() 메서드를 활용하면 버퍼에서 데이터를 읽어온다. put과 마찬가지로 인덱스를 지정해서 데이터를 읽어 올 수 있고, 위치가 가르키고 있는 현재 위치를 가져올 수도 있다.
- 특정 위치에 데이터 넣기 : public abstract byte get(int index, byte a);
- Position이 가르키고 있는 위치에 넣기 : public abstract byte get(byte a);
아래는 데이터를 넣고 읽는 방법의 예시 코드이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | import java.nio.ByteBuffer; public class Solution { public static void main(String[] args) { // TODO Auto-generated method stub // size 100 Buffer ByteBuffer bytebuffer = ByteBuffer.allocate(100); System.out.println("Position : " + bytebuffer.position()); System.out.println("Limit : " + bytebuffer.limit()); System.out.println("Capacity : " + bytebuffer.capacity()); // Position 0에 Mark bytebuffer.mark(); // 순서대로 데이터를 넣는다. (인덱스 미지정) bytebuffer.put((byte) 'a').put((byte) 'd').put((byte) 'r').put((byte) 'i').put((byte) 'a').put((byte) 'n'); // Position reset bytebuffer.reset(); // 순서대로 데이터를 출력. (인덱스 미지정) System.out.println("Relative Get : " + (char) bytebuffer.get() + (char) bytebuffer.get() + (char) bytebuffer.get() + (char) bytebuffer.get() + (char) bytebuffer.get() + (char) bytebuffer.get()); // Position 0에 Mark bytebuffer.mark(); // 순서대로 데이터를 넣는다. (인덱스 미지정) bytebuffer.put(7, (byte) 'a').put(10, (byte) 'd').put(12, (byte) 'r') .put(1, (byte) 'i').put(6, (byte) 'a').put(22, (byte) 'n'); // Position reset bytebuffer.reset(); // 순서대로 데이터를 출력. (인덱스 미지정) System.out.println("Absolute Get : " + (char) bytebuffer.get(7) + (char) bytebuffer.get(10) + (char) bytebuffer.get(12) + (char) bytebuffer.get(1) + (char) bytebuffer.get(6) + (char) bytebuffer.get(22)); } } | cs |
4. 버퍼를 이용한 에코 서버
앞에서 Buffer를 사용하지 않고 Java IO 방식으로 구현 했던 에코 서버를 Java NIO방식으로 구현 가능하다. ByteBuffer를 이용한 에코 서버는 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class Solution { // Port Number 지정 public static int DEFAULT_PORT = 7; public static void main(String[] args) { // TODO Auto-generated method stub int port; // 포트 연결 시도 try { port = Integer.parseInt(args[0]); } catch (RuntimeException ex) { port = DEFAULT_PORT; } // 포트 연결 대기중 System.out.println("Listening for connections on Port" + port); // 서버 소켓 채널 ServerSocketChannel serverChannel; Selector selector; // 서버 소켓 연결 try { // Open serverChannel = ServerSocketChannel.open(); ServerSocket serversocket = serverChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); // Bind serversocket.bind(address); serverChannel.configureBlocking(false); selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException ex) { ex.printStackTrace(); return; } // 연결 while(true) { try { selector.select(); } catch (IOException ex) { ex.printStackTrace(); break; } Set<SelectionKey> readyKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = readyKeys.iterator(); while(iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); // Client try { if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel client = server.accept(); System.out.println("Accepted connection from + " + client); client.configureBlocking(false); SelectionKey clientKey = client.register( selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ); ByteBuffer buffer = ByteBuffer.allocate(100); clientKey.attach(buffer); } if (key.isReadable()){ SocketChannel client = (SocketChannel) key.channel(); ByteBuffer output = (ByteBuffer) key.attachment(); client.read(output); } if (key.isWritable()){ SocketChannel client = (SocketChannel) key.channel(); ByteBuffer output = (ByteBuffer) key.attachment(); output.flip(); client.write(output); output.compact(); } } catch(IOException ex) { key.cancel(); // Close try { key.channel().close(); } catch(IOException ex2) {} } } } } } | cs |
출처 : Java Network Programming - 엘리엇 러스트 해럴드 (O'REILLY)
'Skill > Programming - Network' 카테고리의 다른 글
22. UDP (User Datagram Protocol) (0) | 2019.10.04 |
---|---|
21. 채널 (Channel) (0) | 2019.09.27 |
19. Java NIO (New IO) (0) | 2019.09.11 |
18. 서버 소켓 프로그래밍 예제 (Java) (0) | 2019.09.08 |
17. 네트워크 보안 및 보안 소켓 (0) | 2019.08.31 |