프로그래밍

Unity3D에서 Node.js로 데이터를 protobuf를 사용해서 Websocket으로 전송하기

약올랑 2014. 5. 29. 15:26

요약

Unity3D와 Node.JS의 protobuf를 이용해 Websocket으로 통신하는 예제가 없어 만들어 보았다.


설명하는데 복잡한 감이 있어, 앞으로 하게될 내용을 간략히 추려놓았다.

1. websocket 라이브러리 설치

2. protobuf 라이브러리 설치

3. sample idl작성

4. sample idl을 node.js과 c#용으로 컨버팅

5. 서버와 클라이언트 코드를 작성해 테스트 해보기


준비

Unity3D on Windows

websocket

1) Websocket 라이브러리를 다운로드

2) 다운받은 라이브러리를 release 모드로 컴파일

3) \websocket-sharp\bin\release\websocket-sharp.dll 파일을 유니티 프로젝트에 복사


protobuf

1) .net용 protobuf 라이브러리 다운로드

2) \protobuf-net r668\Full\unity\protobuf-net.dll 파일을 유니티 프로젝트에 복사


Node.js on CentOS

# Node.js에 모듈 설치 $ npm install websocket $ npm install protobuf # Python 2.7.5 설치 $ wget http://www.python.org/ftp/python/2.7.5/Python-2.7.5.tgz $ tar xfz Python-2.7.5.tgz $ cd Python-2.7.5 $ ./configure && make && make install # Protocol Buffer 설치 $ wget https://protobuf.googlecode.com/files/protobuf-2.5.0.tar.gz $ tar xfz protobuf-2.5.0.tar.gz $ cd protobuf-2.5.0 $ ./configure && make && make install


샘플 코드

1. protobuf idl 작성하기

sample idl을 feeds.proto로 저장

package feeds;
message Feed {
optional string title = 1;
message Entry {
optional string title = 1;
}
repeated Entry entry = 2;
}
view raw gistfile1.proto hosted with ❤ by GitHub


2. 작성한 idl 컨버팅

node.js

protoc파일은 리눅스용 protobuf 라이브러리가 소스형태라, 컴파일 해야 하며, 컴파일이 성공했다면 생성된다.

$ /usr/local/bin/protoc --descriptor_set_out=feeds.desc --include_imports feeds.proto

생성된 파일을 Node.js 샘플 서버 코드가 있는 경로에 복사

생성된 파일 내용

e
feeds.protofeeds"O
Feed
title ( 
entry ( 2.feeds.Feed.Entry
Entry
title (
view raw gistfile1.txt hosted with ❤ by GitHub

c#

protogen파일은 라이브러리에 포함되어 있다.

\protobuf-net r668\ProtoGen.exe -i:feeds.proto -o:Feeds.cs

생성된 파일을 유니티 프로젝트에 복사

생성된 파일 내용

//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
// Generated from: feeds.proto
namespace feeds
{
[global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"Feed")]
public partial class Feed : global::ProtoBuf.IExtensible
{
public Feed() {}
private string _title = "";
[global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"title", DataFormat = global::ProtoBuf.DataFormat.Default)]
[global::System.ComponentModel.DefaultValue("")]
public string title
{
get { return _title; }
set { _title = value; }
}
private readonly global::System.Collections.Generic.List<feeds.Feed.Entry> _entry = new global::System.Collections.Generic.List<feeds.Feed.Entry>();
[global::ProtoBuf.ProtoMember(2, Name=@"entry", DataFormat = global::ProtoBuf.DataFormat.Default)]
public global::System.Collections.Generic.List<feeds.Feed.Entry> entry
{
get { return _entry; }
}
[global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"Entry")]
public partial class Entry : global::ProtoBuf.IExtensible
{
public Entry() {}
private string _title = "";
[global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"title", DataFormat = global::ProtoBuf.DataFormat.Default)]
[global::System.ComponentModel.DefaultValue("")]
public string title
{
get { return _title; }
set { _title = value; }
}
private global::ProtoBuf.IExtension extensionObject;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
{ return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
}
private global::ProtoBuf.IExtension extensionObject;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
{ return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

3. 클라이언트와 서버 코드 작성하기

unity sample client script

using UnityEngine;
using System.Collections;
using WebSocketSharp;
using System.IO;
public class background : MonoBehaviour {
private WebSocket ws;
// Use this for initialization
void Start () {
ws = new WebSocket ("ws://172.20.44.92:8080","echo-protocol" );
ws.OnMessage += (sender, e) =>{
feeds.Feed dfeed = ProtoBuf.Serializer.Deserialize<feeds.Feed>(new MemoryStream(e.RawData));
Debug.Log("Laputa says: " + dfeed.title);
};
ws.Connect ();
feeds.Feed feed = new feeds.Feed();
feed.title = "hello";
feed.entry.Add(new feeds.Feed.Entry());
MemoryStream mem = new MemoryStream(); // 메모리 스트림 생성
ProtoBuf.Serializer.Serialize(mem, feed);
ws.Send (mem.ToArray());
}
// Update is called once per frame
void Update () {
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

node.js sample server code

var WebSocketServer = require('websocket').server;
var http = require('http');
var server = http.createServer(function(request, response) {
console.log((new Date()) + ' Received request for ' + request.url + ' ' + process.pid);
response.writeHead(404);
response.end();
});
server.listen(8080, function() {
console.log((new Date()) + ' Server is listening on port 8080 of process:' + process.pid);
});
var wsServer = new WebSocketServer({
httpServer: server,
// You should not use autoAcceptConnections for production
// applications, as it defeats all standard cross-origin protection
// facilities built into the protocol and the browser. You should
// *always* verify the connection's origin and decide whether or not
// to accept it.
autoAcceptConnections: false
});
function originIsAllowed(origin) {
// put logic here to detect whether the specified origin is allowed.
return true;
}
wsServer.on('request', function(request) {
if (!originIsAllowed(request.origin)) {
// Make sure we only accept requests from an allowed origin
request.reject();
console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
return;
}
var connection = request.accept('echo-protocol', request.origin);
console.log((new Date()) + ' Connection accepted.' + process.pid);
connection.on('message', function(message) {
if (message.type === 'utf8') {
console.log('Server Received Message: ' + message.utf8Data + ' len: ' + message.utf8Data.length);
connection.send(message.utf8Data);
}
else if (message.type === 'binary') {
console.log('Server Received Binary Message of ' + message.binaryData.length + ' bytes');
connection.sendBytes(message.binaryData);
// 수신한 protobuf data를 복원시키기.
var Schema = require('protobuf').Schema;
var readFile = require('fs').readFileSync;
var schema = new Schema(readFile('feeds.desc'));
var FeedMsg = schema['feeds.Feed'];
// Convert to protobuf format
var outMsg = FeedMsg.parse(message.binaryData);
console.log(outMsg.title);
}
});
connection.on('close', function(reasonCode, description) {
console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
});
});
view raw gistfile1.js hosted with ❤ by GitHub


테스트 해보기

node.js log

$ ./node test.js Thu May 29 2014 17:20:13 GMT+0900 (KST) Server is listening on port 8080 of process:3709 Thu May 29 2014 17:20:18 GMT+0900 (KST) Connection accepted.3709 Server Received Binary Message of 9 bytes hello

Unity3D log

Laputa says: hello