본문 바로가기
💻 programming/unity

Python client - Unity Server

by 연구원-A 2021. 8. 4.
반응형

I. Data format

I-1. Python using struct


  • 파이썬의 struct 모듈을 이용하면 정수, 문자열 등을 바이트 객체로 변환하거나 추출할 수 있다
    • pack, unpack, calcsize을 제공

struct - Interpret bytes as packed binary data - Python 3.9.6 documentation

I-1. basic_packet_format.py

BasicPacketFormat = '='
BasicPacketFormat += 'i'  # Type Of Service (int; 4bytes)
BasicPacketFormat += 'i'  # Display ID (int; 4bytes)
BasicPacketFormat += 'i'  # Payload Length (int; 4bytes)

I-2. touch_packet.py

from packet import basic_packet_format
from collections import namedtuple
import struct

TouchPacketFormat = basic_packet_format.BasicPacketFormat
TouchPacketFormat += 'i'  # X-axis
TouchPacketFormat += 'i'  # Y-axis

class TouchPacket:
    def __init__(self, display_id):
        print('Create Touch Packet')
        self.display_id = display_id
        self.type_of_service = 0

    def GetData(self, input_x, input_y):
        TouchStruct = namedtuple("TouchStruct", "tos id len x y")
        TupleToSend = TouchStruct(
            tos=self.type_of_service,
            id=self.display_id,
            len=8,
            x=input_x,
            y=input_y
        )
        #print(TouchPacketFormat)
        #print(TupleToSend)
        #print(*TupleToSend._asdict().values())

        StringToSend = struct.pack(TouchPacketFormat, *TupleToSend._asdict().values())
        return StringToSend

I-3. direction_packet.py

from packet import basic_packet_format
from collections import namedtuple
import struct

DirectionPacketFormat = basic_packet_format.BasicPacketFormat
DirectionPacketFormat += 'i'  # Direction

class DirectionPacket:
    def __init__(self, display_id):
        print('Create Direction Packet')
        self.display_id = display_id
        self.type_of_service = 1

    def GetData(self, input_direction):
        DirectionStruct = namedtuple("DirectionStruct", "tos id len direction")
        TupleToSend = DirectionStruct(
            tos=self.type_of_service,
            id=self.display_id,
            len=4,
            direction=input_direction
        )
        #print(DirectionPacketFormat)
        #print(TupleToSend)
        #print(*TupleToSend._asdict().values())

        StringToSend = struct.pack(DirectionPacketFormat, *TupleToSend._asdict().values())
        return StringToSend

I-4. gaze_packet.py

from packet import basic_packet_format
from collections import namedtuple
import struct

GazePacketFormat = basic_packet_format.BasicPacketFormat
GazePacketFormat += 'i'  # X-axis
GazePacketFormat += 'i'  # Y-axis

class GazePacket:
    def __init__(self, display_id):
        print('Create Gaze Packet')
        self.display_id = display_id
        self.type_of_service = 2

    def GetData(self, input_x, input_y):
        GazeStruct = namedtuple("GazeStruct", "tos id len x y")
        TupleToSend = GazeStruct(
            tos=self.type_of_service,
            id=self.display_id,
            len=8,
            x=input_x,
            y=input_y
        )
        StringToSend = struct.pack(GazePacketFormat, *TupleToSend._asdict().values())
        return StringToSend

I-5. voice_packet.py

from packet import basic_packet_format
from collections import namedtuple
import struct

VoicePacketFormat = basic_packet_format.BasicPacketFormat
VoicePacketFormat += '1012s'

class VoicePacket:
    def __init__(self, display_id):
        print('Create Voice Packet')
        self.display_id = display_id
        self.type_of_service = 3

    def GetData(self, input_len, input_txt):
        VoiceStruct = namedtuple("VoiceStruct", "tos id len txt")
        TupleToSend = VoiceStruct(
            tos=self.type_of_service,
            id=self.display_id,
            len=input_len,
            txt=input_txt.encode("ascii")
        )
        StringToSend = struct.pack(VoicePacketFormat, *TupleToSend._asdict().values())
        return StringToSend

I-6. hand_skeleton_packet.py

from packet import basic_packet_format
from collections import namedtuple
import struct

HandSkeletonPacketFormat = basic_packet_format.BasicPacketFormat
HandSkeletonPacketFormat += 'i'  # X-axis
HandSkeletonPacketFormat += 'i'  # Y-axis

