Page 1


네티 인 액션 Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발


|

목차

01

네티 개념과 아키텍처

01

네티: 비동기식 이벤트 기반 네트워킹 프레임워크

1.1 자바의 네트워킹

6

1.1.1 자바 NIO

7

1.1.2 셀렉터

8

1.2 네티 소개

9

1.2.1 네티는 누가 사용할까?

10

1.2.2 비동기식 이벤트 기반 네트워킹

10

1.3 네티의 핵심 컴포넌트

11

1.3.1 Channel

12

1.3.2 콜백

12

1.3.3 Future

13

1.3.4 이벤트와 핸들러

14

1.3.5 내용 정리

16 17

1.4 요약

02

첫 번째 네티 애플리케이션

2.1 개발 환경 설정

IV

5

18 19

2.1.1 JDK 내려받기와 설치

19

2.1.2 IDE 내려받기와 설치

19

2.1.3 아파치 메이븐 내려받기와 설치

20

2.1.4 툴셋 구성

21

2.2 네티 클라이언트/서버 개요

21

2.3 Echo 서버 만들기

22

2.3.1 ChannelHandler와 비즈니스 논리

22

2.3.2 서버 부트스트랩

24


목차

2.4 Echo 클라이언트 만들기

27

2.4.1 ChannelHandler를 이용한 클라이언트 논리 구현

27

2.4.2 클라이언트 부트스트랩

29

2.5 Echo 서버와 클라이언트의 빌드와 실행

31

2.5.1 빌드

31

2.5.2 Echo 서버와 클라이언트 실행

34 38

2.6 요약

03

|

네티 컴포넌트와 설계

3.1 Channel, EventLoop, ChannelFuture

39 40

3.1.1 Channel 인터페이스

40

3.1.2 EventLoop 인터페이스

41

3.1.3 ChannelFuture 인터페이스

41

3.2 ChannelHandler와 ChannelPipeline

42

3.2.1 ChannelHandler 인터페이스

42

3.2.2 ChannelPipeline 인터페이스

42

3.2.3 ChannelHandler에 대한 고찰

44

3.2.4 인코더와 디코더

45

3.2.5 추상 클래스 SimpleChannelInboundHandler

46

3.3 부트스트랩

46

3.4 요약

48

V


|

목차

04

전송

4.1 사례 연구: 전송 마이그레이션

50

4.1.1 네티 없이 OIO와 NIO 이용

50

4.1.2 네티와 함께 OIO와 NIO 이용

53

4.1.3 논블로킹 네티 버전

54

4.2 전송 API

56

4.3 포함된 전송

59

4.3.1 NIO: 논블로킹 입출력

59

4.3.2 Epoll: 리눅스용 네이티브 논블로킹 전송

61

4.3.3 OIO: 기존 블로킹 입출력

62

4.3.4 JVM 내부 통신용 로컬 전송

63

4.3.5 임베디드 전송

63

4.4 전송 사용 사례

63

4.5 요약

65

05

ByteBuf

66

5.1 ByteBuf API

67

5.2 ByteBuf 클래스: 네티의 데이터 컨테이너

67

5.2.1 작동 방식

67

5.2.2 ByteBuf 사용 패턴

68

5.3 바이트 수준 작업

VI

49

72

5.3.1 임의 접근 인덱싱

72

5.3.2 순차 접근 인덱싱

73

5.3.3 폐기할 수 있는 바이트

73

5.3.4 읽을 수 있는 바이트

74


목차

5.3.5 기록할 수 있는 바이트

74

5.3.6 인덱스 관리

75

5.3.7 검색 작업

76

5.3.8 파생 버퍼

77

5.3.9 읽기/쓰기 작업

78

5.3.10 추가 작업

81

5.4 ByteBufHolder 인터페이스

82

5.5 ByteBuf 할당

82

5.5.1 주문식 할당을 위한 ByteBufAllocator 인터페이스

82

5.5.2 풀링되지 않는 버퍼

84

5.5.3 ByteBufUtil 클래스

84

5.6 참조 카운팅

85

5.7 요약

86

06

ChannelHandler와 ChannelPipeline

6.1 ChannelHandler 계층

87 88

6.1.1 Channel 수명주기

88

6.1.2 ChannelHandler 수명주기

88

6.1.3 ChannelInboundHandler 인터페이스

89

6.1.4 ChannelOutboundHandler 인터페이스

91

6.1.5 ChannelHandler 어댑터

92

6.1.6 리소스 관리

92

6.2 ChannelPipeline 인터페이스

|

95

6.2.1 ChannelPipeline 수정

96

6.2.2 이벤트 생성

98

VII


|

목차

6.3 ChannelHandlerContext 인터페이스 6.3.1 ChannelHandlerContext 이용

101

6.3.2 ChannelHandler와 ChannelHandlerContext의 고급 활용

104

6.4 예외 처리

106

6.4.1 인바운드 예외 처리

106

6.4.2 아웃바운드 예외 처리

107 109

6.5 요약

07

EventLoop와 스레딩 모델

110

7.1 스레딩 모델의 개요

111

7.2 EventLoop 인터페이스

112

7.2.1 네티 4의 입출력과 이벤트 처리

114

7.2.2 네티 3의 입출력 작업

115

7.3 작업 스케줄링

115

7.3.1 JDK 스케줄링 API

116

7.3.2 EventLoop를 이용한 작업 스케줄링

117

7.4 구현 세부 사항

118

7.4.1 스레드 관리

118

7.4.2 EventLoop와 스레드 할당

119

7.5 요약

VIII

100

121


목차

08

부트스트랩

122

8.1 부트스트랩 클래스

123

8.2 비연결 프로토콜과 클라이언트 부트스트랩

124

8.2.1 클라이언트 부트스트랩

125

8.2.2 Channel과 EventLoopGroup 호환성

127

8.3 서버 부트스트랩

128

8.3.1 ServerBootstrap 클래스

129

8.3.2 서버 부트스트랩

130

8.4 채널에서 클라이언트 부트스트랩

131

8.5 부트스트랩 중 여러 ChannelHandler 추가

133

8.6 네티 ChannelOption과 특성 이용

135

8.7 DatagramChannel 부트스트랩

136

8.8 종료

137

8.9 요약

138

09

단위 테스트

9.1 EmbeddedChannel 개요

|

139 140

9.2 EmbeddedChannel을 이용한 ChannelHandler 테스트

142

9.2.1 인바운드 메시지 테스트

142

9.2.2 아웃바운드 메시지 테스트

145

9.3 예외 처리 테스트

147

9.4 요약

149

IX


|

목차

02

코덱

10

코덱 프레임워크

10.1 코덱이란?

152

10.2 디코더

153

10.2.1 ByteToMessageDecoder 추상 클래스

153

10.2.2 ReplayingDecoder 추상 클래스

155

10.2.3 MessageToMessageDecoder 추상 클래스

157

10.2.4 TooLongFrameException 클래스

158

10.3 인코더

159

10.2.3 MessageToByteEncoder 추상 클래스

159

10.2.3 MessageToMessageEncoder 추상 클래스

161

10.4 추상 코덱 클래스

X

152

162

10.4.1 ByteToMessageCodec 추상 클래스

162

10.2.3 MessageToMessageCodec 추상 클래스

163

10.4.3 CombinedChannelDuplexHandler 클래스

166

10.5 요약

168

11

169

네티에서 제공하는 ChannelHandler와 코덱

11.1 SSL/TLS를 이용한 네티 애플리케이션 보안

170

11.2 네티 HTTP/HTTPS 애플리케이션 개발

172

11.2.1 HTTP 디코더, 인코더, 코덱

172

11.2.2 HTTP 메시지 집합체

174

11.2.3 HTTP 압축

175

11.2.4 HTTPS 이용

177

11.2.5 웹소켓

177


목차

11.3 유휴 연결과 시간 만료

180

11.4 구분 기호 및 길이 기반 프로토콜의 디코딩

182

11.4.1 구분 기호 프로토콜

182

11.4.2 길이 기반 프로토콜

186

11.5 대용량 데이터 기록

188

11.6 데이터 직렬화

190

11.6.1 JDK 직렬화

191

11.6.2 JBoss 마셜링을 이용한 직렬화

191

11.6.3 프로토콜 버퍼를 통한 직렬화

193 194

11.7 요약

03

네트워크 프로토콜

12

웹소켓

198

12.1 웹소켓 소개

199

12.2 예제 웹소켓 애플리케이션

199

12.3 웹소켓 지원 추가

200

12.3.1 HTTP 요청 처리

201

12.3.2 웹소켓 프레임 처리

204

12.3.3 ChannelPipeline 초기화

206

12.3.4 부트스트랩

208

12.4 애플리케이션 테스트 12.4.1 암호화

12.5 요약

|

210 211 214

XI


|

목차

13

04

사례 연구

UDP를 이용한 이벤트 브로드캐스팅

13.1 UDP 기본 사항

