Language/C#

[C#] DotNetty로 TCP 소켓 통신 구현: Unity와 게임 서버 간 패킷 교환하기

잔소리대마왕 2025. 1. 3. 09:51
Starting DotNetty server...
Server started on port 8080
Received: Hello 0
Received: Hello 1
Received: Hello 2
Received: Hello 3
Received: Hello 4

게임 개발에서 Unity와 서버 간의 원활한 통신은 매우 중요한 요소입니다. 특히 고성능의 비동기 통신 라이브러리인 DotNetty는 .NET 환경에서 이를 쉽게 구현할 수 있게 도와줍니다. 이 글에서는 DotNetty를 사용하여 Unity와 게임 서버 간의 패킷 교환 예제를 보여드리겠습니다.


DotNetty란?

DotNetty는 .NET 환경에서 사용할 수 있는 고성능 네트워크 라이브러리로, Java의 Netty를 기반으로 만들어졌습니다. 높은 확장성과 낮은 레이턴시를 제공하며, TCP 및 UDP 프로토콜을 지원합니다.

주요 특징

  • 비동기 I/O: 높은 성능과 확장성 제공.
  • 이벤트 기반 아키텍처: 네트워크 이벤트를 효율적으로 처리 가능.
  • 모듈화: 유연한 데이터 처리 파이프라인 구성.

프로젝트 설정

DotNetty를 활용하려면 NuGet 패키지를 설치해야 합니다. 다음 명령어를 통해 패키지를 추가합니다.

Install-Package DotNetty.Buffers
Install-Package DotNetty.Codecs
Install-Package DotNetty.Transport

DotNetty 서버 예제

using System;
using System.Text;
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;

namespace DotNettyServer
{
    public class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Starting DotNetty server...");
            var server = new GameServer();
            await server.RunAsync(8080);
        }
    }

    public class GameServer
    {
        public async Task RunAsync(int port)
        {
            var bossGroup = new MultithreadEventLoopGroup(1);
            var workerGroup = new MultithreadEventLoopGroup();

            try
            {
                var bootstrap = new ServerBootstrap();
                bootstrap.Group(bossGroup, workerGroup)
                         .Channel<TcpServerSocketChannel>()
                         .ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
                         {
                             var pipeline = channel.Pipeline;
                             pipeline.AddLast(new GameServerHandler());
                         }));

                var channel = await bootstrap.BindAsync(port);
                Console.WriteLine($"Server started on port {port}");
                await channel.CloseCompletion;
            }
            finally
            {
                await bossGroup.ShutdownGracefullyAsync();
                await workerGroup.ShutdownGracefullyAsync();
            }
        }
    }

    public class GameServerHandler : SimpleChannelInboundHandler<IByteBuffer>
    {
        protected override void ChannelRead0(IChannelHandlerContext ctx, IByteBuffer msg)
        {
            string receivedMessage = msg.ToString(Encoding.UTF8);
            Console.WriteLine($"Received: {receivedMessage}");

            // Echo the message back
            byte[] responseBytes = Encoding.UTF8.GetBytes($"Server response: {receivedMessage}");
            ctx.WriteAndFlushAsync(Unpooled.WrappedBuffer(responseBytes));
        }

        public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
        {
            Console.WriteLine($"Error: {exception.Message}");
            context.CloseAsync();
        }
    }
}
 

클라이언트 코드 (GameClient)

using System;
using System.Text;
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;

namespace DotNettyClient
{
    public class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Starting DotNetty client...");
            var client = new GameClient();
            await client.RunAsync("127.0.0.1", 8080);
        }
    }

    public class GameClient
    {
        public async Task RunAsync(string host, int port)
        {
            var group = new MultithreadEventLoopGroup();

            try
            {
                var bootstrap = new Bootstrap();
                bootstrap.Group(group)
                         .Channel<TcpSocketChannel>()
                         .Handler(new ActionChannelInitializer<IChannel>(channel =>
                         {
                             var pipeline = channel.Pipeline;
                             pipeline.AddLast(new GameClientHandler());
                         }));

                var channel = await bootstrap.ConnectAsync(host, port);
                Console.WriteLine("Client connected to server.");

                for (int i = 0; i < 5; i++)
                {
                    string message = $"Hello {i}";
                    byte[] messageBytes = Encoding.UTF8.GetBytes(message);
                    Console.WriteLine($"Sending: {message}");
                    await channel.WriteAndFlushAsync(Unpooled.WrappedBuffer(messageBytes));
                    await Task.Delay(1000);
                }

                await channel.CloseAsync();
            }
            finally
            {
                await group.ShutdownGracefullyAsync();
            }
        }
    }

    public class GameClientHandler : SimpleChannelInboundHandler<IByteBuffer>
    {
        protected override void ChannelRead0(IChannelHandlerContext ctx, IByteBuffer msg)
        {
            string receivedMessage = msg.ToString(Encoding.UTF8);
            Console.WriteLine($"Server response: {receivedMessage}");
        }

        public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
        {
            Console.WriteLine($"Error: {exception.Message}");
            context.CloseAsync();
        }
    }
}

실행 방법

  1. 서버 실행: GameServer 클래스를 실행하여 서버를 시작합니다.
  2. 클라이언트 실행: GameClient 클래스를 실행하여 서버와 통신을 시작합니다.
  3. 클라이언트에서 보낸 메시지가 서버로 전달되고, 서버는 응답을 반환합니다.

실행 결과

1. 서버

Starting DotNetty server...
Server started on port 8080
Received: Hello 0
Received: Hello 1
Received: Hello 2
Received: Hello 3
Received: Hello 4

 

1. 클라이언트

Starting DotNetty client...
Client connected to server.
Sending: Hello 0
Server response: Server response: Hello 0
Sending: Hello 1
Server response: Server response: Hello 1
Sending: Hello 2
Server response: Server response: Hello 2
Sending: Hello 3
Server response: Server response: Hello 3
Sending: Hello 4
Server response: Server response: Hello 4

마무리

DotNetty는 TCP 소켓 통신에서 뛰어난 성능과 확장성을 제공하며, Unity와 같은 게임 엔진과의 통합에 적합한 라이브러리입니다. 이번 글에서는 간단한 예제를 통해 DotNetty를 사용한 서버-클라이언트 통신을 구현하는 방법을 알아보았습니다.