class HandSkeletonPacket:
    def __init__(self, display_id):
        print('Create Hand Skeleton Packet')
        self.display_id = display_id
        self.type_of_service = 5

    def GetData(self, input_x, input_y):
        HandSkeletonStruct = namedtuple("HandSkeletonStruct", "tos id len x y")
        TupleToSend = HandSkeletonStruct(
            tos=self.type_of_service,
            id=self.display_id,
            len=8,
            x=input_x,
            y=input_y
        )
        StringToSend = struct.pack(HandSkeletonPacketFormat, *TupleToSend._asdict().values())
        return StringToSend

II. Python Client

II-1. Client


  • 클라이언트 인터페이스 객체를 생성하고 패킷 전송 함수를 호출 (예시: SendTouchPacket(1, 2))
import socket
from time import sleep
import client_interface

display_id = input('enter display id : ')

client_interface = client_interface.ClientInterface(int(display_id))
client_interface.SendTouchPacket(1, 2)
client_interface.SendDirectionPacket(0)
client_interface.SendGazePacket(11, 22)
client_interface.SendHandSkeletonPacket(3, 4)
client_interface.SendVoicePacket('hello world')
client_interface.SendTouchPacket(1, 2)

II-2. Client Interface


  • 객체를 생성하면 소켓 클라이언트 연결을 요청
  • 객체가 소멸될 때 소켓을 닫음
  • 패킷 전송 함수를 별도로 구현하여 데이터 포맷에 맞게 전송할 수 있도록 추상화함
import struct
import socket
from time import sleep
from packet import touch_packet
from packet import gaze_packet
from packet import direction_packet
from packet import voice_packet
#from packet import body_skeleton_packet
from packet import hand_skeleton_packet

server_address = '127.0.0.1'  # Need to be changed to 192.168.0.2
port = 50001

class ClientInterface:
    def __init__(self, display_id):
        print('Create Client Interface')
        self.display_id = int(display_id)
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.connect((server_address, port))

    def __del__(self):
        self.s.close()

    def SendTouchPacket(self, input_x, input_y):
        touch = touch_packet.TouchPacket(self.display_id)
        TouchData = touch.GetData(int(input_x), int(input_y))
        self.s.send(TouchData)

    def SendDirectionPacket(self, input_direction):
        direction = direction_packet.DirectionPacket(self.display_id)
        DirectionData = direction.GetData(int(input_direction))
        self.s.send(DirectionData)

    def SendGazePacket(self, input_x, input_y):
        gaze = gaze_packet.GazePacket(self.display_id)
        GazeData = gaze.GetData(int(input_x), int(input_y))
        self.s.send(GazeData)

    def SendVoicePacket(self, txt):
        voice = voice_packet.VoicePacket(self.display_id)
        VoiceData = voice.GetData(len(txt)+1, txt)
        self.s.send(VoiceData)

    def SendHandSkeletonPacket(self, input_x, input_y):
        hand_skeleton = hand_skeleton_packet.HandSkeletonPacket(self.display_id)
        HandSkeletonData = hand_skeleton.GetData(int(input_x), int(input_y))
        self.s.send(HandSkeletonData)

III. C# Server

III-1. Server (Unity)


  • C#에서 제공하는 TcpListner, TcpClient를 이용하여 구현함
  • 게임 오브젝트가 생성되면 별도의 Thread에서 데이터를 수신할 수 있음
  • 빅엔디안의 경우 바이트 배열을 역순으로 배치함 (Array.Reverse(bytesTypeOfService);)
  • BitConverter를 이용해 바이트 배열을 4바이트 int로 변환함
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

public class Server : MonoBehaviour
{
    #region private members
    private TcpListener tcpListener;
    private Thread tcpListenerThread;
    private TcpClient connectedTcpClient;
    #endregion

    void Start()
    {
        // Start TcpServer background thread
        tcpListenerThread = new Thread (new ThreadStart(ListenForIncommingRequest));
        tcpListenerThread.IsBackground = true;
        tcpListenerThread.Start();
    }

    void Update()
    {

    }

