본문 바로가기
UNITY/유니티게임스쿨

Unity 클라이언트와 서버 통신하기

by 램플릿 2024. 9. 25.

Unity 프로젝트에 InputField 생성하기


 

 

유니티에서 InputField 2개 생성. 아래와같이 하이어라키 구조를 만들어준다.

 

 

+강사님 언급) 네트워크 개념 빅엔디언과 리틀엔디언 (ByteOrder) 알아두세요~
BigEndian : 앞에서부터 4바이트를 계산.
LittleEndian : 뒤에서부터 4바이트를 계산. 컴퓨터가 계산하기에 더 유리하다.

 

 

TcpProtobufClien.cs

stream = 패킷이 오고가는것을 관리.

isRunning = 멀티스레드를 돌리고있는지 확인.

메인스레드 기준으로 stream.BeginRead : 프로토버퍼의 길이.0에서부터 4바이트가 찰때까지 읽어온다.

OnMessageReceive 비동기로 메시지가 도착하면 확인.

 

binary.LittleEndian으로 변환해서 읽고 변환해서 보낸다.

 

📄main.go

package main

import (
	"encoding/binary"
	"fmt"
	"log"
	"net"

	pb "golangtcp/messages"

	"google.golang.org/protobuf/proto"

	mg "golangtcp/packages/manager"
)

func main() {

	listener, err := net.Listen("tcp", ":8888")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}
	defer listener.Close()
	fmt.Println("Server is listening on :8888")

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Printf("Failed to accept connection: %v", err)
			continue
		}
		go handleConnection(conn)
	}
}

func handleConnection(conn net.Conn) {
	defer conn.Close()
	for {
		// 메시지 길이를 먼저 읽습니다 (4바이트)
		lengthBuf := make([]byte, 4)
		_, err := conn.Read(lengthBuf)
		if err != nil {
			log.Printf("Failed to read message length: %v", err)
			return
		}
		length := binary.LittleEndian.Uint32(lengthBuf)

		// 메시지 본문을 읽습니다
		messageBuf := make([]byte, length)
		_, err = conn.Read(messageBuf)
		if err != nil {
			log.Printf("Failed to read message body: %v", err)
			return
		}

		// Protocol Buffers 메시지를 파싱합니다
		message := &pb.GameMessage{}
		err = proto.Unmarshal(messageBuf, message)
		if err != nil {
			log.Printf("Failed to unmarshal message: %v", err)
			continue
		}

		// 메시지 처리
		processMessage(message, &conn)

		// 응답 메시지 생성 및 전송 (예: 에코)
		response, err := proto.Marshal(message)
		if err != nil {
			log.Printf("Failed to marshal response: %v", err)
			continue
		}

		// 메시지 길이를 먼저 보냅니다
		binary.LittleEndian.PutUint32(lengthBuf, uint32(len(response)))
		conn.Write(lengthBuf)

		// 메시지 본문을 보냅니다
		conn.Write(response)
	}
}

func processMessage(message *pb.GameMessage, conn *net.Conn) {
	switch msg := message.Message.(type) {
	case *pb.GameMessage_PlayerPosition:
		pos := msg.PlayerPosition
		fmt.Println("Position : ", pos.X, pos.Y, pos.Z)
	case *pb.GameMessage_Chat:
		chat := msg.Chat
		mg.GetChatManager().Broadcast(chat.Sender, chat.Content)
	case *pb.GameMessage_Login:
		playerId := msg.Login.PlayerId
		fmt.Println(playerId)
		playerManager := mg.GetPlayerManager()
		playerManager.AddPlayer(playerId, 0, conn)
	default:
		panic(fmt.Sprintf("unexpected messages.isGameMessage_Message: %#v", msg))
	}
}

 

📄playerManager.go

package manager

import (
	"errors"
	"net"
)

var playerManager *PlayerManager

// Player represents a single player with some attributes
type Player struct {
	ID   int
	Name string
	Age  int
	Conn *net.Conn
}

// PlayerManager manages a list of players
type PlayerManager struct {
	players map[int]Player
	nextID  int
}

// NewPlayerManager creates a new PlayerManager
func GetPlayerManager() *PlayerManager {
	if playerManager == nil {
		playerManager = &PlayerManager{
			players: make(map[int]Player),
			nextID:  1,
		}
	}

	return playerManager
}

// AddPlayer adds a new player to the manager
func (pm *PlayerManager) AddPlayer(name string, age int, conn *net.Conn) Player {
	player := Player{
		ID:   pm.nextID,
		Name: name,
		Age:  age,
		Conn: conn,
	}

	pm.players[pm.nextID] = player
	pm.nextID++
	return player
}

// GetPlayer retrieves a player by ID
func (pm *PlayerManager) GetPlayer(id int) (Player, error) {
	player, exists := pm.players[id]
	if !exists {
		return Player{}, errors.New("player not found")
	}
	return player, nil
}

