[까먹지말자]maven 설정 프로그래밍


아래 설정을 추가하면 메이븐 빌드와 함께 서버를 실행할 수 있다.
mvn clean package exec:exec 이렇게 실행하면 된다.

  <build>
    <plugins>
      ....
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.2.1</version>
        <configuration>
          <executable>java</executable>
          <arguments>
            <argument>-classpath</argument>
            <classpath/>
            <argument>${mainClass}</argument>
            <argument>${server.port}</argument>
          </arguments>
        </configuration>
      </plugin>
    </plugins>
  </build>

[번역-진행중]Java NIO Selector


Java NIO Selector

Selector는 하나 또는 더 많은 NIO 채널을 검토할 수 있는 자바 NIO 컴포넌트이다. 그리고 채널들이 읽기 또는 쓰기 상태가 준비되었는지 결정할 수 있다. 이런 방법은 하나의 쓰레드에서 여러 채널을 관리할 수 있다. 그리고 다중 네트워크 연결에서도 그렇다.

이 글에서 포함되어 있는 토픽 리스트가 아래에 있다.

1. 왜 Selector를 사용해야 하는가
2. Selector 생성
3. Selector로 채널 등록하기
4. SelectionKey's
5. Selector를  통해서 채널 선택하기
6. 깨우기
7. 닫기
8. 풀 예제 소스

왜 Selector를 사용해야 하는가

다중 채널을 다루기 위해서 단일 쓰레드만 사용하는 장점은 채널을 핸들링하기 위해서 적은 쓰레드가 필요하다는 것이다. 확실히 모든 채널을 다루기 위해서 단지 하나의 쓰레드만 사용할 수 있다. 쓰레드간의 스위칭은 운영체제에서 비용이 많이 든다. 그리고 운영체제에서 각각의 쓰레드는 어떤 리소스를 차지하고 있다. 예를 들자면 메모리 같은 것들.. 더 적은 쓰레드를 사용함으로써 더 나아진다.

하지만 명심해야 할 점이 있다. 현대의 운영 체제와 CPU는 훨씬 더 좋아졌다. 그리고 멀티태스킹에서 훨씬 더 좋아졌다. 멀티쓰레딩의 오버해드는 예전보다 더 적은 시간이 걸리게 되었다. 사실 만약 CPU가 멀티 코어라면 멀티쓰레드를 안쓰는 것은 CPU 파워를 낭비하는 것이다. 어째든 그런 디자인 토론은 다른 글에서 다룬다. 오늘 이야기할 것은 하나의 쓰레드에서 Selector를 이용해서 다중채널을 다룰 수 있다.

세 개의 채널을 다루기 위해서 Selector를 이용하는 쓰레드의 도식화이다.

그림 추가

Selector 생성

Selector.open() 메소드를 호출해서 Selector를 생성한다. 아래와 같다.

Selector selector = Selector.open();

Selector로 채널 등록하기

Selector를 통해서 채널을 사용하기 위해서 반드시 채널을 Selector를 이용해서 등록해야 한다. 아래는 SelectableChannel.register() 메소드를 이용하는 방법이다.

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

채널은 Selector를 사용하기 위해서 non-blocking 모드여야 한다. 이것은 FileChannel을 Selector로 사용할 수 없다는 것을 의미한다. FileChannel은 non-blocking 모드로 바꿀 수 없기 때문이다. 하지만 소켓 채널은 잘 작동한다.

두번째 register 메소드의 파라메터에 대해서 알아보자. "interest set"이라고 하며, Selector를 통해서 채널에서 리슨하길 원하는 이벤트가 어떤 것인지 의미한다. 4가지 이벤트가 있다.

1. connect
2. accept
3. read
4. write

만약 한가지 이상의 이벤트에 관심을 가져야 한다면 OR 연산자를 통해서 함께 사용할 수 있다. 아래 와 같이

int interestSet = SelectionKey.OP_READ + SelectionKey.OP_WRITE;

이 글 아래에서 interest set에 대해서 더 이야기할 것이다.

SelectionKey's

이전 섹션에서 언급했듯이, Selector를 통해서 채널을 등록했을 때 register 메소드는 SelectionKey 객체를 리턴한다. 이 SelectionKey 객체는 몇 가지 흥미로운 속성들을 가지고 있다.

