Unity와 Web어플리케이션 사이에서 데이터를 연동하고 싶어서 Unity에서 HTTP서버를 만들어 보았다. 그 후 Unity에서 용이한 WebAPI 를 만들어 보고 싶어서 사용에 편리한 방법을 정리해 보았다.
할일
・Unity에서 HTTP서버를 생성
・사용한 라이브러리 - System.Net.HttpListener
・멀티스레드 처리에서 부하를 줄인다.
・ Get / Post 리퀘스트를 처리
・재사용을 위한서버처리와 리퀘스트처리의 콤포넌트를 분리
・UnityEvent를 사용하여 Inspecter 에서 이벤트를 관리
・통신 테스트는 Postman을 사용
System.Net.HttpListener란?
HTTP요청에 응답하는 간단한 HTTP프로토콜 리스너를 만들 수 있다.
.NET 표준 클래스이므로 Unity에도 표준으로 사용할 수 있다.
멀티스레드 처리의 표준
Unity는 부하고 크기 때문에 안정적인 동작을 위해 System.Net.HttpListener 를 별도 스레드로 실행한다.
단, 리퀘스트 내용에따라 메인스레드에서 처리할 필요가 있다.
Unity에서 스레드를 넘은 함수의 실행은 할 수 없기 때문에
UnityMainThreadDispatcher을 사용하여 메인 스레드의 작업을 호출 할 수있습니다.
HTTP서버 콤포넌트 만들기
아래와 같은 스크립트를 작성한다.
HTTP서버의 코드
UnityHttpListener.cs
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.Events;
public class UnityHttpListener : MonoBehaviour
{
private HttpListener listener;
private Thread listenerThread;
public string domain = "localhost";
public int port = 8080;
[System.Serializable]
public class OnGetRequestEvent : UnityEvent<HttpListenerContext> { }
public OnGetRequestEvent OnGetRequest;
[System.Serializable]
public class OnPostRequestEvent : UnityEvent<HttpListenerContext> { }
public OnPostRequestEvent OnPostRequest;
void Start()
{
listener = new HttpListener();
listener.Prefixes.Add("http://" + domain + ":" + port + "/");
listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
listener.Start();
listenerThread = new Thread(startListener);
listenerThread.Start();
Debug.Log("Server Started");
}
private void OnDestroy()
{
listener.Stop();
listenerThread.Join();
}
private void startListener()
{
while (listener.IsListening)
{
var result = listener.BeginGetContext(ListenerCallback, listener);
result.AsyncWaitHandle.WaitOne();
}
}
private void ListenerCallback(IAsyncResult result)
{
if (!listener.IsListening) return;
HttpListenerContext context = listener.EndGetContext(result);
Debug.Log("Method: " + context.Request.HttpMethod);
Debug.Log("LocalUrl: " + context.Request.Url.LocalPath);
try
{
if (ProcessGetRequest(context)) return;
if (ProcessPostRequest(context)) return;
}
catch (Exception e)
{
ReturnInternalError(context.Response, e);
}
}
private bool CanAccept(HttpMethod expected, string requested)
{
return string.Equals(expected.Method, requested, StringComparison.CurrentCultureIgnoreCase);
}
private bool ProcessGetRequest(HttpListenerContext context)
{
if (!CanAccept(HttpMethod.Get, context.Request.HttpMethod) || context.Request.IsWebSocketRequest)
return false;
UnityMainThreadDispatcher.Instance().Enqueue(() => OnGetRequest.Invoke(context));
return true;
}
private bool ProcessPostRequest(HttpListenerContext context)
{
if (!CanAccept(HttpMethod.Post, context.Request.HttpMethod))
return false;
UnityMainThreadDispatcher.Instance().Enqueue(() => OnPostRequest.Invoke(context));
return true;
}
private void ReturnInternalError(HttpListenerResponse response, Exception cause)
{
Console.Error.WriteLine(cause);
response.StatusCode = (int) HttpStatusCode.InternalServerError;
response.ContentType = "text/plain";
try
{
using(var writer = new StreamWriter(response.OutputStream, Encoding.UTF8))
writer.Write(cause.ToString());
response.Close();
}
catch (Exception e)
{
Console.Error.WriteLine(e);
response.Abort();
}
}
}
Get / Post의 리퀘스트가 있다면 UnityMainThreadDispatcher 를 사용하여 메인스레드에서 여러가지의 UnityEvent를 불러낼 수 있다.
리퀘스트 처리코드
RequestHandler.cs
using System;
using System.Net;
using UnityEngine;
public class RequestHandler : MonoBehaviour
{
public MyData data;
private void Start()
{
data = new MyData();
}
public void OnGetRequest(HttpListenerContext context)
{
var request = context.Request;
var response = context.Response;
response.StatusCode = (int)HttpStatusCode.OK;
response.ContentType = "application/json";
string message = "";
if (request.QueryString.AllKeys.Length > 0)
{
foreach (var key in request.QueryString.AllKeys)
{
object value = request.QueryString.GetValues(key)[0];
Debug.Log("key: " + key + " , value: " + value);
switch (key)
{
case "GetData":
message = JsonUtility.ToJson(data);
break;
case "SetData":
data.success = Convert.ToBoolean(value);
message = JsonUtility.ToJson(data);
break;
}
}
}
var bytes = System.Text.Encoding.UTF8.GetBytes(message);
response.Close(bytes, false);
}
public void OnPostRequest(HttpListenerContext context)
{
var request = context.Request;
var response = context.Response;
response.StatusCode = (int)HttpStatusCode.OK;
response.ContentType = "application/json";
string message = "";
if (request.QueryString.AllKeys.Length > 0)
{
foreach (var key in request.QueryString.AllKeys)
{
object value = request.QueryString.GetValues(key)[0];
Debug.Log("key: " + key + " , value: " + value);
switch (key)
{
case "GetData":
message = JsonUtility.ToJson(data);
break;
case "SetData":
data.success = Convert.ToBoolean(value);
message = JsonUtility.ToJson(data);
break;
}
}
}
var bytes = System.Text.Encoding.UTF8.GetBytes(message);
response.Close(bytes, false);
}
}
[System.Serializable]
public class MyData
{
public bool success = false;
}
여기에서는 UnityEvent를 받고 요청에 해당하는 응답을 반환합니다.
- 요청키가 GetData의 경우 : Unity측의 데이터를 회신
- 요청키가 SetData의 경우 : Unity 측의 데이터를 요청값으로 변경 -> 변경 후 데이터를 회신 응답의 ContentType은 일단 JSON 으로 했습니다.
데이터를 유지하는MyData클래스를 준비하여 JsonUtility에서 Json으로 변환 합니다.
통신 테스트가 가능하면 좋기 때문에 Get/Post 모두 같은 내용이다.
'Unity3D' 카테고리의 다른 글
C# Unity3d Assets이란? (0) | 2023.04.12 |
---|---|
[Unity 3D] UnityWebRequest.Get로 받아온 JSON 데이터를 처리하는 방법 (0) | 2020.12.17 |
[Unity3D] UGUI 와 NGUI 한눈에 비교 (0) | 2020.11.23 |
[Unity3D] Awake 와 Start의 차이점 (0) | 2020.11.19 |
Unity 3D Gameobject.Find 에서 검색되는 기준 (0) | 2020.11.17 |
댓글