    // Runs in background TcpServerThread; Handles incomming TcpClient requests
    private void ListenForIncommingRequest() {
        try {
            // Create listener on 192.168.0.2 port 50001
            tcpListener = new TcpListener(IPAddress.Parse("192.168.0.2"), 50001);
            tcpListener.Start();
            Debug.Log("Server is listening");

            while (true) {
                using (connectedTcpClient = tcpListener.AcceptTcpClient()) {
                    // Get a stream object for reading
                    using (NetworkStream stream = connectedTcpClient.GetStream()) {
                        // Read incomming stream into byte array.
                        do {
                            Byte[] bytesTypeOfService = new Byte[4];
                            Byte[] bytesDisplayId = new Byte[4];
                            Byte[] bytesPayloadLength = new Byte[4];

                            int lengthTypeOfService = stream.Read(bytesTypeOfService, 0, 4);
                            int lengthDisplayId = stream.Read(bytesDisplayId, 0, 4);
                            int lengthPayloadLength = stream.Read(bytesPayloadLength, 0, 4);

                            if(lengthTypeOfService <= 0 && lengthDisplayId <= 0 && lengthPayloadLength <= 0) {
                                break;
                            }

                            // Reverse byte order, in case of big endian architecture
                            if (!BitConverter.IsLittleEndian) {
                                Array.Reverse(bytesTypeOfService);
                                Array.Reverse(bytesDisplayId);
                                Array.Reverse(bytesPayloadLength);
                            }

                            int typeOfService = BitConverter.ToInt32(bytesTypeOfService, 0);
                            int displayId = BitConverter.ToInt32(bytesDisplayId, 0);
                            int payloadLength = BitConverter.ToInt32(bytesPayloadLength, 0);

                            if(typeOfService == 3) {
                                payloadLength = 1012;
                            }

                            Byte[] bytes = new Byte[payloadLength];
                            int length = stream.Read(bytes, 0, payloadLength);

                            HandleIncommingRequest(typeOfService, displayId, payloadLength, bytes);
                        } while (true);
                    }
                }
            }
        }
        catch (SocketException socketException) {
            Debug.Log("SocketException " + socketException.ToString());
        }
    }

    // Handle incomming request
    private void HandleIncommingRequest(int typeOfService, int displayId, int payloadLength, byte[] bytes) {
        Debug.Log("=========================================");
        Debug.Log("Type of Service : " + typeOfService);
        Debug.Log("Display Id      : " + displayId);
        Debug.Log("Payload Length  : " + payloadLength);
        switch(typeOfService) {
            case 0: // Touch (0)
                TouchHandler(displayId, payloadLength, bytes);
                break;
            case 1: // Direction (1)
                DirectionHander(displayId, payloadLength, bytes);
                break;
            case 2: // Gaze (2)
                GazeHandler(displayId, payloadLength, bytes);
                break;
            case 3: // Voice (3)
                VoiceHandler(displayId, payloadLength, bytes);
                break;
            case 4: // Body Skeleton (4)
                BodySkeletonHandler(displayId, payloadLength, bytes);
                break;
            case 5: // Hand Skeleton (5)
                HandSkeletonHandler(displayId, payloadLength, bytes);
                break;
        }
    }

    // Handle Touch Signal
    private void TouchHandler(int displayId, int payloadLength, byte[] bytes) {
        Debug.Log("Execute Touch Handler");
        int x_axis = BitConverter.ToInt32(bytes, 0);
        int y_axis = BitConverter.ToInt32(bytes, 4);
        Debug.Log("X axis     : " + x_axis);
        Debug.Log("Y axis     : " + y_axis);
    }

    // Handle Direction Signal
    private void DirectionHander(int displayId, int payloadLength, byte[] bytes) {
        Debug.Log("Execute Direction Handler");
        int direction = BitConverter.ToInt32(bytes, 0);
        Debug.Log("Direction  : " + direction);
    }

    // Handle Gaze Signal
    private void GazeHandler(int displayId, int payloadLength, byte[] bytes) {
        Debug.Log("Execute Gaze Handler");
        int x_axis = BitConverter.ToInt32(bytes, 0);
        int y_axis = BitConverter.ToInt32(bytes, 4);
        Debug.Log("X axis     : " + x_axis);
        Debug.Log("Y axis     : " + y_axis);
    }

    // Handle Voice Signal
    private void VoiceHandler(int displayId, int payloadLength, byte[] bytes) {
        Debug.Log("Execute Voice Handler");
        string str = Encoding.Default.GetString(bytes);
        Debug.Log("Text       : " + str);
    }

    // Handle Body Skeleton Signal
    private void BodySkeletonHandler(int displayId, int payloadLength, byte[] bytes) {
        Debug.Log("Execute Body Skeleton Handler");
        // TODO
    }

    // Handle Hand Skeleton Signal
    private void HandSkeletonHandler(int displayId, int payloadLength, byte[] bytes) {
        Debug.Log("Execute Hand Skeleton Handler");
        int x_axis = BitConverter.ToInt32(bytes, 0);
        int y_axis = BitConverter.ToInt32(bytes, 4);
        Debug.Log("X axis     : " + x_axis);
        Debug.Log("Y axis     : " + y_axis);
    }
}
반응형

댓글