NIO (New IO)


자바에서 Socket 통신을 할 때 기존 IO 방식에서는 네트워크 처리와 속도에 대해 매우 제한적이었다.


하지만 JDK1.3 버전 이후 Java IO 대신 NIO를 사용하여 한계를 보안하였다.





[IO Process]

 



: 블로킹 문제를 해결하기 위해 클라이언트마다 쓰레드가 추가 되었고 멀티쓰레드 환경에서 수많은 쓰레드로 인해 메모리의 오버헤드가 발생할 수 있다.


  






[NIO Process]



: Selector는 멀티쓰레드 환경에서 여러 클라이언트의 작업요청에 대응할 수 있는 좋은 방법이다. 

 작업을 수행하는 work 쓰레드는 1개 이상이 될 수 있다.   

 물론 클라이언트가 너무 많으면, 이 과정에서 selector와 work 쓰레드가 문제가 발생할 수 있다.

 Accept와 Read 단계에서 SelectorPoolThreadPool을 사용하여 성능을 고려할 수 있다.






Selector Server 간단한 예제



    

package selector; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; 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.Arrays; import java.util.Iterator; public class Server implements Runnable { private Selector select; private InetSocketAddress sockAddr; public Server(String host, int port) { sockAddr = new InetSocketAddress(host, port); } public void accept(SelectionKey key) { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); SocketChannel channel; try { channel = serverChannel.accept(); channel.configureBlocking(false); Socket socket = channel.socket(); SocketAddress remoteAddr = socket.getRemoteSocketAddress(); System.out.println("Connected to: " + remoteAddr); channel.register(key.selector(), SelectionKey.OP_READ); // 읽을 수 있는 모드로 전환 } catch (Exception e) { // TODO: handle exception } } public void read(SelectionKey key) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int numRead = -1; try { numRead = channel.read(buffer); if (numRead == -1) { // 아직 읽지 않았다면 읽는다. channel.close(); key.cancel(); return; } else { System.out.println("read:" + new String(buffer.array())); channel.close(); key.cancel(); return; //읽고 종료 } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void run() { // TODO Auto-generated method stub try { // selector 열기 select = Selector.open(); // 서버 소켓 채널 열기 ServerSocketChannel sockChannel = ServerSocketChannel.open(); sockChannel.configureBlocking(false); // blocking 모드 false sockChannel.bind(sockAddr); System.out.println("Start Server:"+sockChannel.getLocalAddress()); // 서버 셀렉터를 클라이언트 연결을 수용하기 위한 키로 등록합니다. sockChannel.register(select, SelectionKey.OP_ACCEPT); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } while (true) { try { select.select(); // 여기서 Blocking 됨. 등록된 키를 가져온다. // select.selectNow(); // 비동기 처리. Iterator<?> keys = select.selectedKeys().iterator(); while (keys.hasNext()) { SelectionKey key = (SelectionKey) keys.next(); if (!key.isValid()) { // 사용가능한 상태가 아니면 그냥 넘어감. continue; } if (key.isAcceptable()) { // select가 accept 모드이면 accept(key); } else if (key.isReadable()) { // select가 read 모드이면 read(key); } keys.remove(); // 중요함 . 처리한 키는 제거 } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static void main(String[] args) { // TODO Auto-generated method stub Server server = new Server("localhost", 8080); server.run(); } }






3-Way Handshake


서버와 클라이언트간의 TCP 연결 단계에서 신뢰성 있는 연결을 하게 된다.

이 때 데이터의 정확한 전송을 보장하기 위한 세션을 성립(Establish) 단계를 위해 3-Way Handshake 방식을 사용하게 된다.





1-1)client는 TCP SYNchronize 패킷을 server로 전송합니다.

1-2)server는 client의 SYN을받습니다.
2-1)server는 SYNchronize-acknowledgement를 보냅니다.
2-2)client는 server의 SYN-ACK를 수신합니다.
3-1)client는 ACKnowledge를 보낸다.
3-2)server는 ACK를 수신합니다.

 -> TCP 소켓 연결이 설정.  




