반응형

myhappyman.tistory.com/187

 

Netty - Netty Client 튜토리얼 - 01

TCP 통신 해야하는 경우 JAVA SOCKET을 통해 서버를 구성하거나 클라이언트 소스를 구성하여 연결을 하고 메시지를 주고 받고 종료하고 데이터를 가공하거나 처리하거나 등등 여러가지를 할 수 있

myhappyman.tistory.com

NettyClient에 대해 포스팅을 진행하고 있는데, 첫번째 포스팅 글에서는 10개의 보낼 메시지가 있으면 한번 연결된 상태에서 10개의 메시지를 발신할때까지 핸들러에서 제어하면서 처리를 하였습니다.

 

다른 진행 방법으로 요청사항으로 메시지별 발송 -> 수신 -> 종료 형태로 반복하여 처리해달라고 하게 되면, 어떤식으로 처리를 해야 할지 알아보겠습니다.

 

연결 - 발신 - 수신 - 종료

 

NettyClient

1. NettyClientAction.java

package com.psw.socket.nettyPrj.netty.client;

public interface NettyClientAction {
	public void close(NettyClientHandler handler);
	public void receive(NettyClientHandler handler);
}

콜백 메소드 처리를 위해 interface 메소드 선언

 

2. NettyClient.java

package com.psw.socket.nettyPrj.netty.client;

import java.net.InetSocketAddress;
import java.net.SocketAddress;

import org.apache.log4j.Logger;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * NettyClient
 * @author psw
 */
public class NettyClient {
	private static final Logger logger = Logger.getLogger(NettyClient.class);
	
	private Bootstrap bs = new Bootstrap();
	private SocketAddress addr_;
	private Channel channel_;
	private String[] msgArr;
	private int idx;
	private NioEventLoopGroup group;
	
	public NettyClient(SocketAddress addr, String[] msgArr) {
		this.addr_ = addr;
		this.msgArr = msgArr;
	}
	
	public NettyClient(String host, int port, String[] msgArr) {
		this(new InetSocketAddress(host, port), msgArr);
	}
	
	//실제로 동작시킬 메소드 Bootstrap 연결 옵션 설정 및 연결 처리
	public void run() {
		if(this.addr_ == null) {
			logger.error("주소 정보가 없습니다.");
		}else if(this.msgArr == null || this.msgArr.length == 0) {
			logger.error("보낼 메시지가 없습니다.");
		}
		
		group = new NioEventLoopGroup(3);
		bs.group(group)
		.channel(NioSocketChannel.class)
		.option(ChannelOption.SO_KEEPALIVE, true);
		
		
		doConnect();
	}
	
	private void doConnect() {
		handlerSet();
		
		ChannelFuture f = bs.connect(addr_);
		channel_ = f.channel();
		logger.info(addr_ + " connect()");
	}
	
	private void handlerSet() {
		if(bs != null) {
			bs.handler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel ch) throws Exception {
					NettyClientHandler handler = new NettyClientHandler(msgArr[idx]);
					handler.setCallBackClientHandler(new NettyClientAction() {

						public void close(NettyClientHandler handler) {
							//종료 처리 후 더 보낼게 존재한다면 기존 옵션으로 재 연결처리를 하는 콜백 메소드
							closeAndContinue();
						}

						public void receive(NettyClientHandler handler) {
							//응답 받은 메시지 콜백 메소드
							String receiveMsg = handler.getReceiveMsg();
							logger.info("callBack receive : "+ receiveMsg);
							closeAndContinue();
						}
						
					});
					
					ch.pipeline().addLast("clientHandler", handler);
				}
			});
		}
	}
	
	private void closeAndContinue() {
		try {
			channel_.close().sync(); //현재의 채널을 일단 닫는다.
			if(msgArr.length > ++idx) { //보낼 메시지가 남았으면 재연결 처리
				doConnect(); 
			}else { //보낼 메시지가 없다면 종료
				bs.group().shutdownGracefully(); //eventLoop에 등록된 Thread를 종료 처리한다.
			}
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

- 생성자를 통해 연결지의 IP, PORT정보를 얻고 보낼 메시지 배열을 전달받습니다.

- run() 메소드를 통해 전송을 위한 옵션 설정을 진행합니다. - Bootstrap 옵션 처리 (옵션처리는 최초에만 한번 설정)

- 옵션 설정이 끝나면 doConnect메소드를 통해 연결처리를 진행합니다.

- 연결 전 handler부분에서 메소드가 동작 후 콜백 받을 메소드를 정의합니다.

- 연결

- 동작들이 일어나고 receive 콜백 메소드가 동작하면 closeAndContinue() 메소드에서 더 보낼지 멈출지 판단합니다.

  -> 더 이상 보낼 메시지가 없다면 설정한 그룹들의 EventGroup을 모두 종료하기 위해 shutdown처리를 합니다.

 

3. NettyClientHandler.java

package com.psw.socket.nettyPrj.netty.client;

import org.apache.log4j.Logger;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class NettyClientHandler extends ChannelInboundHandlerAdapter{
	
	private static final Logger logger = Logger.getLogger(NettyClientHandler.class);
	
	private String sendMsg;
	public NettyClientHandler(String msg) {
		this.sendMsg = msg;
	}
	
	private NettyClientAction action_;
	public void setCallBackClientHandler(NettyClientAction action) {
		this.action_ = action;
	}
	
	private String receiveMsg;
	public String getReceiveMsg() {
		return this.receiveMsg;
	}
	
	@Override
	public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
		logger.info("채널 등록");
	}
	
	@Override
	public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
		System.out.println("채널 연결이 종료됨.");
	}
	
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		logger.info("채널이 메시지 발송할 준비가 됨.");
		ByteBuf messageBuffer = Unpooled.buffer();
		messageBuffer.writeBytes(sendMsg.getBytes());
		ctx.writeAndFlush( messageBuffer ); //메시지를 발송하고 flush처리
		logger.info("발송 메시지 >" + sendMsg);
	}
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
		logger.info("메시지를 받는 메소드.");
		ByteBuf buf = (ByteBuf)msg;
		int n = buf.readableBytes();
		if( n > 0 ) {
			byte[] b = new byte[n];
			buf.readBytes(b);
			//수신메시지 출력
			this.receiveMsg = new String( b );
			logger.info("handler 수신된 메시지 >" + this.receiveMsg);
			
			if(this.action_ != null) {
				action_.receive(NettyClientHandler.this);
			}
		}
	}
	
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) {
		logger.info("메시지를 받는 동작이 끝나면 동작하는 메소드.");
	}
}

- 채널이 발신 준비가되면 active메소드가 동작하고 발송할 메시지를 전달합니다.

- 응답이 오면 메시지를 읽어들이고 지역변수 receiveMsg에 담아주고 콜백 메소드 receive를 호출합니다.

- receive가 동작되면서 getReceiveMsg 메소드를 통해 전달받은 값을 받고, 현재의 채널을 종료처리 합니다.

- 보낼 메시지가 없을때까지 반복합니다.

 

실행결과

메시지 첫번째의 응답이 오면 채널이 종료되고 재연결되면서 두번째 메시지를 보낸다.

 

 

반응형