자바 네트워크 소녀 네티 1장

Discard 서버

  • 클라이언트로 부터 데이터를 8888번 포트로 수신하는 기능을 수행
  • 데이터만 받고 클라이언트에 어떠한 데이터도 전송하지 않음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DiscardServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new DiscardServerHandler()); // (1)
}
});

ChannelFuture future = bootstrap.bind(8888).sync(); // (2)
future.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
  • 클라이언트의 입력을 받아들일 포트를 지정하는 메인 서버클라이언트로부터 받아들인 입력 데이터를 처리하는 핸들러 로 구분
  • 메인 서버에는 입력받은 데이터를 처리할 핸들러를 지정하는 부분이 포함
  • 이벤트 루프, 부트스트랩, 채널 파이트라인, 핸들러 등의 클래스를 초기화하는 코드
  1. 접속된 클라이언트로부터 수신된 데이터를 처리할 핸들러를 지정
  2. 부트스트랩 클래스의 bind 메서드로 접속할 포트를 지정
  • 8888번 포트에 클라이언트 접속을 허용하고 클라이언트로부터 받은 데이터를 DiscardServerHandler 클래스가 처리하도록 지정
1
2
3
4
5
6
7
8
9
10
11
12
class DiscardServerHandler extends SimpleChannelInboundHandler<Object> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
// do not anything
}

@Override
public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
cause.printStackTrace();
context.close();
}
}
  • SimpleChannelInboundHandler 클래스를 상속받아서 channelRead0과 exceptionCaught 메서드 재정의

Echo 서버 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class EchoServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new EchoServerHandler()); // (1)
}
});

ChannelFuture future = bootstrap.bind(8888).sync();
future.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
  1. 클라이언트로부터 데이터를 수신한 뒤 EchoServerHandler 로 전달
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class EchoServerHandler extends ChannelInboundHandlerAdapter { // (1)

@Override
public void channelRead(ChannelHandlerContext context, Object msg) { // (2)
String readMessage = ((ByteBuf) msg).toString(Charset.defaultCharset()); // (3)
System.out.println("수신한 문자열 [" + readMessage + "]"); //(4)
context.write(msg); // (5)
}

@Override
public void channelReadComplete(ChannelHandlerContext context) { // (6)
context.flush();
}

@Override
public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
cause.printStackTrace();
context.close();
}
}
  1. 입력된 데이터를 처리하는 이벤트 핸들러인 ChannelInboundHandlerAdapter 를 상속 받도록 선언
  2. 데이터 수신 이벤트 처리 메서드. 클라이언트로부터 데이터의 수신이 이루어졌을 때 네티가 자동으로 호출하는 이벤트 메서드
  3. 수신된 데이터를 가지고 있는 네티의 바이트 버퍼 객체로부터 문자열 데이터를 읽어온다
  4. 수신된 문자열을 콘솔로 출력
  5. context는 채널 파이프라인에 대한 이벤트를 처리한다
  6. channelRead 메서드의 처리가 완료된 후 자동으로 수행되는 이벤트 메서드. 채널 파이프라인에 저장된 버퍼를 전송하는 flush 메서드를 호출

클라이언트 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class EchoClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();

try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group) // (1)
.channel(NioSocketChannel.class) // (2)
.handler(new ChannelInitializer<SocketChannel>() { // (3)
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new EchoClientHandler());
}
});

ChannelFuture future = bootstrap.connect("localhost", 8888).sync(); // (4)
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
  1. 서버 애플리케이션의 부트스트랩 설정과 다르게 이벤트 루프 그룹이 1개만 설정
    • 클라이언트 애플리케이션은 서버에 연결된 채널 하나만 존재
  2. 클라이언트 애플리케이션이 생성하는 채널의 종류를 설정
    • NIO 소켓 채널인 NioSocketChannel 클래스를 설정
    • 서버에 연결된 클라이언트의 소켓 채널은 NIO로 동작
  3. 채널 파이프라인의 설정에 일반 소켓 채널 클래스인 SocketChannel을 설정
  4. 비동기 입출력 메소드인 connect()를 호출
    • 메서드의 호출 결과로 ChannelFuture 객체를 반환
    • ChannelFuture 객체를 통해서 비동기 메서드의 처리 결과를 확인
    • sync() 메서드는 ChannelFuture 객체의 요청이 완료될 때까지 대기
    • 요청이 실패하면 예외를 던지는데, connect() 메서드의 처리가 완료될 때까지 다음 라인으로 진행하지 ㅇ않음
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
class EchoClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext context) { // (1)
String sendMessage = "Hello, Netty!";

ByteBuf messageBuffer = Unpooled.buffer();
messageBuffer.writeBytes(sendMessage.getBytes());

StringBuilder builder = new StringBuilder();
builder.append("전송한 문자열[");
builder.append(sendMessage);
builder.append("]");

System.out.println(builder.toString());
context.writeAndFlush(messageBuffer); // (2)
}

@Override
public void channelRead(ChannelHandlerContext context, Object message) { // (3)
String readMessage = ((ByteBuf)message).toString(Charset.defaultCharset()); // (4)

StringBuilder builder = new StringBuilder();
builder.append("수신한 문자열[");
builder.append(readMessage);
builder.append("]");

System.out.println(builder.toString());
}

@Override
public void channelReadComplete(ChannelHandlerContext context) { // (5)
context.close(); // (6)
}

@Override
public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
cause.printStackTrace();
context.close();
}
}
  1. channelActive 이벤트는 소켓 채널이 최초 활성화 되었을 때 실행
  2. writeAndFlush()는 내부적으로 데이터 기록과 전송의 두 가지 메서드를 호출
  3. 서버로부터 수신된 데이터가 있을 때 호출되는 이벤트 메서드
  4. 서버로부터 수신된 데이터가 저장된 message 객체에서 문자열 데이터를 추출
  5. 수신된 데이터를 모두 읽었을 때 호출되는 이벤트 메서드
    • channelRead 메서드의 수행이 완료되고 나서 자동으로 호출
  6. 수신된 데이터를 모두 읽은 후 서버와 연결된 채널을 닫음
    • 데이터 송수신 채널은 닫히게 되고 클라이언트 프로그램은 종료

간단하지만 강력한 예제

  • NIO, 이벤트 루프, 이벤트 핸들링 등의 기능을 제공하는 완벽한 네트워크 어플리케이션

데이터 이동의 방향성

  • 이벤트를 inbound 이벤트와 outbound 이벤트로 구분한 추상화 모델을 제공
    • 개발자가 신경 써야 하는 이벤트들을 논리적으로 구분하여 고수준의 추상화 모델을 제공
  • 서버 개발자는 데이터가 수신될 때 발생하는 이벤트에 관심이 있다면 인바운드 이벤트 중 하나인 데이터 수신 이벤트 메서드에서 데이터를 가공하는 코드를 작성
  • 적절한 추상화 모델을 제공하여 사용자로 하여금 간단한 코드로 애플리케이션을 개발할 수 있게 지원
  • 네트워크 송수신 추상화를 위해 이벤트 모델을 정의
    • 아웃바운드 이벤트: 데이터 송신
    • 인바운드 이벤트: 데이터 수신
    • 각 이벤트는 프로그램을 기준으로 정의
분산 트랜잭션 - 큰힘에는 큰 책임이 따른다 [유승기, MongoDB]

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×