// RemovePlayer removes a player by ID
func (pm *PlayerManager) RemovePlayer(id int) error {
	if _, exists := pm.players[id]; !exists {
		return errors.New("player not found")
	}
	delete(pm.players, id)
	return nil
}

// ListPlayers returns all players in the manager
func (pm *PlayerManager) ListPlayers() []Player {
	playerList := []Player{}
	for _, player := range pm.players {
		playerList = append(playerList, player)
	}
	return playerList
}

 

📄 chatManager.go

package manager

import (
	"encoding/binary"
	"log"

	pb "golangtcp/messages"

	"google.golang.org/protobuf/proto"
)

var chatManager *ChatManager

// PlayerManager manages a list of playersH
type ChatManager struct {
	players map[int]Player
	nextID  int
}

// NewPlayerManager creates a new PlayerManager
func GetChatManager() *ChatManager {
	if chatManager == nil {
		chatManager = &ChatManager{
			players: make(map[int]Player),
			nextID:  1,
		}
	}
	return chatManager
}

// AddPlayer adds a new player to the manager
func (pm *ChatManager) Broadcast(name string, content string) {
	for _, player := range GetPlayerManager().ListPlayers() {
		gameMessage := &pb.GameMessage{
			Message: &pb.GameMessage_Chat{
				Chat: &pb.ChatMessage{
					Sender:  name,
					Content: content,
				},
			},
		}
		response, err := proto.Marshal(gameMessage)
		if err != nil {
			log.Printf("Failed to marshal response: %v", err)
			continue
		}

		lengthBuf := make([]byte, 4)
		binary.LittleEndian.PutUint32(lengthBuf, uint32(len(response)))
		lengthBuf = append(lengthBuf, response...)
		(*player.Conn).Write(lengthBuf)
	}
}

 

📄 TcpProtobufClient.cs

using UnityEngine;
using System;
using System.Net.Sockets;
using System.Threading;
using Google.Protobuf;
using Game;

public class TcpProtobufClient : MonoBehaviour
{
    public static TcpProtobufClient Instance { get; private set; }
    
    private TcpClient tcpClient;
    private NetworkStream stream;
    private bool isRunning = false;

