TCP 통신 해야하는 경우 JAVA SOCKET을 통해 서버를 구성하거나 클라이언트 소스를 구성하여 연결을 하고 메시지를 주고 받고 종료하고 데이터를 가공하거나 처리하거나 등등 여러가지를 할 수 있는데, 이를 좀 더 기능적으로 사용하기 쉽게 도와주는 네트워크 프레임워크로 Netty가 있습니다.
이번에 프로젝트를 하나 진행하면서 여러가지 요구사항을 보다 쉽게 수행하고 추후에 능률적으로 유지보수하기 위해 Netty 프레임워크를 통해 진행해본 과정을 튜토리얼 식으로 남겨볼까 합니다.
Spring 프로젝트에 서버와 클라이언트를 구성하여 필요한 경우 연동처리를 진행하였는데, 이번에는 Maven프로젝트에서 구성하도록 하겠습니다.
Netty Client
1. 먼저 메이븐 프로젝트를 생성합니다.
2. 생성 후 Netty와 Log처리를 위해 Maven추가를 합니다. (pom.xml)
<!-- Netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.33.Final</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
3. 로그를 사용하기 위해 log4j 설정을 합니다.
# Root logger option
log4j.rootLogger=info, stdout, logfile
# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p (%C{2}:%L) - %m%n
# Direct log message to log file
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.DatePattern='.'yyyy-MM-dd
log4j.appender.logfile.File=C:/netty/logs/dailylog.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] %5p (%C{2} - %M:%L) - %m%n
log4j.appender.logfile.Append=true
log4j.appender.logfile.encoding=UTF-8
4. 옵션 설정과 동작시킬 client를 작성합니다 - 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.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 String[] msgArr;
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("보낼 메시지가 없습니다.");
}
bs.group(new NioEventLoopGroup(3))
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("clientHandler", new NettyClientHandler(msgArr));
}
});
ChannelFuture f = bs.connect(addr_);
f.channel();
logger.info(addr_ + " connect()");
}
}
NettyClient 생성자를 통해 보낼 메시지 배열과 연결정보를 넘깁니다.
run()메소드를 통해 연결할 옵션을 설정하고 채널을 연결합니다.
5. 실질적으로 동작할 Handler를 작성합니다 - 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[] msgArr;
private int idx = 0;
public NettyClientHandler(String[] msgArr) {
this.msgArr = msgArr;
}
@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("채널이 메시지 발송할 준비가 됨.");
sendMsg(ctx, this.idx);
}
@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);
//수신메시지 출력
String receiveMsg = new String( b );
logger.info("수신된 메시지 >" + receiveMsg);
//보낼 메시지가 없으면 연결 종료
if(msgArr.length == ++this.idx) {
channelClose(ctx);
}else {
sendMsg(ctx, this.idx);
}
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
logger.info("메시지를 받는 동작이 끝나면 동작하는 메소드.");
}
private void sendMsg(ChannelHandlerContext ctx, int i) {
String msg = msgArr[idx];
ByteBuf messageBuffer = Unpooled.buffer();
messageBuffer.writeBytes(msg.getBytes());
ctx.writeAndFlush( messageBuffer ); //메시지를 발송하고 flush처리
logger.info("발송 메시지 >" + msg);
}
private void channelClose(ChannelHandlerContext ctx) {
logger.error("채널 연결 종료");
ctx.close();
}
}
handler는 ChannelInboundHandlerAdapter를 상속받아 사용하고 각 동작별 정의된 메소드를 통해 데이터를 전달하거나 받을 수 있습니다.
연결이 되자마자 전송할 메시지를 처리하고, read메소드에서는 서버에서 전송하는 메시지를 받습니다.
메소드별로 간단하게 정리를 해보겠습니다.
- channelRegistered : 채널이 등록되면 동작합니다. 등록만 되었지 입출력 이벤트는 동작하지 않습니다.
- channelUnregistered : 반대로 채널이 해제되는 경우 닫힌 경우 동작합니다.(본인이 종료하거나 서버측이 종료 됨.)
- channelActive : 채널에 입출력이 가능해지면 동작하는 메소드입니다.
- channelRead : 메시지가 수신되면 동작합니다.
- channelReadComplete : 메시지 수신이 끝나면 동작합니다. (더 이상 수신할 정보가 없을 경우)
- userEventTriggered : 유저가 커스텀하여 등록한 이벤트가 발생할 경우 동작합니다. (timeout 등)
- exceptionCaught : exception 발생
6. 동작 테스트 - App.java
package com.psw.socket.nettyPrj;
import org.apache.log4j.Logger;
import com.psw.socket.nettyPrj.netty.client.NettyClient;
public class App {
private static final Logger logger = Logger.getLogger(App.class);
public static void main( String[] args ){
logger.info("======= Netty Client Test =======");
String host = "127.0.0.1";
int port = 15510;
String[] msgArr = {"hello world\n", "hello world2\n", "hello world3\n"};
new NettyClient(host, port, msgArr).run();
}
}
저는 SockTest v3.0.0을 통해 테스트를 진행하였습니다.
hello world부터 hello world3까지 발송하는 모습을 볼 수 있습니다.
아래 URL에서 소켓테스트 프로그램을 받을 수 있습니다.
sourceforge.net/projects/sockettest/
'JAVA' 카테고리의 다른 글
Netty Client 튜토리얼 - 02 (메시지별 보내고 받고 끊기) (0) | 2020.11.30 |
---|---|
JAVA - JConsole 활용기 (자바 Thread 동작, 종료 체크하기) (0) | 2020.11.30 |
JAVA - 문자열에 숫자만 존재하는지 확인하기 (2) | 2020.11.19 |
JAVA - 정렬 알고리즘 삽입정렬 (0) | 2020.09.09 |
JAVA - 정렬 알고리즘 선택정렬 (배열 내림차순, 오름차순 정렬) (0) | 2020.09.03 |