4-Way Handshake


4-way handshake는 클라이언트와 서버의 통신을 종료하기 위해서 사용된다.



1) 클라이언트가 연결을 종료하겠다는 FIN패킷을 전송


2) 서버는 세그먼트A를 확인 후 클라이언트에게 ACK패킷을 발송한 후, 자신의 통신이 끝날 때까지 대기(TIME-WAIT)한다. 이때, 클라이언트는 서버의 FIN패킷을 대기(FIN-WAIT-2)한다.


3) 서버의 통신이 끝나서 종료할 준비가 되었다면, 클라이언트에게 FIN패킷 전송한다.

4) 클라이언트는 서버에 ACK패킷을 전송하고 서버는 ACK 패킷을 받는다.




- 서버에서 FIN패킷을 발송하기 전에 전송한 패킷이 라우팅 지연이나 패킷 유실로 인한 재전송 등으로 FIN패킷보다 늦게 도착하는 상황이 발생할 수 있다.

- 클라이언트가 세션을 종료한 후, 뒤늦게 도착하는 패킷이 있다면 이 패킷은 드랍되고 데이터는 유실된다.

- 이런 현상에 대비하여 클라이언트는 서버로부터 FIN패킷을 수신하더라도 일정시간(디폴트 240초) 동안 세션(session)을 남겨놓고 잉여 패킷을 기다리는 과정을 거치는데, 이 과정을 TIME-WAIT라고 한다.









※ 왜 3 way-handshake 와 4-way handshake 방식을 사용할까?


: TCP는 4layer에서 수행되며 신뢰성 있는 통신이다.  클라이언트와 서버간의 연결/종료는 데이터의 정확한 전달만큼 중요한 단계이다.


클라이언트-서버간 서로 SYN과 ACK 패킷을 통해 연결 요청에 대해 물어보고, FIN과 ACK 패킷을 통해 서로간의 통신중단에 대해 물어보는 과정이다.



 









참고: http://www.tcpipguide.com/free/t_TCPConnectionEstablishmentProcessTheThreeWayHandsh-3.htm







Payload


공급자 서버가 APN (ApplePushNotification Service)에 보내는 각 알림에는 페이로드가 포함되어 있다.


알림을 보내기 위해 메시지를 정의하고, 옵션을 선택하는 등의 역할을 하는  json 형태의 데이터다.





특징


- Json 형태의 데이터

- 디바이스로 알림을 보내기 위한 옵션 및 메시지 설정

- TCP Binary API 를 이용하여 보낼 시 최대 2KB 전송 가능

- HTTP/2 API 로 보낼 시 최대 4KB 데이터 전송 가능

- VolP 알림으로 보낼 시 최대 5KB 데이터 전송 가능






ex)


