Java

[Java] Netty로 WebSocket 통신하기

teamnova 2023. 10. 2. 12:00
728x90

안녕하세요

이번 시간에는 네트워크 애플리케이션 프레임워크인 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);
    }
}