본문 바로가기
Java

[Java] Netty로 WebSocket 통신하기

by teamnova 2023. 10. 2.

안녕하세요

이번 시간에는 네트워크 애플리케이션 프레임워크인 Netty로 간단한 WebSocket 에코 서버를 만들어보겠습니다.

 

1. Netty 환경 세팅

Ubuntu 20.04.6 LTS에서 실행했습니다.

 

Netty 프로젝트를 빌드하기 위해 Maven을 설치해보겠습니다.

Maven 란 자바 프로젝트 빌드 자동화 도구이고 Apache 소프트웨어 재단에서 관리하고 있습니다.

다음 명령어를 실행하여 maven을 설치합니다.

sudo apt update
sudo apt install maven

 

다음 명령어를 실행해서 잘 설치 됐는지 확인할 수 있습니다.

mvn -version

# 잘 설치됐으면 아래와 같이 출력됩니다.
# Apache Maven 3.6.3
# Maven home: /usr/share/maven
# Java version: 11.0.7, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
# Default locale: en_US, platform encoding: UTF-8
# OS name: "linux", version: "5.4.0-26-generic", arch: "amd64", family: "unix"

 

작업 폴더로 이동한 뒤 Git에서 Netty 웹소켓 서버 예제를 다운 받아줍니다.

git clone https://github.com/baardl/netty-websocket-server.git

 

프로젝트 내에 있는 Main 클래스를 실행하기 위해 설정파일에 플러그인을 추가해줘야합니다.

다운받은 폴더로 이동한뒤 pom.xml 파일에서 다음과 같이 <plugins> 태그 안에 설정을 추가합니다.

pom.xml 위치 → ( netty-websocket-server > pom.xml )

<plugins>

	... 다른 플러그인 설정들 ...

	<plugin>
	    <groupId>org.codehaus.mojo</groupId>
	    <artifactId>exec-maven-plugin</artifactId>
	    <version>3.1.0</version>
	    <configuration>
	        <mainClass>com.bardlind.ws.Main</mainClass>
	    </configuration>
	</plugin>

</plugins>

 

 

Main 클래스 위치는 다음과 같습니다.

netty-websocket-server > src > main > java > com > bardlind > ws > Main.java

netty-websocket-server 위치에서 다음 명령어를 실행하여 빌드합니다.

mvn clean install

 

“BUILD SUCCESS”가 표시되면 빌드 성공입니다.

 

다음 명령어로 프로젝트를 실행할 수 있습니다.

mvn exec:java

이제 Main 클래스를 커스텀해서 작업을 진행할 수 있습니다.

 

 

2. WebSocket 서버 코드

Main.java

package com.bardlind.ws;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;

import java.io.File;

public class Main {

    private static int PORT = 4000;

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<Channel>() {
                        @Override
                        protected void initChannel(Channel ch) {
                            ch.pipeline().addLast(
                                    new HttpServerCodec(),
                                    new HttpObjectAggregator(65536),
                                    new WebSocketFrameAggregator(65536),
                                    new WebSocketServerProtocolHandler("/websocket"),
                                    new WebSocketEchoServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture future = b.bind(PORT).sync();
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

 

WebSocketEchoServerHandler.java

package com.bardlind.ws;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import java.awt.*;

public class WebSocketEchoServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

	// 클라이언트와 연결됐을때 실행되는 콜백함수
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelActive");
    }


    // 클라이언트와 연결이 끊어졌을 때 실행되는 콜백함수
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client disconnected: " + ctx.channel().remoteAddress());

        super.channelInactive(ctx);
    }

	// 클라이언트로부터 데이터를 전송 받았을때 실행되는 콜백함수
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
        System.out.println("channelRead0: " + ctx.toString());
        System.out.println("channelRead0: " + msg.text());

    }

}

 

 

3. 클라이언트 코드

클라이언트 쪽 JavaScript 코드입니다.

let webSocket;

// 웹소켓 연결하는 함수
const connectWebSocketServer = () => {
    webSocket = new WebSocket("ws://{네티 서버 IP주소}:4000");
    
    // 웹소켓 이벤트 처리
    // 연결 이벤트 처리
    webSocket.onopen = () => {
        console.log("웹소켓서버와 연결됨");
    };
    
    // 메세지 수신 이벤트 처리
    webSocket.onmessage = (event) => {
        console.log("서버 웹소켓에게 받은 데이터: ${event.data}");
    }
    
    // 연결 종료 이벤트 처리
    webSocket.onclose = function(){
        console.log("서버 웹소켓 연결 종료");
    }
    
    // 에러 발생 이벤트 처리
    webSocket.onerror = function(event){
        console.log(event)
    }
}

// 웹소켓으로 메세지 전송하는 함수
const sendMessage = (message) => {
    if(webSocket != null){
        webSocket.send(message);
    }
}