{ "aps" : { "category" : "NEW_MESSAGE_CATEGORY" "alert" : { "body" : "Acme message received from Johnny Appleseed", }, "badge" : 3, "sound" : “chime.aiff" } }







※ Aps Key에 대한 Json Dictionary



key

Value 

설명

Alert

String , 혹은 json dictionary

 

Badge

Number

0의 값은 뱃지를 제거

Sound

String

Apple 에서 지정한 string 문자열

Content-available

Number

1의 값은 백그라운드에서 앱을 깨우고 알림을 전달

category

String

알림의 식별자 값 지정

Thread-id

String

그룹화 알림을위한 앱 별 식별자를 나타내는 문자열 값을이 키에 입력하십시오. 알림 콘텐츠 추가 앱 정보를 제공하는 경우이 값을 사용하여 알림을 그룹화 할 수 있습니다.







※ Alert Key에 대한 Json Dictionary



key 

Value

설명

Title

String

알림의 제목

Body

String

 알림의 내용

Title-loc-key

String or null

로컬 라이즈 용의 파일 내의 타이틀 캐릭터 라인의 키

Title-loc-args

Array of String or null

가변 문자열 값의 형식 지정자의 장소에 표시

Action-loc-key

String or nullString

문자열을 지정하면 닫기 및보기 단추가 포함 된 경고가 표시됩니다.

Loc-key

String

현재 지역화 파일 의 경고 메시지 문자열에 대한 키

Loc-args

Array of string

가변 문자열 값의 형식 지정자의 장소에 표시

Launch-image

String

파일 이름 확장자의 유무에 관계없이 앱 번들에있는 이미지 파일의 파일 이름입니다. 사용자가 작업 버튼을 누르거나 작업 슬라이더를 움직일 때 이미지가 실행 이미지로 사용됩니다.
















Apns Connection Server


HTTP/2


Development enviroment : api.development.push.apple.com: 443 
production enviroment : api.push.apple.com: 443

(APN과 통신 할 때  2197 포트도 사용가능)


HTTP / 2 PING프레임을 사용하여 연결 상태 확인

 HTTP / 2 연결을 종료하기로 결정하면 GOAWAY프레임으로 확인 가능


    TCP Binary


Development enviroment : gateway.sandbox.push.apple.com : 2195 port

production enviroment : gateway.push.apple.com  : 2195 port


Development enviroment : feedback.sandbox.push.apple.com : 2196 port

production enviroment : feedback.push.apple.com  : 2196 port







Apns Connection Trust

APN에 연결할 때 공급자가 TLS 1.2 이상으로 연결 (Http 방식에서만) , 기본적으로 TLS 및 SSL 방식에서 연결
APN 공급자 apns 인증서 없이 연결하려면 개발자 계정을 통해 제공되는 키로 서명 된 공급자 인증 토큰을 만들어야 함. (Json Web Token)
APN은 각 연결에 대해 여러 개의 동시 스트림을 허용.

인증서가 아닌 토큰을 사용하여 APN에 대한 연결을 설정할 때 유효한 공급자 인증 토큰을 사용하여 푸시 메시지를 보낼 때까지 하나의 스트림에 연결 

-> 즉, 개발자 계정의 인증서 파일 혹은 개발자 인증 토큰을 통해 연결을 해야함.




   Device to Apns Connection




1.APNS 서버에 보안연결을 요청. (암호화 보안 프로토콜 SSLTLS) 

2.APNS 인증

3.APNS 인증서의 유효성 검사 
4. 디바이스 단말 인증
5. Device 인증서 유효성 검사 확인 이후 연결





  Provider to Apns Connection




1.APNS 서버에 보안연결을 요청. (암호화 보안 프로토콜 SSL과 TLS) 
2.APNS 인증
3.APNS 인증서의 유효성 검사 
4. 푸쉬 메시지 전송 ( 전송할 단말의 디바이스 토큰 값과 함께)
5. Provider의 인증토큰 값을 검사 (token Trust)
        6. Requst의 대한 응답

=> 기본적으로 http 방식으로 APNS와 연결하고자 할 때, JWT로 구성된 token 값이 포함되어야 한다. ( Device의 고유한 token과는 별개의 개념)
=> TCP 방식으로 구현하고자 할 때 token 값 대신 인증서가 있어야 한다.





Provider to Device Push Notification



메시지 공급자에서 iOS 단말 디바이스로 메시지를 보내는 플로우는 다음과 같다.



1. iOS 디바이스 토큰 값이 Apns 서버에 먼저 등록되 있어야 한다.


2. 메시지 공급자는 디바이스 단말의 토큰과 보내고자 하는 메시지를 포함하여 APNs 서버에 요청한다. 


3.  위의 방식은  http 방식의 요청이기 때문에 token 값을 포함하여 인증해야 한다.


4. apns 서버는 인증 토큰을 토큰 키와 함께 복호화 하여 payload에 담겨진 notification message를 디바이스에 push한다.












-> 정리하면 Provider에서 APNS에 Connection 하기 위해 HTTP / TCP 방식으로 구현 가능하며, 각 각 인증 토큰 값과 인증서 파일이 필요하다.

-> 디바이스 단말 token 값은 Apns 서버에 먼저 등록되어야 하며, 등록된 고유 디바이스 단말 token으로 인증 token과 함께 notification을 push한다.






개요 



Apple Push Notification Service


- apns는 보안 연결을 통해 third-party server에서 앱이 설치된 사용자 디바이스로 푸쉬 알림을 보낼 수 있는 클라우드 서비스이다.




iOS 단말에 모바일 알림 서비스를 이용하려면 애플에서 자체적으로 구축된 푸쉬서버를 이용해야한다.







특징


Apnsend-to-end에서 시행한다.


- Provider는 메시지를 전달하는 공급자이며, 주로 3rd Party Server에서 수행된다. 


Apns에 연결하기 위해서는 인증 토큰 혹은 인증서를 사용해야 한다. 

  (인증토큰과 인증서는 https://developer.apple.com/account/ 에서 확인가능)



Apnstwo levels of trust와 함께 암호 검증 및 인증을 한다.(connection Trust token trust)
: Connection Trust는 연결 level에서 수행되는 암호 검증 단계
: token Trust는 단말기의 고유 deviceToken 인증 단계

- QoS를 지원한다.

 

Quality of Service(QoS)  구성요소 :  Stored-and-Forward, Coalesced Notification




  • QoS: 다른 응용 프로그램, 사용자, 데이터 흐름 등에 우선 순위를 정하여, 데이터 전송에 특정 수준의 성능을 보장하기 위한 능력

  • Stored-and-Forward: Apns가 알림을 전달하려고 할 때 장치가 오프라인이라면 일정 기간 동안 저장하고 장치가 다시 사용될 때 전달한다. 장치가 오프라인이라면 최신 알림만 보내고 이전 알림은 삭제하며, 오랫동안 오프라인이라면 모든 알림을 삭제된다.
  • Coalesced Notification :유사한 알림을 통합 할 수 있도록 알림 요청 내에 축소 식별자를 포함 할 수 있다.

  • 예를 들어 동일한 헤드 라인을 두 번 보내는 뉴스 서비스는 두 요청에 동일한 축소 식별자 값을 사용하여 통합된 알림으로 전송할 수 있다.







참고사이트: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html














TCP Packet(Segment) 




1)  Sourse/Destination Port Number ( 16 비트)             


 - 데이터를 생성한 애플리케이션에서 사용하는 포트번호

 - 목적지 애플리케이션에서 사용하는 포트번호



2) Sequence number (32 비트)             


 - 전송되는 데이터의 가상 회선을 통해 데이터의 모든 바이트에는 고유한 일련번호가 부여된다.

 - 네트워크가 불안하여 패킷을 분실, 지연 등으로 세그먼트가 순서가 어긋나게 도착 할 수 있기 때문에 sequence number를 이용하여 데이터를 올바른 순서로 재배열 할 수 있다.



3) acknowledgement number (32 비트)             