216

13.2 UDP 브로드캐스트

216

13.3 UDP 예제 애플리케이션

217

13.4 메시지 POJO: LogEvent

218

13.5 브로드캐스터 작성

219

13.6 모니터 작성

225

13.7 LogEventBroadcaster와 LogEventMonitor 실행

228

13.8 요약

230

14

사례 연구 1부

14.1 드로플러: 모바일 서비스 구축

234 235

14.1.1 초기 상황

235

14.1.2 드로플러의 작동 방식

236

14.1.3 빠른 업로드 환경 구축

236

14.1.4 기술 스택

239

14.1.5 성능

243

14.1.6 요약: 든든한 도우미

244

14.2 파이어베이스: 실시간 데이터 동기화 서비스

XII

215

245

14.2.1 파이어베이스 아키텍처

245

14.2.2 롱 폴링

246

14.2.3 HTTP 1.1 keep-alive와 파이프라인

249

14.2.4 SslHandler 제어

251

14.2.5 파이어베이스 요약

253


목차

14.3 어번 에어십: 모바일 서비스 구축

253

14.3.1 모바일 메시징의 기본

254

14.3.2 타사 푸시 전달

255

14.3.3 이진 프로토콜

255

14.3.4 직접 장치 전송

258

14.3.5 네티의 탁월한 동시 연결 지원 능력

259

14.3.6 요약: 방화벽의 경계를 넘어

260 261

14.4 요약

15

사례 연구 2부

15.1 페이스북에서의 네티: 니프티와 스위프트

262 263

15.1.1 스리프트란?

263

15.1.2 네티를 이용한 자바 스리프트의 상태 개선

264

15.1.3 니프티 서버 설계

265

15.1.4 니프티 비동기 클라이언트 설계

268

15.1.5 스위프트: 자바 스리프트 서비스를 구축하는 더 빠른 방법

269

15.1.6 결과

270

15.1.7 페이스북 요약

273

15.2 트위터에서의 네티: 피네이글

273

15.2.1 트위터의 성장통

273

15.2.2 피네이글의 탄생

274

15.2.3 피네이글의 작동 방식

275

15.2.4 피네이글의 추상화

280

15.2.5 오류 관리

282

15.2.6 서비스 구성

283

15.2.7 미래: 네티

284

15.2.8 트위터 요약

284

15.3 요약

|

285

XIII


|

목차

부록

메이븐 소개

A.1 메이븐이란?

286

A.1.1 메이븐 설치와 구성

287

A.1.2 메이븐의 기본 개념

288

A.2 POM 예제

XIV

286

299

A.2.1 프로젝트 POM

300

A.2.2 POM 상속과 집계

301

A.3 메이븐 명령줄

305

A.4 요약

307


추천사

웹 애플리케이션 서버를 이용하면 HTTP나 RPC 서버를 제작하는 방법을 배울 필요가 없는 날이 온다고 생각하던 때가 있었다. 아쉽게도 이런 희망은 오래 지속되지 않았다. 우리가 다뤄야 하는 기능 변경의 양 과 속도가 날로 증가하면서 기존의 3계층 아키텍처로 감당할 수 없는 수준이 됨에 따라 이제는 애플리케 이션을 여러 조각으로 나눠서 다수의 시스템으로 구성된 대규모 클러스터로 분산해야 하는 상황에 이르 렀다. 이러한 대규모 분산 시스템을 운영하려면 운영 비용과 대기 시간이라는 두 가지 흥미로운 문제를 고려 해야 한다. 단일 노드의 성능을 30% 또는 100% 이상으로 개선하면 얼마나 많은 시스템을 줄일 수 있을 까? 단일 웹 브라우저에서 다수의 시스템을 통해 십여 개의 내부 원격 프로시저 호출을 트리거할 때 최 적의 지연 시간을 달성하려면 어떻게 해야 할까? 네티의 핵심 기여자 중 한 명인 노먼 마우러는 네티 프로젝트에 대한 최초의 책 『네티 인 액션』에서 네티 로 고성능 저대기 시간 네트워크 애플리케이션을 구축하는 방법으로 이러한 문제의 해결책을 제시한다. 이 책을 마칠 때쯤에는 간소한 HTTP 서버부터 고도의 세부 설정이 가능한 RPC 서버까지 거의 모든 네 트워크 애플리케이션을 구축할 수 있게 될 것이다. 무엇보다 『네티 인 액션』의 놀라운 점은 이 책이 네티의 구석구석을 모두 아는 핵심 기여자가 집필했다는 것만이 아니라 트위터, 페이스북, 파이어베이스 등 네티를 실무 시스템에 활용하고 있는 여러 기업의 실 제 사례 연구를 담고 있다는 점이다. 이러한 사례 연구를 통해 이들 기업에서 네티 기반 애플리케이션의 역량을 최대한으로 끌어올리는 데 사용한 방법을 이해할 수 있게 될 것이다.

