반응형

Unity 에서 Redis 서버를 사용해보자

 

Unity 버전 2019.4.15f1

 

NuGet의 StackExchange.Redis를 사용했다.

 


 설치&세팅

 

visual studio의 NuGet에서 Plugin을 다운받고 사용하면 사용이 안된다.

사용이 안되는 이유는 두가지로 

 

1. 유니티 내부에 있어야한다.

2. 유니티에서 호완하는 버전이 아니다

 

 

 

 

1. 누겟 페키지를 다운받았으면 다운받은 파일에 들어가서 

 

 

 

 

 

dll 파일과 xml 파일을 

 

unity Assets 파일에 Plugin 파일을 만들어서 넣어준다

 

 

 

 

 

 

 

 

 

 

 

2. 유니티 2019버전에서는 Redis 2버전대가 호환이 안된다

그럼으로 1.5버전을 다운받아서 넣어주면 된다.

 

 


사용법

 

 

그뒤로 unity 내부에서 사용하는 방법은 매우간단하다.

    private ConnectionMultiplexer redisConnection;
    private IDatabase DB;
    private ISubscriber sub;

3가지를 선언해 준다.

 

 첫줄은 StackExchange.Redis의 메인 객체다

 두번째 줄은 DB 데이터 를 주고받을 객체다.

 마지막줄 sub역시 데이터를 전달하기위해 있는객체다.

 

백문이 불여일견 코드로 보겠다.

Init Code

 public bool Init(string host, int port)
    {

        this.redisConnection = ConnectionMultiplexer.Connect(host + ":" + port);
        if (redisConnection.IsConnected)
        {
            this.DB = this.redisConnection.GetDatabase();
            sub = redisConnection.GetSubscriber();
            return true;
        }

        return false;
    }

    public bool Init(string host, int port, Events LoopEvents)
    {

        T = new Thread(new ThreadStart(LoopEvents));
        
        try
        {
            ConfigurationOptions option = new ConfigurationOptions
            {
                ConnectTimeout = 1000,
                AbortOnConnectFail = false,
                EndPoints = { $"{host}:{port}" },
            };
            redisConnection = ConnectionMultiplexer.Connect(option);
        }
        catch (Exception e)
        {
            Debug.Log(e);
        }

        if (redisConnection.IsConnected)
        {
            this.DB = this.redisConnection.GetDatabase();
            sub = redisConnection.GetSubscriber();
            T.Start();

            return true;
        }

        return false;
    }

 

위 코드의

        this.redisConnection = ConnectionMultiplexer.Connect(host + ":" + port);  줄에서 알수있듯

       "ip주소:포트넘버: 로 이루어져 있다. 

 

로컬로 테스트 할때는 아래 코드처럼 "localhost" 를 넣어서 사용하면된다.

this.redisConnection = ConnectionMultiplexer.Connect("localhost")

 

 그냥 주소로 connect를 시도 해도 좋지만

 

   ConfigurationOptions option = new ConfigurationOptions
            {
                ConnectTimeout = 1000,
                AbortOnConnectFail = false,
                EndPoints = { $"{host}:{port}" },
            };
            redisConnection = ConnectionMultiplexer.Connect(option);

이 부분처럼 ConfigurationOptions 를 사용하여 전달해도 좋다.

 그러면 구성 옵션을 세팅을 한번에 해줄수도 있다.

   

 

      this.redisConnection = ConnectionMultiplexer.Connect(host + ":" + port);

           ㄴ주소로 연결


        if (redisConnection.IsConnected) //연결 완료되었는가
        { 

       //연결 완료후 데이터들을 받는다.

            this.DB = this.redisConnection.GetDatabase();

            sub = redisConnection.GetSubscriber();

        }

 

 

 

 

이제 정상적으로 넘어간다면 다 끝났다. 

데이터를 주고받는 방법은 간단하다.

 

 

 

 

 

 

 

 

 

 

 

아까 선언해주었던 DB 객체의 내부에 전부 들어있다. 

