Java Netty를 .Net C#으로 포팅한 DotNetty 를 알고계신가요...? (유니티 클라에 적용예정)

1,527 views
Skip to first unread message

SOOH LEE

unread,
Jan 14, 2020, 8:20:13 AM1/14/20
to Netty Korean User Group

깃헙링크 입니다



보니까 Akka.Net 등에서도 사용되고 있는거 같은데 국내에서는 크게 자료가 많진 않더라구요 ㄷㄷ;;




어디선가 발견한 DotNetty 코드인데 요런거 쓸때, netty에 대해서도 어느정도 알아야 하나요?




using UnityEngine;
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Collections;
using System.Collections.Generic;
using DotNetty.Buffers;
using DotNetty.Handlers.Logging;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
public class NetworkManager : MonoBehaviour
{
//싱글톤 패턴을 구현하기 위한 고정객체변수.
public static NetworkManager ins;
Thread networkClientThread;
//네트워크 이벤트 처리용 부트스트랩.
static Bootstrap tcpBootstrap;
static Bootstrap udpBootstrap;
//통신용 소켓채널. 소켓과 같다고 보면 된다.
static IChannel tcpChannel;
static IChannel udpChannel;
//ByteBuf 재사용 Pool
static PooledByteBufferAllocator alloc = PooledByteBufferAllocator.Default;
//목적지 지정.
static IPEndPoint serverAddress_TCP = null;
static IPEndPoint serverAddress_UDP = null;
//메인스레드 통신용 Queue 제어용 락 모니터 객체.
public static object locking = new object();
//메인스레드 통신용 Queue.
public static Queue<Packet_Data> packet_Queue;
void Awake()
{
//싱글톤 패턴을 위한 부분...?
if (ins == null)
{
ins = this;
}
//혹시나 생성된 tcp_instance가 자기자신이 아니라면 이미 생성된 것이 존재한다는 것이므로.
//나중에 생성된 객체를 다시 파괴해서 총 객체수가 언제나 1개가 되게끔 유지!
else if (ins != this)
{
Destroy(gameObject);
}
//TCP 매니저 객체를 계속 유지!
DontDestroyOnLoad(gameObject);
//별도 쓰레드에서 동작하는 비동기 소켓 메소드에서 값을 수신했을때 결과값을 담아두는 Queue. 받아온 값을 메인 쓰레드에서도 활용할 수 있게끔 하는것이 목적이다
packet_Queue = new Queue<Packet_Data>();
}
void Start()
{
//네트워크 클라이언트 스레드 초기화 & 시작.
networkClientThread = new Thread(Unity_DotNettyClient);
networkClientThread.Start();
}
void OnEnable()
{
//받은 데이터를 Queue에서 꺼내서 메인 스레드로 옮김!
StartCoroutine(recvPacket());
}
void OnDisable()
{
//코루틴 종료.
StopCoroutine(recvPacket());
}
//메인 스레드에서 동작하는 코루틴.
IEnumerator recvPacket()
{
Debug.Log("start recvPacket()");
WaitForSecondsRealtime recvRate = new WaitForSecondsRealtime(0.005f); //5ms 단위로 Queue 확인하여 메인스레드로 데이터를 가져옴.
Packet_Data recvData = null;
while (true)
{
//서로 다른 스레드가 Queue를 공유하고 있으므로, 동기화 제어.
lock (locking)
{
//데이터가 있다면 가져옴.
if (packet_Queue.Count > 0)
{
recvData = packet_Queue.Dequeue();
}
}
//가져온 데이터가 있다면 수신처리.
if (recvData != null)
{
//패킷이 도착함.
byte[] data = recvData.packet_data;
//do Something. 이부분에, 다른 메소드 등을 넣어서, 패킷 수신시 처리등을 하면 된다.
recvData = null;
}
yield return recvRate; //지정된 시간만큼 대기.
}
}
//게임 서버로 접속 시도!
public async static void ConnectGameServer(string TYPE, string IP_OR_DOMAIN_VALUE, int PORT)
{
try
{
if (TYPE.Equals("ip"))
{
//TCP 입력된 주소, 포트번호로부터 Endpoint 지정.
serverAddress_TCP = new IPEndPoint(IPAddress.Parse(IP_OR_DOMAIN_VALUE), PORT);
//UDP 접속주소.
serverAddress_UDP = new IPEndPoint(IPAddress.Parse(IP_OR_DOMAIN_VALUE), PORT);
}
else if (TYPE.Equals("domain"))
{
//TCP 도메인 명으로 입력된 경우, DNS를 통해 IP주소로 파싱후 적용!
serverAddress_TCP = new IPEndPoint(Dns.GetHostEntry(IP_OR_DOMAIN_VALUE).AddressList[0], PORT);
//UDP 접속주소.
serverAddress_UDP = new IPEndPoint(Dns.GetHostEntry(IP_OR_DOMAIN_VALUE).AddressList[0], PORT);
}
else
{
Debug.Log("Error: connect()의 첫번째 인자의 타입을 ip 또는 domain중 하나만 입력하십시오.");
//잘못 입력하였으므로 종료.
return;
}
//소켓의 Connect 와 같은 부분. 비동기로 이루어지며 작업이 완료되면 tcpChannel 를 통해, 서버와 통신이 가능하다!
tcpChannel = await tcpBootstrap.ConnectAsync(serverAddress_TCP);
}
catch (Exception e)
{
Debug.Log("연결 시도중 에러! Error:" + e.ToString());
}
}
//UDP 접속 함수. 연결할 서버의 UDP 리스닝 포트번호를 입력함.
public static void initUDPConnection(int setPort=-1)
{
try
{
//클라이언트의 UDP포트 바인딩 작업. (비동기로 실행됨)
Task<IChannel> bindingTask = udpBootstrap.BindAsync( 0 );
//바인딩 작업이 끝났다면 실행되는 델리게이트 함수.
bindingTask.GetAwaiter().OnCompleted(() =>
{
try
{
//바인딩된 udpChannel를 결과로 받아옴.
udpChannel = bindingTask.Result;
//지정된 포트가 존재한다면 해당 포트로 연결 시도! 지정되있지 않다면 ConnectGameServer 에서 지정된 주소로 연결함.
if (setPort != -1)
serverAddress_UDP.Port = setPort;
//서버의 UDP포트로 연결할 작업을 진행.
//(이 과정이 완료되면 위의 바인딩때 지정된 0번포트에서, 운영체제에서 자동으로 할당된 포트로 UDP채널이 최종 바인딩 되고, 송수신 가능한 상태가 된다)
Task connectTask = udpChannel.ConnectAsync(serverAddress_UDP);
//연결 작업이 완료되었다면.
connectTask.GetAwaiter().OnCompleted(() =>
{
//완료된 ip주소, port값 출력.
Debug.Log("initUDPConnection Complete [" + serverAddress_UDP.Address.ToString()+":"+serverAddress_UDP.Port+"]");
});
}
catch (Exception e)
{
Debug.Log("UDP Connect Error:" + e.ToString());
}
});
}
catch (Exception e)
{
Debug.Log("UDP Bind Error:" + e.ToString());
}
}
//TCP 송신.
public static void SendByte(byte[] send_data)
{
//tcpChannel 의 상태를 점검하여, 송신할 상황이 아니라면 종료.
if (tcpChannel == null || tcpChannel.Active != true || tcpChannel.IsWritable != true)
{
return;
}
try
{
//IByteBuffer 는, 자바 Netty 의 ByteBuf 와 같은 역할.
//보낼 데이터릐 길이만큼, alloc 에서 DirectBuffer를 할당받아서 가져옴.
IByteBuffer data = alloc.DirectBuffer(send_data.Length);
//받아온 byte[] 데이터를, 할당받아온 IByteBuffer에 기록함.
data.WriteBytes(send_data);
//tcpChannel 를 통해 해당 데이터를 송신!
tcpChannel.WriteAndFlushAsync(data);
}
catch (Exception e)
{
Debug.Log("SendByte() Error:" + e.ToString());
}
}
//UDP 송신.
public static void udpSendByte(byte[] send_data)
{
//udpChannel 의 상태를 점검하여, 송신할 상황이 아니라면 종료.
if (udpChannel == null || udpChannel.Active != true || udpChannel.IsWritable != true)
{
return;
}
try
{
//IByteBuffer 는, 자바 Netty 의 ByteBuf 와 같은 역할.
//보낼 데이터릐 길이만큼, alloc 에서 DirectBuffer를 할당받아서 가져옴.
IByteBuffer data = alloc.DirectBuffer(send_data.Length);
//받아온 byte[] 데이터를, 할당받아온 IByteBuffer에 기록함.
data.WriteBytes(send_data);
//DotNetty에서 제공하는 DatagramPacket 객체를 꾸밈.
//DatagramPacket 생성자 =>> new DatagramPacket(보낼 데이터(IByteBuffer), 보낸곳의 로컬 주소정보(udpChannel.LocalAddress), 보낼 목적지 주소정보(udpChannel.RemoteAddress));
DatagramPacket udp_data = new DatagramPacket(data, udpChannel.LocalAddress, udpChannel.RemoteAddress);
//udpChannel 를 통해 해당 Datagram 데이터를 송신!
udpChannel.WriteAndFlushAsync(udp_data);
}
catch (Exception e)
{
Debug.Log("udpSendByte() Error:" + e.ToString());
}
}
//Unity_DotNetty 프레임워크 작업용 스레드.
void Unity_DotNettyClient()
{
Debug.Log("NetworkManager:Init...");
//이벤트 루프 그룹 설정. Java Netty프레임워크의 이벤트 루프 그룹.
IEventLoopGroup tcp_workerGroup;
IEventLoopGroup udp_workerGroup;
//Client 용도이므로 각각 싱글스레드 할당!
tcp_workerGroup = new MultithreadEventLoopGroup(1); //안의 숫자만큼 스레드가 생성된다. 여러개로 해도 된다.
udp_workerGroup = new MultithreadEventLoopGroup(1); //서버로 사용할시 공백으로 하면 CPU 코어수 * 2 만큼의 스레드가 자동 할당됨.
//네트워크 이벤트 처리용 부트스트랩.
tcpBootstrap = new Bootstrap();
udpBootstrap = new Bootstrap();
//빌더 패턴을 통하여 세팅함.
try
{
//TCP 세팅.
tcpBootstrap
.Group(tcp_workerGroup) //처리용 MultithreadEventLoopGroup 지정.
.Channel<TcpSocketChannel>() //TCP 소켓채널 지정.
.Option(ChannelOption.Allocator, PooledByteBufferAllocator.Default) //IByteBuffer Pooling 객체이다. 메모리 절약용.
.Option(ChannelOption.TcpNodelay, true) //빠른 반응속도를 위한 TCP 네이글 알고리즘 해제용.
.Handler(new LoggingHandler()) //로깅용 핸들러.
//실제로 수신처리를 위한 핸들러. 먼저 ByteSizeFilterHandler를 통해, 뭉쳐서 오거나 잘려서 오는 TCP 패킷의 길이를 가공하는 역할.
.Handler(new ActionChannelInitializer<TcpSocketChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
//먼저 ByteSizeFilterHandler 에서 데이터를 받아서 처리후, TCP_InBoundHandler로 데이터를 넘겨줍니다.
pipeline.AddLast("ByteSizeFilterHandler", new ByteSizeFilterHandler()); //패킷 길이 가공.
pipeline.AddLast("TCP_InBoundHandler", new TCP_InBoundHandler()); //실제 TCP패킷 처리.
}));
Debug.Log("NetworkManager:DotNetty TCP_Init...Complete");
//UDP 세팅.
udpBootstrap
.Group(udp_workerGroup) //처리용 MultithreadEventLoopGroup 지정.
.Channel<SocketDatagramChannel>() //UDP 소켓채널 지정.
.Option(ChannelOption.Allocator, PooledByteBufferAllocator.Default) //IByteBuffer Pooling 객체이다. 메모리 절약용.
//실제로 수신처리를 위한 핸들러. UDP_InBoundHandler를 통해 UDP 패킷을 처리한다.
.Handler(new ActionChannelInitializer<SocketDatagramChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
pipeline.AddLast("UDP_InBoundHandler", new UDP_InBoundHandler()); //실제 UDP패킷 처리.
}));
Debug.Log("NetworkManager:DotNetty UDP_Init...Complete");
Debug.Log("NetworkManager:DotNetty Initialize Complete");
//블로킹 부분.
while (true)
{
Thread.Sleep(3600);
}
}
catch (Exception e)
{
Debug.Log(e);
}
finally
{
//최종 종료될 경우, 이벤트 루프 그룹의 자원들을 해제한다.
tcp_workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));
udp_workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));
Debug.Log("NetworkManager:DotNetty ShutdownGracefully Complete.");
}
} //비동기 스레드 작업 끝.
} //NetworkManager 클래스 종료 부분.
//서브스레드, 메인스레드간 통신용 Queue 클래스.
public class Packet_Data
{
public byte[] packet_data;
//공백 생성자.
public Packet_Data() {}
//byte[] 생성자.
public Packet_Data(byte[] packet_data)
{
this.packet_data = packet_data;
}
}

jsj...@gmail.com

unread,
Dec 7, 2020, 2:56:57 AM12/7/20
to Netty Korean User Group
NETTY 기본 구조를 거의 그대로 가져왔기 때문에,  NETTY 책을 보시면 사용방법을 이해하실 수 있습니다. 
그러나 클래스명이나 메서드명은 다른 부분이 있기 때문에 JAVA NETTY로 구조만 이해하시고, 샘플코드 보면서
개발하시면 충분히 가능하실 거라 생각되네요


2020년 1월 14일 화요일 오후 10시 20분 13초 UTC+9, SOOH LEE 님의 말:
Reply all
Reply to author
Forward
0 new messages