2 0 0 1 년 필자의 학부 시절 개인 프로젝트로 세상에 선보인 네티 ( http :// t . motd . kr / ko / archives/1930 )는 프로젝트(http://netty.io/community.html )에 참여하는 노먼과 같은 열정적인 기여자들의 값진 노력에 힘입어 지금 이 순간에도 활발한 생명력을 보여주고 있다. 이 책이 “네트워크 프 로그래밍의 미래를 향한” 길에 동참하도록 많은 독자에게 동기를 부여하고 프로젝트의 새로운 측면에 활 기를 불어넣기를 기대한다. 이희승 네티 창시자


서문

뒤돌아보면 내가 해냈다는 게 믿어지지 않는다.

2011년 네티 프로젝트에 처음 참여했을 때는 언젠가 내가 네티에 대한 책을 집필하고 네티 프레임워크 의 핵심 개발자 중 한 명이 되리라고는 꿈에도 생각하지 못했다. 네티와의 인연은 2009년 아파치 소프트웨어 재단에서 개발된 자바 기반 메일 서버인 아파치 제임스 Apache James

프로젝트에 관여하면서 시작됐다.

여러 다른 애플리케이션과 마찬가지로 아파치 제임스에도 견고한 네트워킹 추상화가 필수적이었다. 당 시 네트워킹 추상화를 제공하는 프로젝트를 조사하면서 네티를 접하게 됐고, 그 즉시 네티에 빠져들었 다. 이렇게 사용자 관점에서 네티와 친숙해지면서 더 개선할 수 있는 방향에도 관심이 생겼고 이를 계기 로 커뮤니티에도 적극 참여하게 됐다. 내 첫 번째 기여는 아주 사소한 것이었지만, 프로젝트에 기여한다는 것과 커뮤니티에서 이와 관련된 토 론을 하는 것이 얼마나 큰 도움이 되는지 금방 깨달을 수 있었다. 특히 프로젝트 창시자인 이희승 씨와 의 인연은 내 개인적 성장에 큰 도움이 됐다. 이 경험에 큰 매력을 느끼고 자유 시간에 커뮤니티에 더 적 극적으로 참여하기 시작했다. 그러면서 메일링 리스트를 관리하는 일을 돕게 됐고 IRC 채널의 토론에도 참여했다. 처음에는 단순한 취미로 시작했지만 점차 열정을 갖고 하는 일이 됐다. 네티에 대한 열정은 레드햇에 입사하는 밑바탕이 됐다. 내가 사랑하는 프로젝트에 참여하면서 월급을 받을 수 있다는 것은 꿈이 이뤄진 것과 같았다. 그러던 중 당시 아파치 캐멀Apache Camel을 개발하던(물 론 지금도 개발 중이다) 클라우스 입센Claus Ibsen과 알게 됐다. 당시 네티에는 탄탄한 사용자 기반과 좋은

JavaDoc이 있었지만, 개요 수준의 설명서는 없는 상황이었다. 클라우스는 『Camel in Action』의 저자 였는데, 네티에 대한 비슷한 책을 써보라는 이야기를 해줬다. 몇 주 동안 고민하다가 마침내 해보기로 결 심을 굳혔다. 『네티 인 액션』은 이렇게 시작됐다.


서문

『네티 인 액션』을 집필하는 동안 커뮤니티에 더 적극적으로 참여하게 됐고, 1,000회 이상의 커밋을 기록 해 이희승 씨에 이어 두 번째로 활동이 많은 기여자가 됐다. 덕분에 전 세계 여러 곳에서 열린 컨퍼런스 와 기술 모임에서 네티에 대한 강연을 했고, 결국에는 애플에 입사해 현재는 클라우드 인프라 엔지니어 링 팀의 선임 소프트웨어 엔지니어로 일하고 있다. 필자는 지금도 계속 네티에 참여하고 정기적으로 커 뮤니티에 기여하고 있으며 프로젝트의 방향을 결정하는 역할도 하고 있다. 노먼 마우러Norman Maurer 애플, 클라우드 인프라 엔지니어링

내가 매사추세츠 웰즐리에 위치한 하버드 필그림 보건 센터의 델 서비스 컨설턴트로 일하면서 중점을 두 는 현안은 재사용 가능한 인프라 컴포넌트를 만드는 것이다. 즉, 전반적인 소프트웨어 프로세스에 도움 이 되는 것은 물론, 귀찮고 지루할 수 있는 코드 관리 역할을 맡는 애플리케이션 개발자의 부담을 덜어주 도록 공통 코드 기반을 확장하는 것이 우리의 목표다. 그러던 중 서로 관련된 두 프로젝트에서 다이렉트 TCP/IP 통신만 지원하는 타사의 청구 처리 시스템을 이용하고 있다는 것을 알게 됐다. 둘 중 한 프로젝트에서는 공급업체 전용 구분기호 포맷에 기반을 두며 문서화도 거의 되지 않은 레거시 코볼 모듈을 자바로 다시 구현해야 했다. 결국 이 모듈은 동일한 청구 시스템과 상호작용하는, 최신 XML 기반 인터페이스로 대체됐다(그럼에도 여전히 SOAP가 아닌 순수 소켓이 사용됐다!). 이 두 프로젝트는 공용 API를 개발해 적용할 수 있는 절호의 기회로 보였다. 프로젝트가 충족해야 할 처 리량과 가용성 요건이 엄격하게 설정돼 있었고 설계는 꾸준히 개선되는 과정이었다. 빠른 반복 주기를 지원하려면 기반 네트워크 코드를 비즈니스 논리로부터 완전히 분리하는 것이 무엇보다 중요했다.


서문

자바용 고성능 네트워킹 프레임워크를 조사하면서 곧바로 네티를 접하게 됐다(1장의 시작 부분에 나오 는 가상 프로젝트는 거의 내 실제 경험을 바탕으로 한 것이다). 동적으로 구성 가능한 인코더와 디코더 를 이용하는 네티의 접근법이 우리 프로젝트에 최적이라고 판단했다. 두 프로젝트 모두 동일한 API를 이 용했고, 특정 데이터 포맷에 필요한 핸들러가 함께 배포됐다. 공급업체의 제품 또한 네티에 기반을 두고 있다는 것을 발견하고는 네티가 정답이라는 확신이 더 굳어졌다! 그즈음에 『네티 인 액션』이 집필 중이라는 사실을 알게 됐고, 원고 초안을 읽고 노먼에게 몇 가지 질문과 제안을 함께 전달했다. 노먼과 이야기를 주고받으며 최종 사용자의 관점을 염두에 둬야 한다는 데 의견 을 함께했고, 나 역시 네티 프로젝트에 참여하고 있었기에 기꺼이 내가 할 수 있는 역할을 맡았다. 이 접근 방식이 개발자의 요구를 잘 충족할 수 있기를 기대하며, 이 책을 좀 더 유용하게 이용할 수 있는 방안이 있다면 언제든지 포럼(https://forums.manning.com/forums/netty-in-action )에서 밝혀 주기를 바란다. 마빈 알렌 울프탈Marvin Allen Wolfthal 델 서비스

XVIII


책 소개

네티는 고성능 네트워크 애플리케이션을 신속하게 개발하기 위한 자바 프레임워크로서, 네트워크 프로 그래밍의 복잡한 측면을 캡슐화함으로써 다양한 개발자들이 네트워킹과 웹 분야의 최신 기술을 부담 없 이 활용할 수 있게 돕는다. 네티는 단순한 인터페이스와 클래스의 모음이 아니라 아키텍처 모델과 풍부한 디자인 패턴의 집합을 정 의한다. 그러나 최근까지도 자세하고 체계적인 사용자 가이드가 부족해 초보자가 처음 시작하기 어렵다 는 점이 문제로 지적됐다. 『네티 인 액션』이 해결하려는 문제가 바로 이것이었다. 이 책은 프레임워크 컴 포넌트와 API를 자세하게 설명하는 데서 그치지 않고 네티를 활용해 효율적이고 재사용 가능하며 유지 관리가 용이한 코드를 작성하는 방법을 설명한다. 대상 독자 이 책은 제네릭이나 멀티스레딩과 같은 중급 수준의 자바 주제를 어느 정도 이해하는 독자에게 적합하 다. 고급 네트워킹 프로그래밍에 대한 경험은 필요 없지만 기본 자바 네트워킹 API를 이해하고 있으면 매끄럽게 진행하는 데 도움이 된다. 네티는 아파치 메이븐을 빌드 관리 툴로 이용한다. 메이븐에 익숙하지 않은 독자를 위해 이 책의 예제 코 드를 실행하는 데 필요한 정보를 부록으로 정리했다. 예제로 제공되는 메이븐 구성을 여러분의 네티 기 반 프로젝트의 출발점으로 이용할 수도 있다. 로드맵 『네티 인 액션』은 총 4부와 부록 하나로 구성돼 있다. 1부: 네티 개념과 아키텍처

1부에서는 네티 프레임워크에 대한 가이드로서 네티의 설계, 컴포넌트, 프로그래밍 인터페이스를 자세 히 소개한다.

XXI


책 소개

1장은 블로킹과 논블로킹 네트워크 API와 해당 JDK 인터페이스에 대해 간단하게 설명하면서 시작한다. 이어서 고확장성, 비동기식, 이벤트 기반 네트워킹 애플리케이션을 구축하기 위한 툴킷으로서 네티를 처 음 소개하고, 채널, 콜백, 퓨처, 이벤트, 핸들러를 비롯한 이 프레임워크의 여러 기본 구성요소를 살펴 본다.

2장에서는 이 책의 예제 코드를 작성하고 실행하기 위해 시스템을 구성하는 방법을 설명한다. 연결된 클 라이언트로부터 수신한 메시지를 반향 출력echo하는 간단한 서버 애플리케이션을 테스트하며, 애플리케 이션의 모든 컴포넌트를 런타임에 조립하고 구성하는 부트스트랩 단계를 안내한다.

3장에서는 네티의 기술 및 설계 측면을 논의한다. Channel, EventLoop, ChannelHandler, ChannelPipeline을 비롯한 이 프레임워크의 핵심 컴포넌트를 소개하고, 서버와 클라이언트 부트스트랩의 차이점을 설명하 면서 마무리한다.

4장에서는 네트워크 전송에 대해 논의하며, JDK API와 네티를 이용하는 블로킹과 논블로킹 전송의 차 이를 확인한다. 네티의 전송 API 기반의 인터페이스 계층을 자세히 살펴보고, 여기서 지원되는 전송 형 식을 알아본다.

5장에서는 이 프레임워크의 데이터 처리 API이자 네티의 바이트 컨테이너인 ByteBuf를 중점적으로 다룬 다. JDK의 ByteBuffer와 비교한 장점을 설명하고 ByteBuf가 메모리를 할당하고 접근하는 방법을 알아본 다. 또한 참조 카운팅을 통해 메모리 리소스를 관리하는 방법도 소개한다.

6장에서는 애플리케이션 처리 논리를 호출하고 네트워크 레이어를 통해 데이터와 이벤트를 전달하는 핵 심 컴포넌트인 ChannelHandler와 ChannelPipeline을 집중적으로 살펴본다. 또한 고급 사용 사례를 구현하 고 여러 ChannelPipeline 간에 ChannelHandler를 공유할 수 있게 해주는 ChannelHandlerContext의 역할을 설명한다. 6장은 인바운드와 아웃바운드 이벤트에 의해 트리거된 예외를 처리하는 방법을 설명하면서 마무리한다.

XXII


책 소개

7장에서는 스레딩 모델을 개괄적으로 소개한 다음 네티의 스레딩 모델을 자세히 살펴본다. 네티의 동시 성 API의 주 컴포넌트인 EventLoop 인터페이스를 살펴보고 스레드 및 Channel과 이 인터페이스 간의 관 계를 설명한다. 이 정보는 네티가 비동기 이벤트 기반 네트워킹을 구현하는 방법을 이해하는 데 필수적 이다. 또한 EventLoop를 이용해 작업 스케줄링을 하는 방법도 알아본다.

8장에서는 Bootstrap 클래스 계층부터 시작해 부트스트랩을 심도 있게 살펴본다. 기본 사용 사례를 다시 살펴본 다음, 서버 애플리케이션 내에서 클라이언트 연결 부트스트랩, 데이터그램 채널 부트스트랩, 그 리고 부트스트랩 단계 중 다중 채널 추가와 같은 특수한 사례도 함께 설명한다. 마지막으로 애플리케이 션을 정상적으로 종료하고 모든 리소스를 정돈된 방식으로 해제하는 방법을 알아본다.

9장에서는 네티가 EmbeddedChannel이라는 특수한 Channel 구현을 제공하는 ChannelHandler를 단위 테스트 하는 방법을 중점적으로 다룬다. 예제에서는 이 클래스와 JUnit을 함께 이용해 인바운드와 아웃바운드 핸들러 구현을 테스트하는 방법을 보여준다. 2부: 코덱 데이터 변환은 네트워크 프로그래밍에서 가장 일반적인 작업 중 하나다. 2부에서는 이 작업을 간소화하 기 위해 네티가 제공하는 풍부한 기능을 설명한다.

10장에서는 바이트 시퀀스를 한 포맷에서 다른 포맷으로 변환하는 디코더와 인코더를 설명하면서 시작 한다. 구조화되지 않은 바이트 스트림을 프로토콜 전용 레이아웃으로(또는 그반대로) 변환하는 예는 거 의 어디서나 볼 수 있다. 코덱은 인코더와 디코더를 결합해 양방향 변환을 처리하는 컴포넌트다. 네티의 코덱 프레임워크 클래스를 이용해 손쉽게 커스텀 디코더와 인코더를 제작하는 방법을 보여주는 몇 가지 예를 살펴볼 것이다.

11장에서는 네티가 다양한 사용 사례를 위해 제공하는 ChannelHandler와 코덱을 중점적으로 살펴본다. 이러한 클래스에는 SSL/TLS, HTTP/HTTPS, 웹소켓, SPDY 같은 프로토콜을 위한 즉시 이용 가능한

XXIII


책 소개

코덱과 확장을 통해 거의 모든 구분기호 분리delimited, 가변 길이 또는 고정 길이 프로토콜을 지원할 수 있 는 디코더가 있다. 11장은 직렬화를 위해 대량의 데이터를 기록하는 프레임워크 컴포넌트를 살펴보면서 마무리한다. 3부: 네트워크 프로토콜

3부에서는 이 책의 앞부분에서 간략하게 다룬 여러 네트워크 프로토콜을 집중적으로 살펴본다. 네티를 통해 복잡한 내부 사항에 신경 쓰지 않고 복잡한 API를 애플리케이션에 손쉽게 적용할 수 있는 방법을 구체적으로 다시 알아본다.

12장에서는 웹소켓 프로토콜을 이용해 웹 서버와 클라이언트 간 양방향 통신을 구현하는 방법을 설명한 다. 예제 애플리케이션으로 연결된 모든 사용자가 실시간으로 대화할 수 있는 채팅방 서버를 다룬다.

13장에서는 UDPUser Datagram Protocol 브로드캐스트 기능을 활용하는 서버와 클라이언트를 위한 네티의 무 연결 프로토콜 지원 기능을 살펴본다. 이전 예제와 비슷하게, 이 단원의 예제에는 해당 프로토콜의 전용 지원 클래스인 DatagramPacket과 NioDatagramChannel을 이용한다. 4부: 사례 연구

4부에서는 네티를 이용해 업무 수행에 필수적인 시스템을 구축한 유명 기업에서 제공한 5가지 사례 연 구를 살펴본다. 이러한 예제에서는 지금까지 이 책에서 살펴본 프레임워크 컴포넌트의 실무 활용 방법을 확인하는 것은 물론, 규모 가변성과 확장성이 우수한 애플리케이션을 구축하는 네티의 설계와 아키텍처 원칙을 확인할 수 있다.

14장에서는 드로플러Droplr, 파이어베이스Firebase, 어번 에어십Urban Airship의 사례 연구를 살펴본다. 15장에서는 페이스북Facebook과 트위터Twitter의 사례 연구를 살펴본다.

XXIV


책 소개

부록: 메이븐 소개 이 부록의 주 목표는 이 책의 예제 코드를 컴파일 및 실행하고 더 확장해 네티를 이용하는 프로젝트를 시 작할 수 있게 아파치 메이븐의 기본 사용법을 소개하는 것이다. 다음 주제를 다룬다. ■■ 메이븐의 주요 목표와 활용법 ■■ 메이븐 설치와 구성 ■■ 메이븐의 기본 개념: POM 파일, 아티팩트, 코디네이트, 의존성, 플러그인, 리포지토리 ■■ 예제 메이븐 구성, POM 상속, 집합체 ■■ 메이븐의 명령줄 구문

코드 관례와 예제 내려받기 이 책에서는 각 주제를 활용하는 방법을 보여주는 방대한 예제를 제공한다. 코드 예제나 본문에 포함된 소스코드는 일반 테스트와 구분할 수 있게 fixed-width

font like this와

같이 고정폭 글꼴로 표시된다.

또한 클래스와 메서드 이름, 객체 속성 및 다른 코드 관련 용어와 내용 역시 고정폭 글꼴로 표시된다. 경우에 따라 코드가 reference.dump ( )와 같이 이탤릭으로 표시될 때는 reference를 그대로 입력하지 말 고 필요한 내용으로 바꿔서 입력해야 한다. 이 책의 소스코드는 출판사 웹 사이트 ( http ://wikibook .co .kr /netty -in -action )와 깃허브 (https://github.com/wikibook/netty-in-action )에서 내려받을 수 있다. 예제는 최상위 POM과 모듈이 이 책의 각 단원과 일치하는 멀티 모듈 메이븐 프로젝트로 구성돼 있다.

XXV


01 네티: 비동기식 이벤트 기반 네트워킹 프레임워크

이 단원의 내용 ■■ 자바의 네트워킹 ■■ 네티 소개 ■■ 네티의 핵심 컴포넌트

규모가 크고 중요한 회사에서 업무 수행에 필수적인 애플리케이션을 개발하기 시작했다고 가정해보자. 첫 회의에서 이 새로운 시스템이 최대 15만 동시 사용자를 성능 저하 없이 지원할 수 있게 확장 가능해 야 한다는 것을 알게 됐다. 모든 시선이 여러분의 입을 향하고 있다. 이제 뭐라고 말해야 할까? 확신에 찬 목소리로 “당연하죠, 문제 없습니다!”라고 말할 수 있다면 여러분에게 경의를 표한다. 그러나 “가능하다고 생각합니다” 정도의 신중한 태도를 취하는 사람이 더 많을 것이다. 그러고는 컴퓨터 앞에 앉 자마자 “고성능 자바 네트워킹”을 검색할 것이다. 현재 시점에서 이렇게 검색하면 다음과 같은 결과를 얻는다.

4

네티 인 액션: Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발


Netty: Home netty.io/ 네티는 유지 관리가 용이한 고성능 프로토콜 서버와 클라이언트를 신속하게 개발하기 위한 비동기식 이벤트 기반 네트워크 애플리케이션 프레임워크다.

이와 같은 경로로 네티를 처음 접했다면, 여러분은 아마도 사이트에서 코드를 내려받고, Javadoc과 여 러 블로그를 찾아 꼼꼼히 읽어본 후, 작업을 시작하는 과정을 밟을 것이다. 네트워크 프로그래밍에 능숙 한 개발자라면 만족스러운 결과를 얻을 수 있겠지만, 그렇지 않다면 생각만큼 쉽지 않을 것이다. 그 이유는 앞서 예에서 소개한 고성능 시스템은 단순히 높은 수준의 코딩 기술 뿐만이 아니라 네트워킹, 멀티스레딩, 동시성을 비롯한 여러 복잡한 분야에 대한 전문 기술을 필요로 하기 때문이다. 네티는 이 도 메인 지식을 네트워킹 초심자도 이용할 수 있게 한곳으로 모았다. 그러나 최근까지도 네티에 대한 자세 한 가이드가 없는 탓에 네티를 배우는 과정이 생각 이상으로 어려웠다. 이것이 필자가 이 책을 집필한 이 유다. 이 책을 집필하면서 우리가 목표로 삼은 것은 최대한 다양한 개발자가 네티를 접할 수 있게 한다는 것이 었다. 여기에는 혁신적인 콘텐츠나 서비스를 제공할 역량은 있지만 네트워킹 전문가가 되기 위한 시간이 나 굳이 그럴 필요가 없는 개발자가 포함된다. 이런 조건에 해당하는 개발자라면 생각 외로 쉽고 빠르게 네티 애플리케이션을 만들 수 있다는 사실을 알게 될 것이다. 다른 한편으로는 직접 네트워크 프로토콜 을 제작하기 위한 툴을 찾는 수준 높은 전문가를 지원하는 데도 초점을 맞췄다. 네티는 아주 풍부한 네트워킹 툴킷을 제공하며, 이 책의 거의 대부분을 그 기능을 설명하는 데 할애할 것 이다. 그러나 네티는 궁긍적으로 하나의 프레임워크이며, 기술적 내용에 못지 않게 아키텍처상의 접근법 이나 설계 원칙도 중요한 요점이다. 따라서 다음 주제도 중요하게 다룬다. ■■ 관심사의 분리(비즈니스 논리와 네트워크 논리의 분리) ■■ 모듈성과 재사용성 ■■ 테스트 용이성을 우선 요건으로 취급

이번 장에서는 고성능 네트워킹에 대한 배경 정보로 시작하며, 특히 JDKJava Development Kit 기반의 구현에 대해 알아본다. 이 배경 정보를 바탕으로 네티를 소개하고 핵심 개념과 기본 구성요소를 알아본다. 이번 장을 끝내면 첫 번째 네티 기반 클라이언트와 서버를 시작할 준비가 끝난다.

1부. 네티 개념과 아키텍처

5


1.1 자바의 네트워킹 초창기 네트워킹 시절을 경험한 개발자라면 C 언어 소켓 라이브러리의 복잡한 내용과 운영체제별로 다 른 특이성을 다루고 해결하는 데 많은 시간을 보냈을 것이다. 자바 초기 시절(1995~2002 )부터 까다로 운 세부 사항을 감추기 위한 객체 지향 파사드가 적지 않게 선보였지만, 복잡한 클라이언트/서버 프로토 콜을 제작하려면 여전히 많은 양의 보일러플레이트 코드를 작성해야 했고, 모든 사항이 매끄럽게 작동하 게 하려면 틈틈이 내부를 들춰봐야 했다. 이러한 최초의 자바 API (java.net )는 네이티브 시스템 소켓 라이브리가 제공하는 이른바 블로킹 함수만 지원했다. 다음 예제는 이러한 호출을 이용한 서버 코드를 그대로 보여준다. 예제 1.1 블로킹 입출력 예제

ServerSocket serverSocket = new ServerSocket(portNumber); Socket clientSocket = serverSocket.accept();

새로운 ServerSocket이 지정된 포트에서 연결 요청을 수신한다. ➊ accept() 호출은 연결될 때까지 진행을 블로킹한다.

BufferedReader in = new BufferedReader( new InputStreamReader(clientSocket.getInputStream())); PrintWriter out =

➋ 이러한 소켓으로부터 스트림 객체를 파생한다.

new PrintWriter(clientSocket.getOutputStream(), true); String request, response; ➌ 처리 루프를 시작한다.

while ((request = in.readLine()) != null) { if ("Done".equals(request) (

클라이언트가 “Done”을 전송한 경우 처리 루프를 종료한다.

break; } response = processRequest(request); out.println(response); }

➍ 요청이 서버의 처리 메서드로 전달됐다. 서버의 응답이 클라이언트로 전달됐다.

처리 루프를 계속함

이 예제는 기본적인 Socket API 패턴의 구현을 보여준다. 여기서 중요한 요점은 다음과 같다. ■■ accept()는 ServerSocket에서 연결될 때까지 진행을 블로킹하며➊, 연결되면 클라이언트와 서버 간 통신을 위한 새로 운 Socket 하나를 반환한다. 이후 들어오는 연결을 수신하는 역할은 ServerSocket이 재개한다. ■■ Socket의 입력과 출력 스트림으로부터 BufferedReader와 PrintWriter가 하나씩 파생된다➋. 전자는 문자 입력 스트 림에서 텍스트를 읽으며, 후자는 객체의 포매팅된 표현을 텍스트 출력 스트림으로 출력한다. ■■ readLine()은 줄 바꿈이나 캐리지 리턴으로 끝나는 문자열을 읽을 때까지 진행을 블로킹한다➌. ■■ 클라이언트 요청이 처리된다➍.

6

네티 인 액션: Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발


이 코드는 한 번에 한 연결만 처리한다. 다수의 동시 클라이언트를 관리하려면 그림 1.1에 나오는 것처 럼 각 새로운 클라이언트 Socket마다 새로운 Thread를 할당해야 한다. Socket

Socket

Socket

읽기/쓰기

읽기/쓰기

읽기/쓰기

Thread

Thread

Thread

그림 1.1 블로킹 입출력을 이용하는 다중 연결

이 방식이 어떤 결과를 초래할지 생각해보자. 우선, 여러 스레드가 입력이나 출력 데이터가 들어오기를 기다리며 무한정 대기 상태로 유지될 수 있다. 이것은 리소스 낭비로 이어질 가능성이 높다. 둘째, 각 스 레드가 스택 메모리를 할당해야 하는데, 운영체제에 따라 다르지만 스택의 기본 크기는 64KB에서 1MB 까지 차지할 수 있다. 셋째, JVMJava virtual machine이 물리적으로 아주 많은 수의 스레드를 지원할 수 있지만, 동시 접속이 한계에 이르기 훨씬 전부터(예: 1만 개에 육박하는 수준) 컨텍스트 전환에 따른 오버헤드가 심각한 문제가 될 수 있다. 이러한 동시성 처리 방식도 클라이언트 수가 소~중규모라면 고려해볼 만하지만 10만 이상의 동시 연결 을 지원해야 할 때는 처음부터 배제하는 것이 바람직하다. 다행스럽게도 더 나은 방법이 있다.

1.1.1 자바 NIO 예제 1.1의 코드가 기반을 두는 블로킹 시스템 호출 방식 외에도 네이티브 소켓 라이브러리에는 오래 전 부터 네트워크 리소스 사용률을 세부적으로 제어할 수 있는 논블로킹non-blocking 호출이 포함돼 있었다. ■■ setsockopt()를 이용하면 데이터가 없을 때, 즉 블로킹 호출이라면 진행을 블로킹할 상황1에서 읽기/쓰기 호출이 즉시 반환하도록 소켓을 구성할 수 있다. ■■ 시스템의 이벤트 통지 API2를 이용해 논블로킹 소켓의 집합을 등록하면 읽거나 기록할 데이터가 준비됐는지 여부를 알 수 있다.

1 W. Richard Stevens, “4.3 BSD는 논블로킹 설명자 상의 작업이 블로킹 없이 완료할 수 없는 경우 EWOULDBLOCK을 반환한다.” Advanced Programming in the UNIX Environment (Addison-Wesley, 1992), 364쪽. 2 입출력 멀티플렉싱이라고도 하는 이 인터페이스는 여러 해에 걸쳐 원래의 select()와 poll() 호출에서 시작해 좀 더 성능 기준에 맞는 구현으로 발 전했다. 자세한 내용은 한상진 씨의 “규모 가변형 이벤트 멀티플렉싱: epoll과 kqueue 비교” 게시물(www.eecs.berkeley.edu/~sangjin/2012/12/21/ epoll-vs-kqueue.html)을 참고한다.

1부. 네티 개념과 아키텍처

7


논블로킹 입출력을 위한 자바 지원 기능은 2002년 JDK 1.4 패키지인 java.nio와 함께 도입됐다. New인가 논블로킹인가? NIO는 원래 New Input/Output의 약자였지만 자바 API가 워낙 오랫동안 사용된 탓에 더 이상 새롭다고 말하기 어렵게 됐 다. 대부분의 사용자는 NIO가 논블로킹 입출력을 나타내며, 블로킹 입출력은 OIO 또는 Old Input/Output이라고 생각한다. 일반 입출력plain I/O이라는 용어를 접하는 경우도 있을 것이다.

1.1.2 셀렉터 그림 1.2의 논블로킹 설계를 보면 이전 절에서 설명한 블로킹 방식의 단점이 사실상 완전히 해결된 것을 알 수 있다. Socket

Socket

Socket

읽기/쓰기

읽기/쓰기

읽기/쓰기

Selector

Thread 그림 1.2 Selector를 이용한 논블로킹 입출력

java.nio.channels.Selector

클래스는 자바의 논블로킹 입출력 구현의 핵심으로서, 논블로킹 Socket의 집

합에서 입출력이 가능한 항목을 지정하기 위해 이벤트 통지 API를 이용한다. 언제든지 읽기나 쓰기 작업 의 완료 상태를 확인할 수 있으므로 그림 1.2에 나오는 것처럼 한 스레드로 여러 동시 연결을 처리할 수 있다. 이 모델은 블로킹 입출력 모델에 비해 전체적으로 훨씬 개선된 리소스 관리 효율을 보여준다. ■■ 적은 수의 스레드로 더 많은 연결을 처리할 수 있으므로 메모리 관리와 컨텍스트 전환에 따르는 오버헤드가 감소한다. ■■ 입출력을 처리하지 않을 때는 스레드를 다른 작업에 활용할 수 있다.

직접 자바 NIO API를 이용해 제작하는 애플리케이션도 많지만 이 작업을 올바르고 안전하게 하기는 아 주 어렵다. 특히 부하가 높은 상황에서 입출력을 안정적이고 효율적으로 처리하고 호출하는 것과 같이 까다롭고 문제 발생 위험이 높은 일은 네티와 같은 고성능 네트워킹 전문가에게 맡기는 게 좋다.

8

네티 인 액션: Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발


1.2 네티 소개 앞에서 제시한 시나리오와 같이 수십만의 동시 사용자를 지원하는 일은 불과 얼마 전까지만 해도 불가능 한 일로 여겨졌다. 현재의 시스템 사용자는 이 정도 수준을 당연하게 여기고 있으며, 우리 개발자 역시 항상 한계를 극복하려고 노력하고 있다. 게다가 더 높은 수준의 처리량과 규모 가변성을 더 낮은 비용으 로 구현해야 한다는 것도 언제나 가장 먼저 손꼽히는 요건이다. 마지막으로 제시한 사항도 중요하다. 저수준 API를 직접 이용하면 복잡성이 심화되며, 구하기 어려운 특수한 고급 인력에 대한 의존성이 높아진다는 사실을 그간의 길고 고통스러운 경험을 통해 알고 있다. 따라서 기반 구현의 복잡성을 단순한 추상화로 감추는 객체 지향의 기본 개념을 도입해야 한다. 이 기본 원칙은 특히 분산 시스템 개발 분야에서 공통적인 프로그래밍 작업을 위한 솔루션을 캡슐화하는 다양한 프레임워크의 개발 동기가 됐다. 전문 자바 개발자라면 적어도 이러한 하나 이상의 프레임워크에 익숙하다고 가정해도 좋을 것이다3. 이러한 프레임워크는 기술 요건과 개발 일정을 충족하는 데 없어서 는 안 될 중요한 도구로 자리 잡았다. 네티는 네트워킹 도메인에서 가장 유명한 자바용 프레임워크다4. 네티는 사용하기 쉬운 API를 전면에 내세우고 자바의 고급 API로 내부를 무장함으로써 개발자에게 중요한 각 애플리케이션의 고유 영역에 집중하게 도와준다. 네티를 자세히 살펴보기 전에 표 1.1에 요약된 핵심 기능을 자세히 살펴보자. 일부는 기술적인 것이지만 아키텍처나 철학에 대한 항목도 있다. 각 항목에 대해서는 이 책을 진행하면서 다시 심도 있게 다룬다. 표 1.1 네티의 특징 요약 범주 설계

네티의 특징 단일 API로 블로킹과 논블로킹 방식의 여러 전송 유형을 지원. 단순하지만 강력한 스레딩 모델. 진정한 비연결 데이 터그램 소켓 지원. 재사용 지원을 위한 논리 컴포넌트 연결.

이용 편이성

자세한 Javadoc과 광범위한 예제. JDK 1.6+을 제외한 추가 의존성 없음. (일부 옵션 기능을 이용하려면 자바 1.7+ 및/또는 추가 의존성이 필요할 수 있음)

성능

코어 자바 API보다 높은 처리량과 짧은 지연 시간. 풀링과 재사용을 통한 리소스 소비 감소. 메모리 복사 최소화.

Spring 3 스프링 은 객체 생성, 일괄 처리, 데이터베이스 프로그래밍 등을 해결하는 애플리케이션 프레임워크의 전체 생태계로서 가장 잘 알려진 프레임워 크다. Duke’s Choice Award 를 수상했다. 4 네티는 2011년 듀크 초이스 어워드

1부. 네티 개념과 아키텍처

9


범주 견고성

네티의 특징 저속, 고속 또는 과부하 연결로 인한 OutOfMemoryError가 발생하지 않음. 고속 네트워크 상의 NIO 애플리케이션 에서 일반적인 읽기/쓰기 비율 불균형이 발생하지 않음.

보안

완벽한 SSL/TLS 및 StartTLS 지원. 애플릿이나 OSGi 같은 제한된 환경에서도 이용 가능.

커뮤니티 주도 개발

빨리 그리고 자주 릴리스됨.

1.2.1 네티는 누가 사용할까? 네티는 애플, 트위터, 페이스북, 구글, 스퀘어, 인스타그램과 같은 대기업은 물론, Infinispan ,

HornetQ, Vert.x, 아파치 카산드라, 엘라스틱서치 등의 유명한 오픈소스 프로젝트를 아우르는 활동적 이고 성장하고 있는 사용자 커뮤니티5를 보유하고 있다. 이들 기업과 프로젝트에서는 모두 네티의 강력 한 네트워크 추상화를 핵심 코드에 통합해 활용하고 있다. 신생 기업 중에는 파이어베이스와 어번 에어 십이 있다. 파이어베이스는 수명이 긴long-lived HTTP 연결을 위해, 어번 에어십은 모든 유형의 푸시 알림 을 위해 네티를 활용한다. 트위터는 모든 내부 시스템 간 통신에 네티 기반 프레임워크인 피네이글Finagle 6을 이용한다. 페이스북은 자체 아파치 스리프트Apache Thrift 서비스인 니프티Nifty에 네티를 사용한다. 두 기업은 모두 확장성과 성능 을 중요하게 판단하며, 네티에 정기적으로 기여하고 있다7. 네티가 이렇게 많은 프로젝트에 도입되면서 FTP, SMTP, HTTP, 웹소켓과 이진 및 텍스트 기반의 다양 한 프로토콜 구현을 통해 네티의 범위와 유연성이 향상되는 긍정적 결과를 얻었다.

1.2.2 비동기식 이벤트 기반 네트워킹 이 책에서은 비동기asynchronous라는 용어가 아주 많이 등장하므로 문맥을 명확히 해두는 편이 좋겠다. 비 동기, 즉 동기화되지 않은 이벤트는 사실 우리에게 아주 익숙하다. 좋은 예로 이메일이 있다. 보낸 메시 지의 답장이 올 수도 있지만 답장이 없는 경우도 있고, 메시지를 보내는 동안 예기치 않은 메시지를 받을 수도 있다. 또한 비동기 이벤트는 정돈된 관계를 가질 수 있다. 일반적으로 답변은 질문한 사항에 대해서 만 받을 수 있고, 답변을 기다리는 동안 다른 일을 할 수도 있다.

5 네티를 도입한 전체 기업 목록은 http://netty.io/wiki/adopters.html에서 볼 수 있다. 6 피네이글에 대한 내용은 https://twitter.github.io/finagle/을 참고한다. 7 14장과 15장의 사례 연구에서는 일부 기업에서 네티를 활용해 실무 문제를 해결하는 방법을 보여준다.

10

네티 인 액션: Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발


비동기성은 우리 일상 생활의 흔한 단면이므로 그동안 깊게 생각해보지 않았을 수 있다. 그런데 컴퓨터 프로그램을 완전히 비동기식으로 작동하게 만들려면 몇 가지 아주 특수한 문제를 고려해야 한다. 본질 적으로 비동기식과 이벤트 기반 특징을 모두 갖는 시스템은 특수하고도 동시에 극히 유용한 행동 유형을 보여주는데, 바로 발생하는 이벤트에 대해 언제든지, 그리고 순서에 관계없이 응답할 수 있다는 것이다. 이러한 특징은 “증가하는 작업량에 맞게 적절히 처리할 수 있는 시스템, 네트워크, 프로세스의 능력 또는 이러한 작업량 증가에 맞게 규모를 늘리는 능력”8이라고 정의되는 최고 수준의 확장성을 실현하는 데 필 수적이다. 비동기성과 확장성은 어떻게 연결돼 있을까? ■■ 논블로킹 네트워크 연결은 작업 완료를 기다릴 필요가 없게 해준다. 완전 비동기 입출력은 이 특징을 바탕으로 한 단계 더 나아간다. 비동기 메서드는 즉시 반환하며 작업이 완료되면 직접 또는 나중에 이를 통지한다. ■■ 셀렉터는 적은 수의 스레드로 여러 연결에서 이벤트를 모니터링할 수 있게 해준다.

종합하면, 논블로킹 입출력을 이용하면 블로킹 입출력 방식을 이용할 때보다 더 많은 이벤트를 훨씬 빠 르고 경제적으로 처리할 수 있다. 이것은 네트워킹 관점에서 우리가 구축하려는 시스템의 핵심이며, 앞 으로 알아보겠지만 네티 설계의 핵심이기도 하다. 다음 절에서는 네티의 핵심 컴포넌트를 처음 살펴본다. 일단은 이러한 컴포넌트를 구상 자바 클래스보다 는 도메인 객체라고 생각하자. 나중에는 이러한 컴포넌트가 협력해 네트워크에서 발생하는 이벤트에 대 한 알림을 제공하고 이벤트를 처리할 수 있게 해주는 방법을 알아본다.

1.3 네티의 핵심 컴포넌트 이 절에서는 네티의 주 구성요소를 소개한다. ■■ Channel ■■ 콜백 ■■ Future ■■ 이벤트와 핸들러

8 Andre B. Bondi, “Characteristics of scalability and their impact on performance” 제2회 소프트웨어 및 성능 국제 워크숍 공식기록—WOSP ‘00 (2000), 195쪽.

1부. 네티 개념과 아키텍처

11


이러한 구성요소는 리소스, 논리, 알림이라는 각기 다른 종류의 구조를 나타낸다. 여러분의 애플리케이 션은 이러한 구성요소를 이용해 네트워크와 네트워크를 통해 전달되는 데이터에 접근한다. 각 컴포넌트에 대해 기본 정의와 함께 필요한 경우 사용법을 설명하는 간단한 코드 예제를 살펴보자.

1.3.1 Channel Channel은 자바 NIO의 기본 구조이며, 다음과 같이 정의된다.

하나 이상의 입출력 작업(예: 읽기 또는 쓰기)을 수행할 수 있는 하드웨어 장치, 파일, 네트워크 소켓, 프로그램 컴포 넌트와 같은 엔티티에 대한 열린 연결9

일단 Channel을 들어오는(인바운드) 데이터와 나가는(아웃바운드) 데이터를 위한 운송수단이라고 생각 하자. Channel은 열거나 닫고, 연결하거나 연결을 끊을 수 있다.

1.3.2 콜백 콜백callback은 간단히 말해 다른 메서드로 자신에 대한 참조를 제공할 수 있는 메서드다. 다른 메서드에서 는 이 참조가 가리키는 메서드를 필요할 때 호출할 수 있다. 콜백은 광범위한 프로그래밍 상황에 이용되 며 관심 대상에게 작업 완료를 알리는 가장 일반적인 방법 중 하나다. 네티는 이벤트를 처리할 때 내부적으로 콜백을 이용한다. 콜백이 트리거되면 ChannelHandler 인터페이스 의 구현을 통해 이벤트를 처리할 수 있다. 다음 예제에서 새로운 연결이 이뤄지면 ChannelHandler 콜백인 channelActive ( )가 호출되며 여기에서 메시지를 출력한다.

예제 1.2 콜백에 의해 트리거되는 ChannelHandler

public class ConnectHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println(

새로운 연결이 이뤄지면 channelActive(ChannelHandlerContext)가 호출된다.

"Client " + ctx.channel().remoteAddress() + " connected"); } }

9 자바 플랫폼, 스탠다드 에디션 8 API 사양, java.nio.channels, Channel 인터페이스, http://docs.oracle.com/javase/8/docs/api/java/nio/channels/ package-summary.html

12

네티 인 액션: Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발


1.3.3 Future Future는

작업이 완료되면 이를 애플리케이션에 알리는 한 방법이다. 이 객체는 비동기 작업의 결과를

담는 자리표시자placeholder 역할을 하며, 미래의 어떤 시점에 작업이 완료되면 그 결과에 접근할 수 있게 해준다.

JDK는 java.util.concurrent.Future 인터페이스를 제공하지만, 제공되는 구현에는 수동으로 작업 완료 여부를 확인하거나 완료되기 전까지 블로킹하는 기능만 있다. 그래서 네티는 비동기 작업이 실행됐을 때 이용할 수 있는 자체 구현 ChannelFuture를 제공한다. ChannelFuture에는 ChannelFutureListener

인스턴스를 하나 이상 등록할 수 있는 추가 메서드가 있다. 작

업이 완료되면 리스너의 콜백 메서드인 operationComplete ( )가 호출되며, 이 시점에 리스너는 작업이 정 상적으로 완료됐는지, 아니면 오류가 발생했는지 확인할 수 있다. 오류가 발생한 경우 생성된 Throwable 을 가져올 수 있다. 즉, ChannelFutureListener가 제공하는 알림 메커니즘을 이용하면 작업 완료를 수동 으로 검사할 필요가 없다. 네티의 모든 아웃바운드 입출력 작업은 ChannelFuture를 반환하며 진행을 블로킹하는 작업은 없다. 앞서 언급한 대로 네티는 기본적으로 비동기식이며 이벤트 기반이다. 예제 1.3에는 입출력 작업의 일부로 반환되는 ChannelFuture의 예가 나온다. 여기서 connect ( )는 진행을 블로킹하지 않고 곧바로 반환하며, 백그라운드로 호출이 완료된다. 언제 호출이 완료될지는 여러 요인에 의해 좌우되지만, 일단 이 관심사는 코드 밖으로 추상화됐다. 스레드가 작업 완료를 대기하면서 블로킹 되지 않기 때문에 다른 작업을 할 수 있으며 결과적으로 리소스를 효율적으로 이용할 수 있다. 예제 1.3 비동기 연결

Channel channel = ...; // 블로킹하지 않음 원격 피어에 대한 비동기 연결

ChannelFuture future = channel.connect( new InetSocketAddress("192.168.0.1", 25));

예제 1 .4 에는 connect ( )

ChannelFutureListener 를

호출로 반환된

ChannelFuture를

활용하는 방법이 나온다. 먼저 원격 피어로 연결한 다음, 이용해 새로운

ChannelFutureListener를

등록한다. 리스너가

연결이 만들어졌다는 알림을 받으면 상태를 확인한다➊. 작업이 정상적이면 데이터를 Channel로 기록하 며, 그렇지 않으면 ChannelFuture에서 Throwable을 가져온다.

1부. 네티 개념과 아키텍처

13


예제 1.4 콜백을 이용하는 방법

Channel channel = ...; // 블로킹하지 않음 원격 피어로 비동기 연결을 만듦

ChannelFuture future = channel.connect( new InetSocketAddress("192.168.0.1", 25));

작업이 완료되면 알림을 받을 ChannelFutureListener를 등록

future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { if (future.isSuccess()){

작업의 상태를 검사

작업이 성공적인 경우 데이터를 저장할 ByteBuf를 생성

ByteBuf buffer = Unpooled.copiedBuffer( "Hello",Charset.defaultCharset()); ChannelFuture wf = future.channel() .writeAndFlush(buffer);

데이터를 비동기식으로 원격 피어로 전송하고 ChannelFuture를 반환

.... } else { Throwable cause = future.cause(); cause.printStackTrace();

오류가 발생한 경우 원인을 알 수 있는 Throwable에 접근

} } });

