유니티/여러가지

유니티 TCP 통신

smallship 2024. 9. 3. 17:32

유니티에서 새 프로젝트를 생성한다. (이름은 Server로 하였다.)

빈 객체를 하나 생성하고 이름을 변경해주었다.

TCP Server 스크립트를 생성하여 방금 만든 객체에 적용시켜준다.

 

using UnityEngine;
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

public class TCPServer : MonoBehaviour
{
    public int port = 7777;
    private TcpListener tcpListener;
    private Thread listenThread;

    private ConcurrentDictionary<TcpClient, NetworkStream> StreamDic = new();

    void Start()
    {
        this.tcpListener = new TcpListener(IPAddress.Any, port);
        this.tcpListener.Start();

        this.listenThread = new Thread(new ThreadStart(ListenForClients));
        this.listenThread.Start();
    }

    private void ListenForClients()
    {
        while (true)
        {
            TcpClient client = this.tcpListener.AcceptTcpClient();
            Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
            clientThread.Start(client);
        }
    }

    private void HandleClientComm(object client)
    {
        TcpClient tcpClient = (TcpClient)client;
        NetworkStream clientStream = tcpClient.GetStream();

        StreamDic.TryAdd(tcpClient, clientStream);

        byte[] message = new byte[4096];
        int bytesRead;

        while (true)
        {
            bytesRead = 0;

            try
            {
                bytesRead = clientStream.Read(message, 0, 4096);
            }
            catch
            {
                break;
            }

            if (bytesRead == 0)
                break;

            string receivedMessage = Encoding.ASCII.GetString(message, 0, bytesRead);
            Debug.Log("Received: " + receivedMessage);

            // 여기에 메시지 처리 로직을 추가하세요

            // 클라이언트에 응답을 보냅니다
            byte[] response = Encoding.ASCII.GetBytes("서버 응답: " + receivedMessage);

            foreach (var streamDicValue in StreamDic.Values)
            {
                streamDicValue.Write(response, 0, response.Length);
                streamDicValue.Flush();
            }
            // clientStream.Write(response, 0, response.Length);
            // clientStream.Flush();
        }

        StreamDic.TryRemove(tcpClient, out NetworkStream stream);
        tcpClient.Close();
    }

    void OnApplicationQuit()
    {
        if (tcpListener != null)
        {
            tcpListener.Stop();
        }
        if (listenThread != null)
        {
            listenThread.Abort();
        }
    }
}

유니티 엔진을 사용하여 TCP 서버를 구현하였다.

 

  • 서버 초기화 (Start 메서드)
    • 지정된 포트(기본값 7777)로 TcpListener를 생성하고 시작한다.
    • 클라이언트 연결을 대기하는 별도의 스레드를 시작한다.
  • 클라이언트 연결 대기 (ListenForClients 메서드)
    • 무한 루프에서 새로운 클라이언트 연결을 계속 수락한다.
    • 각 클라이언트 연결에 대해 새로운 스레드를 생성하여 통신을 처리한다.
  • 클라이언트 통신 처리 (HandleClientComm 메서드)
    • 클라이언트와의 통신 스트림을 설정한다.
    • 클라이언트로부터 메시지를 수신하고 처리한다.
    • 수신된 메시지를 모든 연결된 클라이언트에게 브로드캐스트한다.
    • 연결이 끊어지면 클라이언트를 목록에서 제거하고 연결을 종료한다.
  • 동시성 처리
    • ConcurrentDictionary를 사용하여 여러 클라이언트 연결을 안전하게 관리한다.
  • 애플리케이션 종료 처리 (OnApplicationQuit 메서드)
    • 서버가 종료될 때 리소스를 정리한다.

 

클라이언트 또한 새 프로젝트를 생성한 후 빈 객체를 생성하여 이름을 지정하고 TCP Client 스크립트를 적용시켜준다.

 

using UnityEngine;
using System;
using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using TMPro;

public class TCPClient : MonoBehaviour
{
    public string serverIP = "127.0.0.1";
    public int serverPort = 7777;
    private TcpClient client;
    private NetworkStream stream;
    private Thread receiveThread;
    private bool isRunning = false;

    private ConcurrentQueue<string> messageQueue = new ConcurrentQueue<string>();

    public TextMeshProUGUI text;

    void Start()
    {
        ConnectToServer();
    }

