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();
}
}