물론 오류 처리를 어떻게 할지는 전적으로 개발자의 선택과 상황, 그리고 특정한 오류에 의해 적용되는 제약에 따라 달라진다. 예를 들어 연결 실패의 경우 다시 연결을 시도하거나 다른 원격 피어로 연결을 만 들 수 있다. ChannelFutureListener는

더 정교한 버전의 콜백이라고 생각할 수도 있다. 실제로 콜백과 Future는 상호

보완적 메커니즘이며 둘의 조합을 통해 네티의 핵심 구성요소 중 하나를 형성한다.

1.3.4 이벤트와 핸들러 네티는 작업의 상태 변화를 알리기 위해 고유한 이벤트를 이용하며, 발생한 이벤트를 기준으로 적절한 동작을 트리거할 수 있다. 다음과 같은 동작이 포함된다. ■■ 로깅 ■■ 데이터 변환

14

네티 인 액션: Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발


■■ 흐름 제어 ■■ 애플리케이션 논리

네티는 네트워크 프레임워크이므로 이벤트 역시 인바운드 또는 아웃바운드 데이터 흐름에 대한 연관성 을 기준으로 분류된다. 인바운드 데이터나 연관된 상태 변화로 트리거되는 이벤트는 다음을 포함한다. ■■ 연결 활성화 또는 비활성화 ■■ 데이터 읽기 ■■ 사용자 이벤트 ■■ 오류 이벤트

