하아찡

[C#/WPF] Upbit프로젝트 MyTradeClientWebSocket 본문

C#/코인프로그램 - 코드

[C#/WPF] Upbit프로젝트 MyTradeClientWebSocket

하아찡 2023. 11. 28. 16:08

블로그 글 작성시 Websocket부분에 Public타입과 Private타입이 생긴걸 확인해서 기존에 작업된 Websocket은 Public타입이여서 헤더에 인증데이터를 전달 할 필요가없어가지고 Websocket으로 작업했는데 Private타입은 헤더이 인증데이터를 포함해야되가지고 ClientWebscoket을 사용하는 방식으로 작업했습니다.

 

결과확인(정상작동됨)

Received message: {"ty":"myTrade","cd":"KRW-ONG","ab":"BID","p":624,"v":14.10897435,"ouid":"d3cd91a6-f1cf-4706-8798-d333c29b899f","ot":"price","tuid":"7e94c668-fdd2-4934-8b0b-79f2e308a8e8","ttms":1701154120723,"st":"REALTIME"}
Received message: {"ty":"myTrade","cd":"KRW-ONG","ab":"ASK","p":623,"v":14.10897435,"ouid":"b9d99eb8-5ea2-42d3-afb1-aa47ae2895a0","ot":"market","tuid":"3720e907-d0c5-4e50-9d72-0410d258dab9","ttms":1701154125670,"st":"REALTIME"}

 

 

MyTradeClientWebSocket.cs

using Newtonsoft.Json.Linq;
using System;
using System.Diagnostics;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using Upbit.Functions;
using Upbit.UpbitFunctions.Interface;

namespace Upbit.UpbitFunctions
{
    public class MyTradeClientWebSocket: IAccess
    {
        #region Event
        public delegate void MyTradeEventHandler(Structs.MyTrade connect);
        public static event MyTradeEventHandler MyTradeEvent;
        #endregion Event

        #region Model
        private ClientWebSocket cws;
        private URLs url = new URLs();
        Thread thread;
        private static MyTradeClientWebSocket instance;
        public static MyTradeClientWebSocket Instance
        {
            get
            {
                if (instance is null) { instance = new MyTradeClientWebSocket(); }
                return instance;
            }
        }
        #endregion Model


        #region Functions
        /// <summary>
        /// 소켓 생성 및 실행
        /// </summary>
        /// <returns></returns>
        public async Task SetWebSocket()
        {
            //인증이 안됐을경우
            if (!Access.UpbitInstance.GetAccess())
            {
                return;
            }

            Uri serverUri = new Uri(url.WebSocketTick);
            string AuthToken = JWT.GetJWT();
            JObject ticket = new JObject();
            JObject mytrade = new JObject();
            JObject format = new JObject();
            string Message = "";
            ticket["ticket"] = Guid.NewGuid().ToString();//UUID
            mytrade["type"] = "myTrade";
            format["format"] = "SIMPLE";
            Message = string.Format("[{0},{1},{2}]", ticket.ToString(), mytrade.ToString(), format.ToString());

            try
            {
                cws = new ClientWebSocket();
                cws.Options.SetRequestHeader("Authorization", AuthToken);
                await cws.ConnectAsync(serverUri, CancellationToken.None);


                await SendMessage(cws, Message);

                while (cws.State == System.Net.WebSockets.WebSocketState.Open)
                {
                    await ReceiveMessage(cws);
                }
            }
            catch (Exception ex) 
            {

            }


        }

        private void CloseWebsocket(object sender, WebSocketCloseStatus e)
        {
            Console.WriteLine($"WebSocket closed with status: {e}");
        }
        /// <summary>
        /// 전달받은 데이터 처리
        /// </summary>
        /// <param name="webSocket"></param>
        /// <returns></returns>
        private async Task ReceiveMessage(ClientWebSocket webSocket)
        {

            var buffer = new ArraySegment<byte>(new byte[1024]);
            var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
            if (result.MessageType == WebSocketMessageType.Binary)
            {
                string Message = System.Text.Encoding.UTF8.GetString(buffer.Array, 0, result.Count);
                JObject jsonObject = JObject.Parse(Message);
                Structs.MyTrade obj = new Structs.MyTrade();
                obj.ty = (string)jsonObject.GetValue("ty");         //myTrade
                obj.cd = (string)jsonObject.GetValue("cd");         //마켓코드
                obj.ab = (string)jsonObject.GetValue("ab");         //매수 매도 구분
                obj.p = (double)jsonObject.GetValue("p");           //체결가격
                obj.v = (double)jsonObject.GetValue("v");           //체결량
                obj.ouid = (string)jsonObject.GetValue("ouid");     //주문의 고유 아이디
                obj.ot = (string)jsonObject.GetValue("ot");         //주문타입
                obj.ab = (string)jsonObject.GetValue("ab");         //체결의 고유 아이디
                obj.tuid = (string)jsonObject.GetValue("tuid");     //체결의 고유 아이디
                obj.ttms = (long)jsonObject.GetValue("ttms");       //체결타임스탬프
                obj.st = (string)jsonObject.GetValue("st");         //스트림타입
                if (MyTradeEvent != null)
                    MyTradeEvent(obj);

                //기능만 추가해둬가지고 체결이벤트 떨어지는 코드는 아래에 작업하면됨.
                Debug.WriteLine($"Received message: {Message}");
            }
        }

        private async Task SendMessage(ClientWebSocket webSocket, string message)
        {
            byte[] messageBytes = System.Text.Encoding.UTF8.GetBytes(message);
            await webSocket.SendAsync(new ArraySegment<byte>(messageBytes), WebSocketMessageType.Text, true, CancellationToken.None);
        }

        /// <summary>
        /// 쓰레드를 생성해서 따로 처리함.
        /// </summary>
        public void SocketStart()
        {
            thread = new Thread(async () => await SetWebSocket());
            thread.Start();
        }

        public void D_AccessEvent(bool b)
        {
            if (b)
            {
                //다시 인증완료됐을때
                thread = new Thread(async () => await SetWebSocket());
                thread.Start();
            }
        }

        #endregion Functions


        public MyTradeClientWebSocket()
        {
            Access.CheckedAccess += (D_AccessEvent);
        }
    }
}

현재는 급하게 코드를 작업해서 부족한 부분이 있습니다.

혹시나 처리하는 방법이 필요하신분이 있을까봐 작업해서 올렸습니다.

 

기존과는 다르게 Websocket이 아닌 ClientWebsocket을 사용했습니다. Websocket은 헤더에 인증절차를 넣는법이 없어가지고 방법을 변환하였습니다.

 

 

Private Message생성

JObject ticket = new JObject();
JObject mytrade = new JObject();
JObject format = new JObject();
string Message = "";
ticket["ticket"] = Guid.NewGuid().ToString();//UUID
mytrade["type"] = "myTrade";
format["format"] = "SIMPLE";
Message = string.Format("[{0},{1},{2}]", ticket.ToString(), mytrade.ToString(), format.ToString());

기존  Websocket에서 사용했던 코드 그대로 사용가능하여 가져와서 사용했습니다.

 

 

JWT생성 및 소켓연결

string AuthToken = JWT.GetJWT();
try
{
    cws = new ClientWebSocket();
    cws.Options.SetRequestHeader("Authorization", AuthToken);
    await cws.ConnectAsync(serverUri, CancellationToken.None);


    await SendMessage(cws, Message);

    while (cws.State == System.Net.WebSockets.WebSocketState.Open)
    {
        await ReceiveMessage(cws);
    }
}
catch (Exception ex) 
{

}

해당 코드가 인증데이터를 헤더에 넣는부분입니다.

cws.Options.SetRequestHeader("Authorization", AuthToken); 

 

위에서 생성된 Message를 소켓을동해 서버에 전송합니다.

await SendMessage(cws, Message);

이과정에서 성공했을경우 정상적으로 내 코인이 체결됐을때 실시간으로 체결데이터를 받아올 수 있습니다.

 

정상적으로 성공하여 소켓 연결상태를 계속 유지하여 서버에서 오는 데이터를 계속 받는상태로 유지합니다.

while (cws.State == System.Net.WebSockets.WebSocketState.Open)
{
    await ReceiveMessage(cws);
}

 

 

ReceiveMessage

private async Task ReceiveMessage(ClientWebSocket webSocket)
{

    var buffer = new ArraySegment<byte>(new byte[1024]);
    var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
    if (result.MessageType == WebSocketMessageType.Binary)
    {
        string Message = System.Text.Encoding.UTF8.GetString(buffer.Array, 0, result.Count);
        JObject jsonObject = JObject.Parse(Message);
        Structs.MyTrade obj = new Structs.MyTrade();
        obj.ty = (string)jsonObject.GetValue("ty");         //myTrade
        obj.cd = (string)jsonObject.GetValue("cd");         //마켓코드
        obj.ab = (string)jsonObject.GetValue("ab");         //매수 매도 구분
        obj.p = (double)jsonObject.GetValue("p");           //체결가격
        obj.v = (double)jsonObject.GetValue("v");           //체결량
        obj.ouid = (string)jsonObject.GetValue("ouid");     //주문의 고유 아이디
        obj.ot = (string)jsonObject.GetValue("ot");         //주문타입
        obj.ab = (string)jsonObject.GetValue("ab");         //체결의 고유 아이디
        obj.tuid = (string)jsonObject.GetValue("tuid");     //체결의 고유 아이디
        obj.ttms = (long)jsonObject.GetValue("ttms");       //체결타임스탬프
        obj.st = (string)jsonObject.GetValue("st");         //스트림타입
        if (MyTradeEvent != null)
            MyTradeEvent(obj);

        //기능만 추가해둬가지고 체결이벤트 떨어지는 코드는 아래에 작업하면됨.
        Debug.WriteLine($"Received message: {Message}");
    }
}

 

 

 

 

GetJWT

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Text;
using Upbit.UpbitFunctions;

namespace Upbit.Functions
{
    public class JWT
    {
        public static string GetJWT(string parameter = "")
        {
            string queryHash = "";

            //파라미터 존재할경우
            if (parameter != "")
            {
                Dictionary<string, string> parameters = new Dictionary<string, string>();

                foreach (string value in parameter.Split(','))
                {
                    parameters.Add(value.Split(':')[0], value.Split(':')[1]);
                }

                StringBuilder builder = new StringBuilder();
                foreach (KeyValuePair<string, string> pair in parameters)
                {
                    builder.Append(pair.Key).Append("=").Append(pair.Value).Append("&");
                }
                string queryString = builder.ToString().TrimEnd('&');

                SHA512 sha512 = SHA512.Create();
                byte[] queryHashByteArray = sha512.ComputeHash(Encoding.UTF8.GetBytes(queryString));
                queryHash = BitConverter.ToString(queryHashByteArray).Replace("-", "").ToLower();
            }

            var payload = new JwtPayload
            {
                { "access_key", Access.UpbitInstance.GetAccessKey() },
                { "nonce", Guid.NewGuid().ToString() },
                { "query_hash", queryHash },
                { "query_hash_alg", "SHA512" }
            };

            byte[] keyBytes = Encoding.Default.GetBytes(Access.UpbitInstance.GetSecretKey());
            var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(keyBytes);
            var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, "HS256");
            var header = new JwtHeader(credentials);
            var secToken = new JwtSecurityToken(header, payload);

            var jwtToken = new JwtSecurityTokenHandler().WriteToken(secToken);
            var authorizationToken = "Bearer " + jwtToken;

            return authorizationToken;
        }
    }
}

위 코드는 UpbitAPI에서 제공하는 JWT인증토큰 만드는 코드입니다.

 

https://docs.upbit.com/docs/create-authorization-request

 

Open API | 업비트 개발자 센터

 

docs.upbit.com

 

 

기존에 Websocket에서 사용하던 코드와 매우 비슷합니다. 다만 차이점이 있다면 Websocket을 사용했냐

ClientWebsocket을 사용했냐 차이가 있기때문에 약간의 코드가 다른점이 있으니 헷갈리지 마시고 사용해주세요.

반응형