- 수신하기를 기다리는 다음 바이트 번호 

- 마지막 수신성공 번호 +1 .. 순서로 할당



4) HLEN = Header Length (4 비트)             


헤드의 길이를 32비트 단위로 나타낸다. 최소 필드 값은 5 (5 * 32 = 160bit or 20Byte )  최대 값 15 (15 * 32 = 480bit or 60byte)


5) 예약 (6 비트)             


- 추후 사용을 위한 예약된 필드


6)  Flag Bits 


- 6개의 플래그 비트

TCP 세그먼트 전달과 관련되어 TCP 회선 및 데이터 관리 제어 기능을 하는 플래그


CWR :(Congestion Window Reduced) – 혼잡 윈도우 크기 감소

- ECN :(Explicit Congestion Notification) – 혼잡을 알림

- URG(Urgent) : Urgent Pointer 필드가 가리키는 세그먼트 번호까지 긴급 데이터를 포함되어 있다는 것을 뜻한다.

  이 플래그가 설정되지 않았다면 Uregent Pointer 필드는 무시되어야 한다.

- ACK(Acknowledgment) : 확인 응답 메시지

- PSH(Push) : 데이터를 포함한다는 것을 뜻한다.

- RST(Reset) : 수신 거부를 하고자 할때 사용

- SYN(Synchronize) : 가상 회선이 처음 개설될 때 두 시스템의 TCP 소프트웨어는 의미 있는 확인 메시지를 전송하기 위해  일련 번호를서로 동기화해야 한다.