아래 코드처럼 자신의 프로젝트에서 사용하기 편하게 만들어주면 끝이다.

 

   public string GetString(string key)
    {
        return this.DB.StringGet(key);
    }
    public bool SetString(string key, string val)
    {
        return this.DB.StringSet(key, val);
    }
    public RedisValue GetHASH(string key, string val)
    {
        return this.DB.HashGet(key, val);
    }
    public bool GetHASH_Bool(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        if (data.ToString()[0] == 't' || data.ToString()[0] == 'T')
        {
            return true;
        }
        return false;
    }
    public int GetHASH_Int(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        int Idata = 0;
        if (data.HasValue == true)
        {
            int DataSize = data.ToString().Length - 1;
            int sizenum = (int)Math.Pow(10, DataSize);
            foreach (var d in data.ToString())
            {
                if ('0' <= d && d <= '9')
                {
                    Idata += ((d - 48) * sizenum);
                    sizenum /= 10;
                }
                else
                {
                    return -int.MaxValue;
                }
            }
        }
        return Idata;
    }
    public double GetHASH_Double(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        if (data.HasValue == true)
        {
            double Idata = Double.Parse(data);
            return Idata;
        }
        return -Double.MaxValue;
    }
    public void SetHASH(string key, HashEntry[] val)
    {
        this.DB.HashSet(key, val);
    }
    public void DestroyHash(string key, RedisValue field)
    {
        this.DB.HashDelete(key, field);
    }

또한 Redis 서버에 메시지를 따로보내는 방법이 있는데 

그게 아까 보여주었던 sub 로 가능하다

    public void Meseage(string Channel, string Meseage)
    {
        sub.Publish(Channel, Meseage);
    }

특정 채널에 메시지를 보낼수있다.

또한 채널의 메시지를 구독하여 올라올때마다 확인받을수 있다.

 

    public void Setsubscrib(string _channel, CallBack_Funtc _CallBack_Funtc)
    {
        if (redisConnection.IsConnected)
        {
            sub.Subscribe(_channel, (channel, message) =>
            {
                try
                {
                    _CallBack_Funtc?.Invoke(channel+" "+message);
                    Debug.Log(message);
                }
                catch
                {

                }
            });
        }
    }

 

 

 

 

 

 