아웃바운드 이벤트는 다음과 같이 미래에 한 동작을 트리거하는 작업의 결과다. ■■ 원격 피어로 연결 열기 또는 닫기 ■■ 소켓으로 데이터 쓰기 또는 플러시

아웃바운드

아웃바운드

아웃바운드

아웃바운드

아웃바운드

이벤트

핸들러

이벤트

핸들러

이벤트

인바운드

인바운드

인바운드

인바운드

인바운드

이벤트

핸들러

이벤트

핸들러

이벤트

그림 1.3 ChannelHandler의 체인을 통한 인바운드와 아웃바운드 이벤트의 흐름

모든 이벤트는 핸들러 클래스의 사용자 구현 메서드로 전달할 수 있다. 이것은 이벤트 기반 패러다임이 직접적으로 애플리케이션 구성요소로 변환되는 좋은 사례다. 그림 1.3에 이러한 이벤트 핸들러의 체인 을 통해 이벤트가 처리되는 방법이 나온다. 네티의 ChannelHandler는 그림 1.3에 나온 것과 같은 핸들러의 기본 추상화를 제공한다. ChannelHandler 에 대해서는 이후 적절한 시점에 자세히 다룬다. 지금은 각 핸들러 인스턴스가 특정 이벤트에 반응해 실 행되는 일종의 콜백이라고 이해하면 된다.