- FIN(Finish) : 작업이 끝나고 가상 회선을 종결하고자 할 때 사용



7)  Windows Size(16 비트)


- 흐름제어를 위해 사용하는 필드



8)  CheckSum (16 비트)


TCP 세그먼트의 내용이 유효한지 검증하고 손상 여부를 검사 할 수 있다.





9)  Urgent Pointer (16 비트)


- TCP 세그먼트에 포함된 긴급 데이터에 대한 마지막 바이트에 대한 일련번호

 현재 일련번호(sequence number)로부터 긴급 데이터까지의  바이트 오프셋(offset)
        . 해당 세그먼트의 일련번호에 urgent point 값을 더해 긴급 데이터의 끝을 알수있음







참고자료: http://www.ktword.co.kr/abbr_view.php?m_temp1=1889

Java ByteBuffer



자바 NIO ByteBuffer는 바이트 데이터를 저장하고 읽는 저장소




내부 배열을 3가지 속성으로 관리한다.

(1)Capacity : 버퍼에 저장하는 최대 크기

(2)Position : 읽기 또는 쓰기가 작업 중인 위치

(3)Limit : 읽고 쓰는 버퍼의 최대 공간


 

생성에는 3가지 특징 : allocate , allocateDirect, wrap

(1)Allocate : jvm heap 영역에 생성.

(2)AllocateDirect : 운영체제의 커널 영역에 생성. ByteBuffer로만 생성할 수 있음.

(3)Wrap : 입력된 바이트 배열을 사용하여 바이트 버퍼를 생성.







Netty ByteBuf



Pure Java에서 Buffer와 그 하위 클래스들을 제공함에도 불구하고 네티에서는 자체적인 바이트 버퍼 API를 사용

 

특징

• 별도의 읽기 인덱스와 쓰기 인덱스

• flip 메서드 없이 읽기 쓰기 가능

• 가변 바이트 버퍼

• 프레임워크 레벨의 바이트 버퍼 풀 제공

• Java ByteBuffer와 호환됨

• 자료형에 따른 클래스를 제공하지 않고 별도의 method로 제공





Byte Buffer Pool


네티의 바이트버퍼의 특징은 바이트버퍼풀을 제공한다는 것이다. 이를 통해 생성된 바이트 버퍼를 재사용 할 수 있다.


•  다음은 풀링을 할 수 있는 버퍼를 생성하는 방법이다.


        PooledByteBufAllocator.DEFAULT.heapBuffer() 

         PooledByteBufAllocator.DEFAULT.directBuffer()


 

• 풀링을 하지 않는 버퍼 생성 방법.

        

        Unpooled.buffer(); 

         Unpooled.directBuffer();

 



바이트 버퍼 풀을 통해 매번 ByteBuf 메모리의 할당과 해제를 하지 않아도 됨.


 






















ChannelFutureListener 


.....


ChannelFutureListener future = ctx.write(msg);


//write가 완료됐을 때 이벤트를 받을 수 있는 리스너 추가

future.addListener(listener);


//io작업이 완료됐는지

future.isDone();


//io작업 대기

future.sync();






네티가 제공하는 기본 ChannelFutureListener 


- ChannelFutureListener.CLOSE;

- ChannelFutureListener.CLOSE_ON_FAILURE;

- ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE;



.....


ChannelFutureListener future = ctx.write(msg);



future.addlistener(ChannerFutureListener.CLOSE);







참고자료: https://www.slideshare.net/krisjeong/going-asynchronous-with-netty-soscon-2015








+ Recent posts