    private void ConnectToServer()
    {
        try
        {
            client = new TcpClient(serverIP, serverPort);
            stream = client.GetStream();
            isRunning = true;

            receiveThread = new Thread(new ThreadStart(ReceiveData));
            receiveThread.Start();

            Debug.Log("Connected to server!");
        }
        catch (Exception e)
        {
            Debug.LogError("Error connecting to server: " + e.Message);
        }
    }

    private void ReceiveData()
    {
        byte[] data = new byte[1024];
        try
        {
            while (isRunning)
            {
                int bytesRead = stream.Read(data, 0, data.Length);
                if (bytesRead > 0)
                {
                    string receivedMessage = Encoding.UTF8.GetString(data, 0, bytesRead);
                    Debug.Log("Received from server: " + receivedMessage);
                    messageQueue.Enqueue(receivedMessage);
                    // 여기에서 UI 업데이트나 게임 로직을 처리하세요.
                    // 주의: UI 업데이트는 메인 스레드에서 수행해야 합니다.
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError("Error receiving data: " + e.Message);
        }
    }

    public void SendMessage(string message)
    {
        if (client != null && client.Connected)
        {
            try
            {
                byte[] data = Encoding.UTF8.GetBytes(message);
                stream.Write(data, 0, data.Length);
                Debug.Log("Sent to server: " + message);
            }
            catch (Exception e)
            {
                Debug.LogError("Error sending data: " + e.Message);
            }
        }
        else
        {
            Debug.LogWarning("Not connected to server.");
        }
    }

    void OnApplicationQuit()
    {
        CloseConnection();
    }

    private void CloseConnection()
    {
        isRunning = false;
        if (receiveThread != null)
        {
            receiveThread.Abort();
        }
        if (stream != null)
        {
            stream.Close();
        }
        if (client != null)
        {
            client.Close();
        }
        Debug.Log("Disconnected from server.");
    }

    // 이 메서드를 사용하여 메시지를 보낼 수 있습니다.
    // 예: 버튼 클릭 이벤트나 다른 게임 로직에서 호출
    public void OnSendButtonClick(string msg)
    {
        SendMessage(msg);
    }

    void Update()
    {
        if (messageQueue.TryDequeue(out string mesage))
        {
            text.text = mesage;
        }
    }
}

TCP 클라이언트를 구현하였다.

 

  • 서버 연결 (ConnectToServer 메서드)
    • 지정된 IP 주소와 포트로 서버에 연결을 시도한다.
    • 연결이 성공하면 데이터 수신을 위한 별도의 스레드를 시작한다.
  • 데이터 수신 (ReceiveData 메서드)
    • 별도의 스레드에서 서버로부터 지속적으로 데이터를 수신한다.
    • 수신된 메시지를 ConcurrentQueue에 저장한다.
  • 메시지 전송 (SendMessage 메서드)
    • 서버에 메시지를 전송한다.
  • UI 업데이트 (Update 메서드)
    • 메인 스레드에서 수신된 메시지를 UI에 표시한다.
  • 연결 종료 처리 (CloseConnection 메서드)
    • 애플리케이션 종료 시 연결을 안전하게 종료한다.

 

메세지를 확인하기 위해 UI를 만들어주고 버튼에 SendMessage 스크립트를 적용시켰다.

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class SendMessage : MonoBehaviour, IPointerClickHandler
{
    public TCPClient client;
    public TMP_InputField inputField;

    public void OnPointerClick(PointerEventData eventData)
    {
        client.OnSendButtonClick(inputField.text);
        inputField.text = string.Empty;
    }
}

TCP Client 클래스와 함께 작동하며, UI를 통해 서버에 메시지를 보낼 수 있다.

 

서버를 켜주고 ( 서버 프로젝트 실행 ) 빌드한 클라이언트 프로젝트를 실행시켜서 테스트를 해보았다.

같은 IP와 PORT를 사용하면 서로 통신이 되는것을 확인할 수 있다. 

유니티를 사용해서 간단한 클라이언트-서버 통신 시스템을 만들어보았다.

'유니티 > 여러가지' 카테고리의 다른 글

TCP 통신 javascript  (1) 2024.09.05
Post Process (URP)  (3) 2024.07.30
RenderTexture를 이용하여 CCTV만들기 (미니맵 활용 가능)  (2) 2024.07.14