1부. 네티 개념과 아키텍처

15


네티는 HTTP 및 SSL/TLS와 같은 프로토콜용 핸들러를 비롯해 곧바로 이용할 수 있는 미리 정의된 핸 들러를 광범위하게 제공한다. ChannelHandler는 내부적으로 이벤트와 Future를 이용하므로 여러분의 애 플리케이션에 적용할 일부 추상화에 대한 소비자가 된다.

1.3.5 내용 정리 이 단원에서는 네티가 고성능 네트워킹을 구현하기 위해 활용하는 접근법과 구현의 몇 가지 주요 컴포넌 트를 소개했다. 지금까지 논의한 사항을 큰 그림으로 정리해보자. 퓨처, 콜백, 핸들러 네티의 비동기 프로그래밍 모델은 Future와 콜백의 개념, 그리고 더 깊은 단계에서 이벤트를 핸들러 메서 드로 발송하는 작업을 기반으로 작동한다. 이러한 요소를 함께 활용해 애플리케이션의 논리를 모든 네트 워크 작업에 대해 독립적으로 발전시킬 수 있는 처리 환경을 제공하는 것이 네티의 핵심 설계 목표다. 작업을 가로채고 인바운드나 아웃바운드 데이터를 즉시 변환하려면 콜백을 제공하거나 작업이 반환하는 Future를