* The interest Set
* The ready set
* The Channel
* The Selector
* An attached object (optional)

아래에 이 속성들에 대해서 설명할 것이다.

Interest Set

Interest Set은 

채널이 "fires and event(이벤트 발생했다)"는 것은 그런 이벤트가 준비되었다는 것을 말한다. 그래서 또 다른 서버에 성공적으로 접속한 채널은 "connect ready". 서버 소켓

[번역]Java Networking: Protocol Design 프로그래밍


만약 client-server 시스템을 디자인하고 있다면 아마도 서버와 클라이언트 간의  통신 프로토콜 역시 디자인했을 것이다. 물론 종종 이미 정의된 HTTP, XML-RPC(XML over HTTP), SOAP(also XML over HTTP) 같은 걸로 결정했을 수도 있다. 하지만 프로토콜 선택이 아직 미정이라면 클라이언트-서버 프로토콜을 디자인하는데 있어서 생각해봐야 할  몇 가지 이슈를 보여줄려고 한다. 

1. Client - Server Roundtrips - 클라이언트/서버 왕복
2. Demarcating the end of requests and response - 요청과 응답의 끝 지정하기
3. Penetrating Firewalls - 방화벽 회피하기

클라이언트 - 서버 왕복

클라이언트와 서버가 특정 오퍼레이션을 수행하기 위해서 통신을 하고 있을 때, 정보를 주고 받는다. 예를 들어서 클라이언트는 수행해야 할 서비스를 요청할 것이다. 그리고 서버는 그 서비스를 수행하려고 할 것이다. 그리고 클라이언트에게 알려줄 결과가 담긴 응답을 되돌려준다. 그런 정보 교환을 roundtrip이라고 부른다.

컴퓨터(서버와 클라이언트)는 인터넷 구간을 통해서 또다른 컴퓨터로 데이터를 보낸다. 이런 작업은 데이터를 보내는데 시간이 걸리고 상대방이 받는데 시간이 걸린다. 이것이 인터넷을 통해서 데이터를 주고 받는데 걸리는 시간이다. 이 시간을 대기 시간(latency)이라고 부른다.

여러분이 사용하려고 하는 프로토콜을 이용해서 더 많은 왕복(roundtrip)을 하면 할 수록 프로토콜은 더 느려진다. 특히 대기 시간이 높다면 더더욱 심해진다. HTTP 프로토콜은 어떤 서비스를 수행할 때 하나의 요청과 하나의 응답으로 구성되어 있다. 단일 왕복이라고 말한다. 한 편 SMTP 프로토콜은 email을 보내기 전까지 여러 번의 왕복으로 구성되어 있다. 

The only reason to break your protocol up into multiple roundtrips is, 만약 많은 양의 데이터를 클라이언트에서 서버로 보내야 한다면 두 가지 옵션이 있다.

1. 분리된 왕복으로 헤더 정보를 보낸다.
2. 메시지 바디를 더 작은 chunk로 나눈다.

첫번째로 헤더를 분리된 roundtrip으로 보내서 서버가 헤더 정보에 대한 초기 검증을 할 수 있다면 시스템이 더 스마트하게 될 수 있다. 만약 잘못된 헤더 정보라면 덩치가 큰 바디를 낭비일 수 있다. 

만약 엄청 큰 데이터를 전송하는 중에 네트워크 접속이 실패한다면 여러분은 그 데이터를 전부 재전송해야 할지도 모른다. 데이터를 작은 단위로 쪼개서 보냄으로써 네트워크가 끊어졌다가 다시 연결되었을 때에 전송하던 데이터부터 다시 전송하기만 하면 된다.성공적으로 전송된 청크는 재전송하지 않아도 된다.

Demarcating the End of Requests and Responses

만약 프로토콜이 동일한 연결을 통해서 다중 요청 전송을 허용한다면, 서버가 하나의 요청의 끝과 시작을 알게 해주는 방법이 필요하다. 클라이언트는 어떤 응답의 끝과 또다른 응답의 시작되었을 때를 알 필요가 있다.

요청 종료를 정하는 두 가지 방법이 있다.