    private const string SERVER_IP = "127.0.0.1";
    private const int SERVER_PORT = 8888;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(this.gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
    
    void Start()
    {
        ConnectToServer();
    }

    void ConnectToServer()
    {
        try
        {
            tcpClient = new TcpClient(SERVER_IP, SERVER_PORT);
            stream = tcpClient.GetStream();
            isRunning = true;
            StartReceiving();

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

    void StartReceiving()
    {
        byte[] lengthBuffer = new byte[4];
        stream.BeginRead(lengthBuffer, 0, 4, OnLengthReceived, lengthBuffer);
    }
    
    void OnLengthReceived(IAsyncResult ar)
    {
        try
        {
            int bytesRead = stream.EndRead(ar);
            if (bytesRead == 0) return; // 연결 종료

            byte[] lengthBuffer = (byte[])ar.AsyncState;
            int length = BitConverter.ToInt32(lengthBuffer, 0);

            byte[] messageBuffer = new byte[length];
            stream.BeginRead(messageBuffer, 0, length, OnMessageReceived, messageBuffer);
        }
        catch (Exception e)
        {
            Debug.LogError($"Error receiving message length: {e.Message}");
        }
    }

    void OnMessageReceived(IAsyncResult ar)
    {
        try
        {
            int bytesRead = stream.EndRead(ar);
            if (bytesRead == 0) return; // 연결 종료

            byte[] messageBuffer = (byte[])ar.AsyncState;
            GameMessage gameMessage = GameMessage.Parser.ParseFrom(messageBuffer);

            if (gameMessage.MessageCase == GameMessage.MessageOneofCase.Chat)
            {
                ChatMessage chatMessage = gameMessage.Chat;
                Debug.Log($"Received chat: {chatMessage.Sender}: {chatMessage.Content}");
                // 여기서 UI 업데이트 등의 추가 처리를 할 수 있습니다.
            }

            StartReceiving(); // 다음 메시지 수신 대기
        }
        catch (Exception e)
        {
            Debug.LogError($"Error receiving message: {e.Message}");
        }
    }
    
    Vector3 prevPosition = Vector3.zero;

    void Update()
    {
        if (transform.position != prevPosition)
        {
            SendPlayerPosition("정모", transform.position.x, transform.position.y, transform.position.z);
        }
        prevPosition = transform.position;
    }

    public void SendPlayerPosition(string playerId, float x, float y, float z)
    {
        var position = new PlayerPosition
        {
            PlayerId = playerId,
            X = x,
            Y = y,
            Z = z
        };
        var message = new GameMessage
        {
            PlayerPosition = position
        };
        SendMessage(message);
    }

    public void SendChatMessage(string sender, string content)
    {
        var chat = new ChatMessage
        {
            Sender = sender,
            Content = content
        };
        var message = new GameMessage
        {
            Chat = chat
        };
        SendMessage(message);
    }
    
    public void SendLoginMessage(string playerId)
    {
        var login = new LoginMessage()
        {
            PlayerId = playerId
        };
        var message = new GameMessage
        {
            Login = login
        };
        SendMessage(message);
    }

    private void SendMessage(GameMessage message)
    {
        if (tcpClient != null && tcpClient.Connected)
        {
            byte[] messageBytes = message.ToByteArray();
            byte[] lengthBytes = BitConverter.GetBytes(messageBytes.Length);

            // 메시지 길이를 먼저 보냅니다
            stream.Write(lengthBytes, 0, 4);
            // 메시지 본문을 보냅니다
            stream.Write(messageBytes, 0, messageBytes.Length);
        }
    }

    void OnDisable()
    {
        isRunning = false;
        if (stream != null) stream.Close();
        if (tcpClient != null) tcpClient.Close();
    }
}

 

📄 TcpProtobuflient_MultiThread.cs

using UnityEngine;
using System;
using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Threading;
using Google.Protobuf;
using Game;

public class TcpProtobufClient_MultiThread : MonoBehaviour
{
    public static TcpProtobufClient_MultiThread Instance { get; private set; }
    
    private TcpClient tcpClient;
    private Thread receiveThread;
    private NetworkStream stream;
    private bool isRunning = false;

    private const string SERVER_IP = "127.0.0.1";
    private const int SERVER_PORT = 8888;
    
    private ConcurrentQueue<GameMessage> messageQueue = new ConcurrentQueue<GameMessage>();

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(this.gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
    
    void Start()
    {
        ConnectToServer();
    }

    void ConnectToServer()
    {
        try
        {
            tcpClient = new TcpClient(SERVER_IP, SERVER_PORT);
            stream = tcpClient.GetStream();
            isRunning = true;
            receiveThread = new Thread(ReceiveLoop);
            receiveThread.Start();

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

    void ReceiveLoop()
    {
        byte[] lengthBuffer = new byte[4];
        while (isRunning)
        {
            try
            {
                int bytesRead = stream.Read(lengthBuffer, 0, 4);
                if (bytesRead == 0) break; // Connection closed

                int length = BitConverter.ToInt32(lengthBuffer, 0);
                byte[] messageBuffer = new byte[length];
                bytesRead = stream.Read(messageBuffer, 0, length);
                if (bytesRead == 0) break; // Connection closed

                GameMessage gameMessage = GameMessage.Parser.ParseFrom(messageBuffer);
                messageQueue.Enqueue(gameMessage);
            }
            catch (Exception e)
            {
                Debug.LogError($"Error in receive loop: {e.Message}");
                break;
            }
        }
    }
    
    Vector3 prevPosition = Vector3.zero;

    void Update()
    {
        if (transform.position != prevPosition)
        {
            SendPlayerPosition("정모", transform.position.x, transform.position.y, transform.position.z);
        }
        prevPosition = transform.position;
    }

    public void SendPlayerPosition(string playerId, float x, float y, float z)
    {
        var position = new PlayerPosition
        {
            PlayerId = playerId,
            X = x,
            Y = y,
            Z = z
        };
        var message = new GameMessage
        {
            PlayerPosition = position
        };
        SendMessage(message);
    }

    public void SendChatMessage(string sender, string content)
    {
        var chat = new ChatMessage
        {
            Sender = sender,
            Content = content
        };
        var message = new GameMessage
        {
            Chat = chat
        };
        SendMessage(message);
    }
    
    public void SendLoginMessage(string playerId)
    {
        var login = new LoginMessage()
        {
            PlayerId = playerId
        };
        var message = new GameMessage
        {
            Login = login
        };
        SendMessage(message);
    }

    private void SendMessage(GameMessage message)
    {
        if (tcpClient != null && tcpClient.Connected)
        {
            byte[] messageBytes = message.ToByteArray();
            byte[] lengthBytes = BitConverter.GetBytes(messageBytes.Length);

            // 메시지 길이를 먼저 보냅니다
            stream.Write(lengthBytes, 0, 4);
            // 메시지 본문을 보냅니다
            stream.Write(messageBytes, 0, messageBytes.Length);
        }
    }

    void OnDisable()
    {
        isRunning = false;
        if (receiveThread != null) receiveThread.Interrupt();
        if (stream != null) stream.Close();
        if (tcpClient != null) tcpClient.Close();
    }
}

 

 

'UNITY > 유니티게임스쿨' 카테고리의 다른 글

오브젝트 풀링  (0) 2024.09.30
csv Parser  (0) 2024.09.30
프로토버퍼  (0) 2024.09.23
쉐이더  (0) 2024.07.31
포스트 프로세싱  (0) 2024.07.30