활용하면 간단하다. 이 방식으로 작업을 쉽고 효율적으로 연결하고 재사용하기 쉬운 범용 코드

작성을 촉진할 수 있다. 셀렉터, 이벤트, 이벤트 루프 네티는 이벤트를 발생시켜

Selector를

애플리케이션 밖으로 추상화하므로 개발자가 발송 코드를 직접

작성할 필요가 없다. 각 Channel에 할당되는 EventLoop는 내부적으로 다음을 비롯한 모든 이벤트를 처리 한다. ■■ 관심 이벤트 등록 ■■ 이벤트를 ChannelHandler로 발송 ■■ 추가 동작 스케줄링

EventLoop

자체는 한 Channel의 모든 입출력 이벤트를 처리하는 한 스레드에 의해 제어되며, EventLoop의

수명 기간 동안 달라지지 않는다. 이 간단하고 강력한 설계 덕분에 ChannelHandler의 동기화와 관련된 모 든 문제를 고려할 필요 없이, 처리할 데이터가 있을 때 실행할 올바른 논리를 제공하는 데만 집중할 수 있다. 네티의 스레딩 모델을 자세히 살펴볼 때 다시 확인하겠지만 API는 간단하고 작다.

16

네티 인 액션: Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발


1.4 요약 1장에서는 자바 네트워킹 API의 발전사, 블로킹과 논블로킹 네트워크 작업의 차이, 고용량/고성능 네트 워킹에서 비동기 입출력의 장점을 비롯한 네티 프레임워크의 배경 정보를 살펴봤다. 그다음으로 네티의 특징, 설계, 장점을 간단히 정리했다. 그러면서 네티 비동기 모델의 기반 메커니즘에 해당하는 콜백, Future를 소개했고, 이것들을 조합해서 사용하는 방법을 간단히 설명했다. 그리고 이벤 트가 생성되는 방법과 이를 가로채고 처리하는 방법도 간단히 알아봤다. 앞으로는 이처럼 풍성한 도구들을 활용해 애플리케이션의 특정 요건을 충족하는 방법을 아주 자세히 알 아보겠다.