위에서 만들었던 클래스 사용

  private string Key;
    public string RedisIP_Num;
    public int PortNum;
    public bool GetDataLoop;
    public int ThreadDelay;

    private void Start()
    {
     // 따로 XMl로 받아오는 데이터들
        Key = Datas.data.DataKey;
        RedisIP_Num = Datas.data.RedisIP_Num;
        PortNum = Datas.data.PortNum;
        GetDataLoop = Datas.data.GetDataLoop;
        ThreadDelay = Datas.data.ThreadDelay;

        redis = new Redis();
        if (redis.Init(RedisIP_Num, PortNum, delegate
         {
             while (GetDataLoop)
             {
                 GEtD(); // redis.GetHASH_Bool(Key, "KeyData") 데이터들을 받아오는 함수
                 Thread.Sleep(ThreadDelay);

             }
         }))
        {
            Debug.Log("connect");
        }
        else
        {
            Debug.Log("Non_connect");
        }

 GEtD()함수 내부 코드는 이렇게 데이터를 받는다.

 

또한 Init 의 델리게이트에 데이터를 받아오는 함수와 스레드.Sleep(변수)

를 이용하여 연결이 완료되었을때 데이터를 주기적으로 받아오게 만들었다.

 

 

 

전체코드

더보기

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using StackExchange.Redis;
using System.Threading;
using System;
using System.Threading.Tasks;

namespace TStory
{
    static public class Global
    {
        public static bool Dnet_Connection;
        public static bool Dnet_readwrite;
        public static bool Dnet_error;
        public static double server_cache_alive;
        public static int client_connection_count;

    }
}

public class RDS : MonoBehaviour
{
    private Redis redis;
    public RedisValue redisValue;

    private string Key;
    public string RedisIP_Num;
    public int PortNum;
    public bool GetDataLoop;
    public int ThreadDelay;

    private void Start()
    {
        Key = Datas.data.DataKey;
        RedisIP_Num = Datas.data.RedisIP_Num;
        PortNum = Datas.data.PortNum;
        GetDataLoop = Datas.data.GetDataLoop;
        ThreadDelay = Datas.data.ThreadDelay;

        redis = new Redis();
        if (redis.Init(RedisIP_Num, PortNum, delegate
        {
            while (GetDataLoop)
            {
                GEtD();
                Thread.Sleep(ThreadDelay);

            }
        }))
        {
            Debug.Log("connect");
        }
        else
        {
            Debug.Log("Non_connect");
        }


    }

    private void OnApplicationQuit()
    {
        GetDataLoop = false;
        //redis.T.Join();
    }

    public void GEtD()
    {
        TStory.Global.Dnet_Connection = (redis.GetHASH_Bool(Key, "TStory-connection"));
        TStory.Global.Dnet_readwrite = (redis.GetHASH_Bool(Key, "TStory-readwrite"));
        TStory.Global.Dnet_error = (redis.GetHASH_Bool(Key, "device-net-error"));
        TStory.Global.server_cache_alive = (redis.GetHASH_Double(Key, "server-cache-alive"));
        TStory.Global.client_connection_count = (redis.GetHASH_Int(Key, "client-connection-count"));

    }

}

public class Redis
{
    private ConnectionMultiplexer redisConnection;
    private IDatabase DB;
    private ISubscriber sub;
    public delegate void Events();
    public Thread T;
    public bool Init(string host, int port)
    {

        this.redisConnection = ConnectionMultiplexer.Connect(host + ":" + port);
        if (redisConnection.IsConnected)
        {
            this.DB = this.redisConnection.GetDatabase();
            sub = redisConnection.GetSubscriber();
            return true;
        }

        return false;
    }

    public bool Init(string host, int port, Events LoopEvents)
    {


        T = new Thread(new ThreadStart(LoopEvents));
        try
        {
            ConfigurationOptions option = new ConfigurationOptions
            {
                ConnectTimeout = 1000,
                AbortOnConnectFail = false,
                EndPoints = { $"{host}:{port}" },
            };
            redisConnection = ConnectionMultiplexer.Connect(option);
        }
        catch (Exception e)
        {
            Debug.Log(e);
        }

        if (redisConnection.IsConnected)
        {
            this.DB = this.redisConnection.GetDatabase();
            sub = redisConnection.GetSubscriber();
            T.Start();

            return true;
        }

        return false;
    }
    public string GetString(string key)
    {
        return this.DB.StringGet(key);
    }
    public bool SetString(string key, string val)
    {
        return this.DB.StringSet(key, val);
    }
    public RedisValue GetHASH(string key, string val)
    {
        return this.DB.HashGet(key, val);
    }
    public bool GetHASH_Bool(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        if (data.ToString()[0] == 't' || data.ToString()[0] == 'T')
        {
            return true;
        }
        return false;
    }
    public int GetHASH_Int(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        int Idata = 0;
        if (data.HasValue == true)
        {
            int DataSize = data.ToString().Length - 1;
            int sizenum = (int)Math.Pow(10, DataSize);
            foreach (var d in data.ToString())
            {
                if ('0' <= d && d <= '9')
                {
                    Idata += ((d - 48) * sizenum);
                    sizenum /= 10;
                }
                else
                {
                    return -int.MaxValue;
                }
            }
        }
        return Idata;
    }
    public double GetHASH_Double(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        if (data.HasValue == true)
        {
            double Idata = Double.Parse(data);
            return Idata;
        }
        return -Double.MaxValue;
    }
    public void SetHASH(string key, HashEntry[] val)
    {
        this.DB.HashSet(key, val);
    }
    public void DestroyHash(string key, RedisValue field)
    {
        this.DB.HashDelete(key, field);
    }
    public void Meseage(string Channel, string Meseage)
    {
        sub.Publish(Channel, Meseage);
    }

}

 

'엔진 > 유니티' 카테고리의 다른 글

Unity Spline 기능 추가!  (1) 2023.06.01
Unity Simulator  (0) 2023.01.20
Unity ObjectPool Api  (0) 2022.11.02
unity _Prefab Variants  (0) 2022.03.17
Unity Inspector 창 정리  (2) 2022.02.24
반응형

n Nulnud-coalescing operator  

   NULL 병합 연산자

   ??  , ??=

 

특징  : 오른쪽 결합

 

 

null 병합 연산자 라고 부르는 ?? 연산자는

null이 아닌 경우 왼쪽 값을 반환하고 null인 경우 오른쪽 값을 확인 하고 반환하는 연산자다.

만약 왼쪽값이 null 이 아닌경우  오른쪽 값을 확인하지 않는다.

 

 

            int? a = 17;
            int b = 15;
            int? c = null;
            
            Console.WriteLine(a ?? b); // Output :17,  왼쪽값이 null이 아니기때문에 17.
            Console.WriteLine(c ?? b); //Output :15,   왼쪽(null)->오른쪽 값을 확인해 본뒤 b반환
            Console.WriteLine(c ?? c); //Output : ,    둘다 null 이기 때문에 null
            //Console.WriteLine(b ?? c); Err ?? 연산자는 int 및 int? 형식의 피연산자에 적용할수 없다

int? 가 뭔지 모르겠다면 이 블로그의 Nullable 을 보고오는게 좋다.

https://programing-note.tistory.com/entry/c-Nullabe-%EA%B0%92-%ED%98%95%EC%8B%9D

 

 

c#8.0 이상에서 사용할수 있는null 병합 할당 연산자(??=)는  null 병합 연산자 와 비슷하다. 

  왼쪽 값이 null인경우 우측 값을 할당시켜준다. 물론 좌측값이 null이 아닌경우 우측값을 확인하지 않는다.

            int? a = null;
            int b = 15;
            a ??= b; 좌측값이 null 이기때문에 우측b 값을 확인후 a값에 대입한다.
            Console.WriteLine(a); // 15

 이 연산자는 가끔 유용하게 쓰일수있다.

 

 

예시로는 null인지 확인하는 구문을 간단하게 만들수있다.

            if (isNull is null)
            {
                isNull = 10;
            }

이러한 연산을

            isNull ??= 10;

이렇게 간단하게 쓸수 있다.

 

 

 

Null 조건부 연산자 ?. 및 ?[]

C# 6이상부터 사용할수 있는 연산자다.

이는 Null이 아닌것으로 평가되었을 때만 멤버 엑세스 ?. ,또는 요소 엑세스?[], 연산을 피연산자 에게 적용하고

 Null 인것으로 평가되었을경우 Null을 반환한다.

 

  조건부 멤버나 요소 액세스 작업에서 null 을 반환하면 나머지 체인은 실행되지 않는다.

 

A?.B?.c

위 와 같을때 A가 Null일때 B는 평가되지 않는다.

이와 같이 A또는 B가 Null일경우 C는 평가되지 않는다.

 

아래 윈도우 예제 코드를 보면 쉽게 이해할수 있다.

            
            double SumNumbers(List<double[]> setsOfNumbers, int indexOfSetToSum)
            {
                return setsOfNumbers?[indexOfSetToSum]?.Sum() ?? double.NaN;
            }

            var sum1 = SumNumbers(null, 0);
            Console.WriteLine(sum1);  // output: NaN
            var numberSets = new List<double[]>
                 {
                     new[] { 1.0, 2.0, 3.0 },
                     null
                  };

            var sum2 = SumNumbers(numberSets, 0);
            Console.WriteLine(sum2);  // output: 6

            var sum3 = SumNumbers(numberSets, 1);
            Console.WriteLine(sum3);  // output: NaN
            
                //var sum4 = SumNumbers(numberSets, 2); 예외발생

위 코드처럼 코드를 짤때 Null 이 들어가 오류가 생기는 상황을 간단하게 처리할수있다.

 

 

주의 할점

a.x 또는 a[x] 가 예외를 던지면 a?.x 또는 a?[x] 는 null이 아닌 예외를 던진다.

위의 예제 마지막줄 처럼 x가 a의 배열의 크기 보다 큰경우 a?[x] 는 IndexOutOFRangeExcption 예외를 던진다.

 

참고

?. 연산자는 왼쪽 연산자를 한번만 계산하고 null이 아닌것을 확인한 후에는 null로 변경할수 없도록 보장한다.

 

또한 스레드를 사용할때도 안전하게 호출할수 있다.

스레드를 호출할때 null인지 확인하고 호출하는 것을

var handler = this.PropertyChanged;
if (handler != null)
{
    handler(…);
}
 
 를 아래처럼 변경할수있다

PropertyChanged?.Invoke(…)

위 상황처럼 간단하게  사용할수있다

 

참고 링크 :docs.microsoft.com;

'언어 > C#' 카테고리의 다른 글

c# Nullable값 형식  (0) 2022.03.24
c# Tuple  (0) 2022.03.23
C# Ref,Out 키워드 와 in 키워드  (0) 2022.03.16
반응형

 

Ref 키워드와 Out 키워드를 더 잘 이해하기 위해서는 call by refurence , call by value를 알고 보는 게 좋다.

Ref , Out 키워드

정의부터 설명하자면

 Ref Pass by Reference

   ㄴ 얕은 복사 매개변수 지정자

Out Output Parameters

   ㄴ 출력용 매개변수 - 내부에서 값을 할당해주어야 한다.

 

 

사용 방법

 ref out 사용법

 

함수 선언 시

 리턴 값 함수명 (ref & out키워드 매개변수 타입  변수 이름 ) <-함수를 선언 또는 구현 시에도 매개변수 앞에 써줘야 한다.

함수 사용 시

 함수명( ref & out 키워드 변수 ) <- 사용할 때에도 매개변수 앞에다 ref 키워드를 써줘야 한다.

    public void FuncRefB(ref int A) 
    {
        A++;
    }

    public void Func( int A)
    {
        A++;
    }

    public void FuncOutB(out int A)
    {
        A = 10; // 내부에서 값을 대입 하지 않으면 오류가 나온다.
    }
   
       ...
       {
        int A;
        
        FuncOutB(out A);
          Console.WriteLine(A); // 내부에서 생성해서 받는다 Call by Refurence + @  10
        Func(A);
          Console.WriteLine(A); // 늘어나지 않는다 Call by Value   10
        FuncRefB(ref A);
          Console.WriteLine(A); // 값이 늘어난다. Call by Refurence  11
        }

이처럼 c나 c++처럼 포인터 참조형처럼 Call by Reference 형식으로 넘겨줄 수 있다.

 

(참고할 점)

1. Ref 키워드는 할당이 되어있어야 한다.

2. Out 은 Ref처럼 할당이 되어있지 않아도 사용 가능하지만. 내부에서 새로 할당한다.(이전 값은 사라짐)

3. Ref int -> int 오버로드는 가능하나 Out -> Ref , in 오버로드는 불가능하다.

 

 

구조체 같은 경우 기본적으로 call by value 형식이기 때문에 구조체를 ref 키워드로 넘겨주면 
얕은 복사(주소 값)처럼 사용할 수 있다.

 

in 키워드

in : 읽기 전용 매개변수 지정자

 

함수 선언 시

 리턴 값 함수명 ( in 키워드 매개변수 타입  변수이름 ) <-함수를 선언 또는 구현 시에도 매개변수 앞에 써줘야 한다.

함수 사용 시

 함수명(  키워드 변수 ) <-  in은 사용할 때 앞에 붙여 주지 않는다.

 

void FuncIn(in int num)
{
  //num++; 내부 수정불가능
  
}

내부에서 수정이 불가능하기 때문에 혹여나 다른 값을 입력하거나 수정하는 실수를 미연에 방지할 수 있다.

 

 

Ref 와 in 키워드는  복사 비용을 참조로 변경하여 성능 향상을 생각할수 있다.

 

 

--추가--

 

out 키워드 의경우 C#7.0 버전부터는 튜블 반환을 지원했다.

튜플 반환이 있기전에 여러값을 반환 할수 없었기 때문에 out을 주로 사용하였다.

 

(int sum, int product) Calculate(int a, int b) => (a + b, a * b);

 

 

void Calculate(int a, int b, out int sum, out int product)
{
    sum = a + b;
    product = a * b;
}

Calculate(2, 3, out int sum, out int product);

 

out 키워드는 성능이 중요한 코드나 오래된 api, 호환성 때문에 쓰인다.

 

 

 

반복적으로 많이 쓰일때 생성되는것보다 out으로 불필요한 메모리할당과 GC부하를 줄일수있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

'언어 > C#' 카테고리의 다른 글

c# ?? 연산자 (Null 병합 연산자)  (0) 2022.07.12
c# Nullable값 형식  (0) 2022.03.24
c# Tuple  (0) 2022.03.23

+ Recent posts