📚 [JAVA] Spring Boot - Netty 서버 만들기
Category: JAVA | 📅 March 11, 2017
개요
차기 프로젝트에서 모뎀 단말기와 통신 하는 모듈을 개발 하기 위해 Spring Boot + Netty로 프로그램을 구성할 수 있는지 테스트.
프로젝트 구조

메이븐 설정
4.0.0 com.example demo 0.0.1-SNAPSHOT jar boot-netty Demo project for Spring Boot org.springframework.boot spring-boot-starter-parent 1.5.2.RELEASE UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test io.netty netty-all 4.1.8.Final org.springframework.boot spring-boot-maven-plugin
BootNettyApplication
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class BootNettyApplication {
@Autowired
private ApplicationContext context;
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(BootNettyApplication.class, args);
NettyServer nettyServer = context.getBean(NettyServer.class);
nettyServer.start();
}
}
NettyServer
package com.example;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* The type Netty server.
*/
@Component
@PropertySource(value = "classpath:/application.properties")
public class NettyServer {
/**
* The Tcp port.
*/
@Value("${tcp.port}")
private int tcpPort;
/**
* The Boss count.
*/
@Value("${boss.thread.count}")
private int bossCount;
/**
* The Worker count.
*/
@Value("${worker.thread.count}")
private int workerCount;
/**
* The constant SERVICE_HANDLER.
*/
private static final ServiceHandler SERVICE_HANDLER = new ServiceHandler();
/**
* Start.
*/
public void start() {
/**
* 클라이언트 연결을 수락하는 부모 스레드 그룹
*/
EventLoopGroup bossGroup = new NioEventLoopGroup(bossCount);
/**
* 연결된 클라이언트ㄹ의 소켓으로 부터 데이터 입출력 및 이벤트를 담당하는 자식 스레드
*/
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) //서버 소켓 입출력 모드를 NIO로 설정
.handler(new LoggingHandler(LogLevel.INFO)) //서버 소켓 채널 핸들러 등록
.childHandler(new ChannelInitializer() { //송수신 되는 데이터 가공 핸들러
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LoggingHandler(LogLevel.INFO));
pipeline.addLast(SERVICE_HANDLER);
}
});
ChannelFuture channelFuture = b.bind(tcpPort).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}</pre>
ServiceHandler
package com.example;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
/**
* The type Service handler.
*/
@ChannelHandler.Sharable
public class ServiceHandler extends ChannelInboundHandlerAdapter {
/**
* The Logger.
*/
Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* The Channels.
*/
private final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* Channel active.
*
* @param ctx the ctx
* @throws Exception the exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
channels.add(ctx.channel());
}
/**
* Channel read.
*
* @param ctx the ctx
* @param msg the msg
* @throws Exception the exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
logger.debug("message : {} ",byteBuf.toString(Charset.defaultCharset()));
channels.writeAndFlush(msg);
}
}
application.properties
tcp.port=8080
boss.thread.count=1
worker.thread.count=1
so.keepalive=true
so.backlog=100
테스트
BootNettyApplication 실행
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.2.RELEASE)
2017-03-11 04:20:05.735 INFO 7356 --- [ main] com.example.BootNettyApplication : Starting BootNettyApplication on DESKTOP-CC5LT0H with PID 7356 (C:\EEProject\workspaces_example\boot-netty\target\classes started by redpunk in C:\EEProject\workspaces_example\boot-netty)
2017-03-11 04:20:05.739 INFO 7356 --- [ main] com.example.BootNettyApplication : No active profile set, falling back to default profiles: default
2017-03-11 04:20:05.812 INFO 7356 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@35a50a4c: startup date [Sat Mar 11 04:20:05 KST 2017]; root of context hierarchy
2017-03-11 04:20:06.893 INFO 7356 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-03-11 04:20:06.927 INFO 7356 --- [ main] com.example.BootNettyApplication : Started BootNettyApplication in 1.635 seconds (JVM running for 2.153)
2017-03-11 04:20:08.381 INFO 7356 --- [ntLoopGroup-2-1] io.netty.handler.logging.LoggingHandler : [id: 0x5a6c69e2] REGISTERED
2017-03-11 04:20:08.382 INFO 7356 --- [ntLoopGroup-2-1] io.netty.handler.logging.LoggingHandler : [id: 0x5a6c69e2] BIND: 0.0.0.0/0.0.0.0:8080
2017-03-11 04:20:08.385 INFO 7356 --- [ntLoopGroup-2-1] io.netty.handler.logging.LoggingHandler : [id: 0x5a6c69e2, L:/0:0:0:0:0:0:0:0:8080] ACTIVE
텔넷으로 메세지 전송

2017-03-11 04:26:45.313 INFO 7356 --- [ntLoopGroup-2-1] io.netty.handler.logging.LoggingHandler : [id: 0x5a6c69e2, L:/0:0:0:0:0:0:0:0:8080] RECEIVED: [id: 0x57c00a34, L:/127.0.0.1:8080 - R:/127.0.0.1:8667]
2017-03-11 04:26:45.315 INFO 7356 --- [ntLoopGroup-3-1] io.netty.handler.logging.LoggingHandler : [id: 0x57c00a34, L:/127.0.0.1:8080 - R:/127.0.0.1:8667] REGISTERED
2017-03-11 04:26:45.315 INFO 7356 --- [ntLoopGroup-3-1] io.netty.handler.logging.LoggingHandler : [id: 0x57c00a34, L:/127.0.0.1:8080 - R:/127.0.0.1:8667] ACTIVE
2017-03-11 04:26:48.507 INFO 7356 --- [ntLoopGroup-3-1] io.netty.handler.logging.LoggingHandler : [id: 0x57c00a34, L:/127.0.0.1:8080 - R:/127.0.0.1:8667] RECEIVED: 7B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f 0d 0a |hello.. |
+--------+-------------------------------------------------+----------------+
2017-03-11 04:26:48.521 INFO 7356 --- [ntLoopGroup-3-1] io.netty.handler.logging.LoggingHandler : [id: 0x57c00a34, L:/127.0.0.1:8080 - R:/127.0.0.1:8667] WRITE: 7B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f 0d 0a |hello.. |
+--------+-------------------------------------------------+----------------+
2017-03-11 04:26:48.523 INFO 7356 --- [ntLoopGroup-3-1] io.netty.handler.logging.LoggingHandler : [id: 0x57c00a34, L:/127.0.0.1:8080 - R:/127.0.0.1:8667] FLUSH