2장에서는 네티의 API 및 프로그래밍 모델의 기초를 알아보고 첫 번째 클라이언트와 서버를 작성한다.

1부. 네티 개념과 아키텍처

17


02 첫 번째 네티 애플리케이션

이 단원의 내용 ■■ 개발 환경 설정 ■■ Echo 서버와 클라이언트 만들기 ■■ 애플리케이션 빌드와 테스트

2장에서는 네티 기반 클라이언트와 서버를 만드는 과정을 다룬다. 이 예제 애플리케이션은 클라이언트 가 서버로 메시지를 전송하면 서버가 이 메시지를 반향 출력하는 아주 간단한 예제지만, 다음의 두 가지 이유로 아주 중요한 연습 과정이다. 첫째, 개발 툴과 환경을 설정하고 확인하는 시험대를 마련할 수 있다. 자신의 개발 환경을 준비하기 위해 이 책의 예제 코드를 직접 실행해보려면 이러한 기반 환경이 반드시 필요하다. 둘째, 1장에서 간단히 소개한 네티의 핵심 요소인 ChannelHandler를 이용해 애플리케이션의 논리를 구축 하는 과정을 체험할 수 있다. 3장부터 네티 API에 대한 심도 깊은 내용을 배우기 위해 꼭 필요한 준비 과 정이다.

18

네티 인 액션: Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발


2.1 개발 환경 설정 이 책의 예제를 컴파일하고 실행하려면 JDK와 아파치 메이븐이 필요하며 둘 모두 무료로 내려받을 수 있다. 이 책에서는 여러분이 예제 코드를 수정하고 테스트하며, 직접 코드를 작성하기도 한다고 가정한다. 코 드는 일반 텍스트 편집기로 작성해도 되지만, 그보다는 자바용 IDEintegrated development environment를 이용하는 것이 바람직하다.

2.1.1 JDK 내려받기와 설치 사용 중인 운영체제에 JDK가 이미 설치돼 있을 수 있다. 확인하려면 명령줄에 다음을 입력해본다. javac -version

javac 1.7...

또는 1.8...이라고 나오면 이미 준비가 된 것이므로 이 단계를 건너뛴다10.

http://java.com/en/download/manual.jsp에서 JDK 버전 8을 내려받을 수 있다. JREJava Runtime Environment

가 아닌 JDK를 내려받아야 한다는 것을 기억하자. JRE는 자바 애플리케이션을 컴파일할 때가

아니라 실행할 때 필요하다. 각 플랫폼에 맞는 설치 관리자 실행파일이 제공되며, 설치에 대한 설명도 해 당 사이트에서 찾을 수 있다. 다음과 같이 하는 것이 좋다. ■■ JAVA_HOME 환경변수를 JDK를 설치한 위치로 설정한다(윈도우에서는 C:\Program Files\Java\jdk1.8.0_60과 비슷한 기본 위치에 설치된다). ■■ 실행 경로에 %JAVA_HOME%\bin(리눅스의 경우 ${JAVA_HOME}/bin)을 추가한다.

2.1.2 IDE 내려받기와 설치 다음은 가장 널리 이용되는 세 가지 자바 IDE이며, 모두 무료로 받을 수 있다.

10 네티의 제한된 기능은 JDK 1.6으로도 실행되지만 컴파일하거나 메이븐의 최신 버전을 실행하려면 JDK 7 이상이 필요하다.

1부. 네티 개념과 아키텍처

19


■■ 이클립스Eclipse: www.eclipse.org ■■ 넷빈즈NetBeans: www.netbeans.org ■■ 인텔리제이 커뮤니티 버전Intellij Idea Community Edition: www.jetbrains.com

세 IDE 모두 우리가 이용하는 아파치 메이븐을 완전히 지원한다. 넷빈즈와 인텔리제이는 설치 관리자 실행파일로 배포된다. 보통 이클립스는 ZIP 압축 파일로 배포되지만 자체 설치 관리자가 있는 커스텀 버 전도 있다.

2.1.3 아파치 메이븐 내려받기와 설치 메이븐에 익숙하더라도 이 절을 간단히 읽어보기를 권한다. 메이븐은 아파치 소프트웨어 재단에서 개발한 빌드 관리 툴이며 아주 널리 이용된다. 네티 프로젝트에서 도 이 책의 예제와 마찬가지로 메이븐을 이용한다. 메이븐을 아주 잘 모르더라도 이 책의 예제를 빌드하 고 실행할 수는 있지만 가급적 부록의 메이븐 소개를 꼼꼼히 읽어보기를 권한다. 메이븐을 설치해야 할까? 이클립스와 넷빈즈에는 이 책을 진행하는 데 문제가 없는 메이븐이 자체적으로 포함돼 있다. 자체 메이븐 리포지토리가 있 는 환경에서 일하는 경우, 관리자가 메이븐 설치 패키지를 미리 구성해서 제공할 가능성이 있다.

이 책이 출간될 당시의 최신 메이븐 버전은 3.3.3이었다. http://maven.apache.org/download.cgi 에서 자신의 시스템에 맞는 tar.gz 또는 ZIP 파일을 내려받은 다음, 압축 파일을 원하는 폴더(이제부터 <install_dir>이라고

함)에 풀면 설치가 완료된다. 그러면 <install_dir>\apache-maven-3.3.3이라

는 디렉터리가 만들어진다. 자바 환경과 마찬가지로 다음과 같이 한다. ■■ M2_HOME 환경변수를 <install_dir>\apache-maven-3.3.3으로 설정한다. ■■ 실행 경로에 %M2_HOME%\bin(리눅스의 경우 ${M2_HOME}/bin)을 추가한다.

그러면 명령줄에 mvn.bat (또는 mvn )을 입력해 메이븐을 실행할 수 있다.

20

네티 인 액션: Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발


네티 인 액션 : Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발  

노먼 마우러, 마빈 알렌 울프탈 지음 | 최민석 옮김 | 시스템 & 네트워크 시리즈_004 | ISBN: 9791158390327 | 25,000원 | 2016년 04월 15일 발행 | 336쪽 | Netty, 네트워킹, 네티, 미나, 서버, 아파치,...

Read more
Read more
Similar to
Popular now
Just for you