1. 요청 데이터의 시작 부분에 요청 데이터의 길이를 보내는 방법
2. 데이터 요청 후에 요청 종료 마커를 보내는 방법

HTTP 프로토콜은 첫번째 매커니즘을 사용한다. 서버는 요청 헤더에서 Content-Length 필드를 받는다. 이 필드는 그 헤더를 포함하는 요청 데이터가 몇 바이트 크기인지 알려준다.

이 모델의 장점은 요청 종료 마커에서 발생하는 오버헤드가 없다는 것이다. 요청 종료 마커처럼 보이는 데이터를 피하기 위해서 데이터의 바디를 인코딩하지 않아도 된다.

첫번째 모델의 단점은 전송하는 쪽에서 실제 데이터를 보내기 전에 데이터의 크기를 꼭 보내야 한다는 것이다. 만약에 데이터가 동적으로 생성된다면 데이터를 전송하기 전에 먼저 모든 데이터를 꼭 버퍼에 담아서 크기를 계산해야만 한다.

요청 종료 마커를 사용함으로써 상대방에 데이터를 얼마나 보내는지 알려줄 필요가 없다. 단지 요청 데이터 끝에 요청 종료 마커를 보내기만 하면 된다. 하지만 실제 데이터 안에 요청 종료 마커와 동일한 패킷이 포함되어 보내지지 않도록 조심해야 한다. 아래에 한가지 방법을 제시한다.

요청 종료 마커가 바이트로 255 값을 가진다고 하자. 물론 데이터 역시 255 값을 가질 수 있다. 그래서 255 값을 가지는 각각의 데이터는 추가적으로 255 값을 더해서 보낸다. 요청 종료 마커는 255 값 뒤에 꼭 0 값이 따라오도록 수정했다. 아래에 인코딩한 요약본이다.

255 in data --> 255, 255
요청 종료 마커 --> 255, 0

모든 255 값을 255,255로 변경했기 때문에 255, 0 시퀀스는 데이터에서 절대 나타나지 않을 수 있게 되었다. 그리고 255,255,0 은 255, 0으로 오판되지 않을 것이다. 첫번째 255들은 함께 함께 해석될 것이고, 마지막 0만 해석된다.

Penetrating Firewalls

대부분의 방화벽은 HTTP 프로토콜 외에 모든 트래픽을 차단한다. 그러므로 HTTP 프로토콜 위에 응용 프로토콜(XML-RPC, SOAP, REST)을 올리는 것도 좋은 아이디어이다.

HTTP 프로토콜 위에 레이어하기 위해서 서버/ 클라이언트 통신에서 HTTP 요청/응답 안쪽에 데이터를 앞뒤로(또는 이리저리) 보내야 한다. 기억하라. HTTP 요청과 응답은 HTML이나 택스보다 더 많은 것을 포함할 수 있다. 안에다가 바이너리 데이터도 보낼 수 있다. 

HTTP 프로토콜 위에 요청을 레이어함으로 약간 이상해질 수 있는 단 한 가지는 HTTP 요청은 꼭 Host 헤더를 포함해야  한다는 것이다. 만약 HTTP 프로토콜 위에 P2P 프로토콜을 디자인하고 있다면, 피어(peers)들은 다중 "Hosts"로 운영하지 않아야 할 것이다. 이런 요구되는 헤더 필드는 그런 상황에서 불필요한 오버헤드가 된다.(하지만 작은 한개일 뿐)

[번역]java nio scatter / gather 프로그래밍



Java NIO는 내장 scatter/gather를 지원한다. Scatter / gather는 채널로부터 읽고, 채널에 쓰는데 사용되는 개념이다.

"scattering"은 채널로부터 데이터를 하나 이상의 버퍼로  읽기 동작이다. 그러므로, 채널은 채널에서 읽은 데이터를 여러 개의 버퍼로 "흩뿌린다".

채널로부터 읽은 "gathering"은 쓰기 동작이다. 이는 하나의 채널로 여러 buffer로부터 읽은 데이터를 쓰게 되는 것이다. 이렇게 채널은 여러 개의 버퍼로부터 데이터를 읽어서 하나의 채널에 쓰는 것이다.

Scatter / Gather 는 다양한 파트에서 분산되서 전송된 데이터를 가지고 작업할 필요가 있는 상황에서 매우 유용할 수 있다. 예를 들어서 메시지가 헤더와 바디로 구성되어 있을 때 헤더와 바디를 분리된 버퍼에 유지하는 경우가 있다. 이럴 때 Scatter/gather를 사용함으로써 헤더와 바디를 분리하는 작업을 더 쉽게 만들 수 있다.

Scattering Reads

"scattering read"는 하나의 채널로부터 데이터를 읽어서 여러 개의 버퍼에 넣는다. 아래는 이런 원리를 도식화 한 것이다.



아래는 scattering read를 수행하는 방법을 보여주는 샘플 코드이다.

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = {header, body};

channel.read(bufferArray);

배열에 어떻게 버퍼가 처음 삽입되는지 알아보자. 그러고 나서 배열은 channel.read() 메소드에 파라메터로써 전달된다. 그러면 read() 메소드는 채널로부터 읽은 데이터를 배열 시퀀스 순서대로 나오는 버퍼에 쓴다. 버퍼가 꽉 차게 되면, 채널은 다음 버퍼에 쓰기 위해서 이동한다.

scattering read가 다음 버퍼로 이동하기 전에 하나의 버퍼를 다 채운다는 사실은 동적인 길이를 가지는 메시지 파트에 대해서 적합하지 않다는 것을 의미한다. 다른 말로 하면 만약 여러분이 헤더와 바디를 가지고 있다면 헤더는 고정 길이여야 한다. 그러면 scattering read는 잘 동작한다. 

Gathering Writes

"gathering write"는 데이터를 여러 개의 버퍼로부터 읽어서 하나의 채널에 쓴다. 아래는 도식화된 이미지이다.


"gathering write" 샘플 코드

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = {header, body};
channel.write(buffers);

버퍼들의 배열은 write 메소드에 전달된다. 메소드는 배열에 들어있는 버퍼의 순서대로 버퍼의 내용을 쓴다. 단, 버퍼의 limit와 position 사이의 데이터만 쓰여진다. 예를 들어서 버퍼가 128바이트 크기를 가지고 있지만 58바이트를 포함하고 있다면, 단지 58바이트만 채널에 쓰여진다. 그러므로 gathering write는 동적인 크기를 가지는 메시지에 대해서 잘 동작한다. 이 부분은 scattering reads와 대조를 이룬다.


리눅스 서버 ulimit 설정하기 프로그래밍

아래 명령어를 입력하면 리눅스 ulimit 값을 확인할 수 있다.


$ ulimit –a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 62341
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 10240   
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 1024   
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

 

위 설정을 바꾸기 위해서는 root 권한을 가진 사용자로 /etc/security/limits.conf 파일을 열어서 아래 항목을 추가하거나 수정한다.


 

서버 limits.conf 설정

kildong soft nproc unlimited

kildong hard nproc unlimited

kildong soft nofile 10240

kildong hard nofile 20480


 

형식 <domain> <type> <item> <value>


 

Domain : 특정 사용자명이나 그룹명 등으로 지정할 수 있고, wildcard 문자(*)을 사용하여 전체 사용자를 지정할 수 있다.

Type : hard로 설정하면 superuser 와 커널에 의해 설정이 되게 하며, user는 설정된 값 이상으로 변경이 가능하다. soft 옵션은 soft로 설정한 값은 user가 얼마든지 설정값을 변경 가능하다.

Item : 여러 가지 설정이 있으며, 이 중에 아래 두가지에 대해서 설명한다.

  • nofile : 파일 오픈 최대 개수, 기본값은 1024로 되어 있다. 1024 -> 10240
  • nproc : 프로세스 생성 최대 개수(리눅스에서는 Java 쓰레드도 프로세스로 인식된다.) 이 값은 아무리 크게 잡아도 커널 내부에서 시스템의 사양에 따라 정한 최대값 이상을 넘지 않도록 설정되어 있다. Unlimited로 설정한다.

참고 사이트

http://blog.naver.com/yehyang0512/40146844770

http://blog.daum.net/ljs3075/3


1 2 3 4
Top