원하는 게임을 만들기 위해선 Tcp 전송에 대해서 좀더 알아 볼 필요가 있었다.
using UnityEngine; // UnityEngine 네임스페이스 사용
using System; // 시스템 관련 기능 사용
using System.Net.Sockets; // 소켓 통신을 위한 네임스페이스
using System.Collections.Generic; // 리스트 사용을 위한 네임스페이스
// 게임 네트워크 클라이언트 클래스
public class GameNetworkClient : MonoBehaviour
{
private TcpClient client; // TCP 클라이언트
private NetworkStream stream; // 네트워크 스트림
private byte[] receiveBuffer = new byte[1024]; // 수신 버퍼
private List<byte> messageBuffer = new List<byte>(); // 메시지 버퍼
private string serverIP = "127.0.0.1"; // 서버 IP 주소
private int serverPort = 3000; // 서버 포트 번호
// MonoBehaviour의 Start 메서드
void Start()
{
Debug.Log("시작 시도중..."); // 시작 로그
ConnectToServer(); // 서버 연결 시도
}
// MonoBehaviour의 OnDestroy 메서드
void OnDestroy()
{
Debug.Log("OnDestroy 호출됨"); // 종료 로그
DisconnectFromServer(); // 서버 연결 종료
}
// 서버에 연결하는 메서드
private void ConnectToServer()
{
try
{
Debug.Log($"서버 연결 시도 중... IP: {serverIP}, Port: {serverPort}"); // 서버 연결 로그
client = new TcpClient(); // TCP 클라이언트 인스턴스 생성
client.Connect(serverIP, serverPort); // 서버에 연결
Debug.Log("서버 연결 성공, 스트림 얻기 시도..."); // 연결 성공 로그
stream = client.GetStream(); // 네트워크 스트림 가져오기
Debug.Log("수신 시작 시도..."); // 수신 시작 로그
BeginReceive(); // 비동기 수신 시작
Debug.Log("서버에 연결됨"); // 연결 완료 로그
// 테스트 메시지 전송
Debug.Log("테스트 메시지 전송 시도..."); // 테스트 메시지 전송 로그
SendChatMessage("안녕하세요!"); // 채팅 메시지 전송
SendPosition(new Vector3(1.0f, 2.0f, 3.0f)); // 위치 정보 전송
}
catch (Exception e)
{
Debug.LogError($"서버 연결 실패: {e.Message}\n스택 트레이스: {e.StackTrace}"); // 연결 실패 로그
}
}
// 비동기 수신 시작 메서드
private void BeginReceive()
{
try
{
if (stream == null) // 스트림이 null인 경우
{
Debug.LogError("스트림이 null입니다!"); // 에러 로그
return; // 메서드 종료
}
stream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, OnReceiveCallback, null); // 비동기 읽기 시작
Debug.Log("비동기 읽기 시작됨"); // 비동기 읽기 시작 로그
}
catch (Exception e)
{
Debug.LogError($"수신 시작 실패: {e.Message}\n스택 트레이스: {e.StackTrace}"); // 수신 시작 실패 로그
}
}
// 수신 콜백 메서드
private void OnReceiveCallback(IAsyncResult ar)
{
try
{
Debug.Log("OnReceiveCallback 호출됨"); // 콜백 호출 로그
if (stream == null) // 스트림이 null인 경우
{
Debug.LogError("콜백에서 스트림이 null입니다!"); // 에러 로그
return; // 메서드 종료
}
int bytesRead = stream.EndRead(ar); // 읽은 바이트 수
Debug.Log($"받은 바이트 수: {bytesRead}"); // 수신된 바이트 수 로그
if (bytesRead <= 0) // 수신된 데이터가 없는 경우
{
Debug.LogWarning("받은 데이터가 없음, 연결 종료"); // 경고 로그
DisconnectFromServer(); // 연결 종료
return; // 메서드 종료
}
// 수신한 데이터를 메시지 버퍼에 추가
messageBuffer.AddRange(new ArraySegment<byte>(receiveBuffer, 0, bytesRead)); // 메시지 버퍼에 추가
ProcessMessages(); // 메시지 처리
BeginReceive(); // 다음 수신 대기
}
catch (ObjectDisposedException e) // 스트림이 이미 닫힌 경우
{
Debug.LogWarning($"스트림이 이미 닫혔습니다: {e.Message}"); // 경고 로그
}
catch (Exception e)
{
Debug.LogError($"수신 콜백 에러: {e.Message}\n스택 트레이스: {e.StackTrace}"); // 에러 로그
DisconnectFromServer(); // 연결 종료
}
}
// 메시지를 처리하는 메서드
private void ProcessMessages()
{
Debug.Log($"메시지 처리 시작. 버퍼 크기: {messageBuffer.Count}"); // 메시지 처리 시작 로그
while (messageBuffer.Count >= 4) // 최소 헤더 크기 확인
{
Debug.Log("메시지 언패킹 시도..."); // 메시지 언패킹 시도 로그
var (messageLength, messageType, payload) = MessageParser.UnpackMessage(messageBuffer.ToArray()); // 메시지 언패킹
Debug.Log($"메시지 길이: {messageLength}, 타입: {messageType}"); // 메시지 길이와 타입 로그
if (messageBuffer.Count >= messageLength + 4) // 전체 메시지를 수신한 경우
{
HandleMessage(messageType, payload); // 메시지 처리
messageBuffer.RemoveRange(0, messageLength + 4); // 처리된 메시지를 버퍼에서 제거
}
else // 메시지가 완전하지 않은 경우
{
Debug.Log("메시지가 완전하지 않음, 더 기다림"); // 메시지가 불완전하다는 로그
break; // 루프 종료
}
}
}
// 수신된 메시지를 처리하는 메서드
private void HandleMessage(MessageParser.MessageType messageType, byte[] payload)
{
try
{
Debug.Log($"메시지 타입 {messageType} 처리 시도"); // 메시지 타입 처리 시도 로그
switch (messageType)
{
case MessageParser.MessageType.Chat: // 채팅 메시지 타입 처리
var chatMessage = MessageParser.DeserializeChatMessage(payload); // 채팅 메시지 역직렬화
Debug.Log($"[채팅] {chatMessage.userName}: {chatMessage.content}"); // 채팅 메시지 로그
break;
case MessageParser.MessageType.ServerResponse: // 서버 응답 메시지 타입 처리
string response = System.Text.Encoding.UTF8.GetString(payload); // 응답 메시지 역직렬화
Debug.Log($"[서버 응답] {response}"); // 서버 응답 로그
break;
case MessageParser.MessageType.Position: // 위치 메시지 타입 처리
var position = MessageParser.DeserializePosition(payload); // 위치 정보 역직렬화
Debug.Log($"[위치] X: {position.x:F2}, Y: {position.y:F2}, Z: {position.z:F2}"); // 위치 정보 로그
break;
default: // 알 수 없는 메시지 타입 처리
Debug.Log($"알 수 없는 메시지 타입: {messageType}"); // 알 수 없는 메시지 타입 로그
break;
}
}
catch (Exception e)
{
Debug.LogError($"메시지 처리 중 에러: {e.Message}\n스택 트레이스: {e.StackTrace}"); // 메시지 처리 중 에러 로그
}
}
// 채팅 메시지를 전송하는 메서드
public void SendChatMessage(string message)
{
try
{
Debug.Log($"채팅 메시지 전송 시도: {message}"); // 채팅 메시지 전송 시도 로그
byte[] payload = MessageParser.SerializeChatMessage("Player", message); // 채팅 메시지 직렬화
SendMessage(MessageParser.MessageType.Chat, payload); // 메시지 전송
}
catch (Exception e)
{
Debug.LogError($"채팅 전송 실패: {e.Message}\n스택 트레이스: {e.StackTrace}"); // 채팅 전송 실패 로그
}
}
// 위치 정보를 전송하는 메서드
public void SendPosition(Vector3 position)
{
try
{
Debug.Log($"위치 전송 시도: {position}"); // 위치 전송 시도 로그
byte[] payload = MessageParser.SerializePosition(position); // 위치 정보 직렬화
SendMessage(MessageParser.MessageType.Position, payload); // 메시지 전송
}
catch (Exception e)
{
Debug.LogError($"위치 전송 실패: {e.Message}\n스택 트레이스: {e.StackTrace}"); // 위치 전송 실패 로그
}
}
// 메시지를 서버로 전송하는 메서드
private void SendMessage(MessageParser.MessageType messageType, byte[] payload)
{
try
{
if (stream == null) // 스트림이 null인 경우
{
Debug.LogError("스트림이 null이라 메시지를 보낼 수 없습니다!"); // 에러 로그
return; // 메서드 종료
}
byte[] message = MessageParser.PackMessage(messageType, payload); // 메시지 패킹
stream.Write(message, 0, message.Length); // 메시지를 스트림에 전송
Debug.Log($"메시지 타입 {messageType} 전송 완료, 크기: {message.Length}"); // 메시지 전송 완료 로그
}
catch (Exception e)
{
Debug.LogError($"메시지 전송 실패: {e.Message}\n스택 트레이스: {e.StackTrace}"); // 메시지 전송 실패 로그
}
}
// 서버와의 연결을 종료하는 메서드
private void DisconnectFromServer()
{
Debug.Log("서버 연결 종료 시도..."); // 연결 종료 시도 로그
if (stream != null) // 스트림이 존재하는 경우
{
Debug.Log("스트림 닫기"); // 스트림 닫기 로그
stream.Close(); // 스트림 종료
stream = null; // 스트림 null로 설정
}
if (client != null) // 클라이언트가 존재하는 경우
{
Debug.Log("클라이언트 닫기"); // 클라이언트 닫기 로그
client.Close(); // 클라이언트 종료
client = null; // 클라이언트 null로 설정
}
Debug.Log("서버 연결 종료 완료"); // 연결 종료 완료 로그
}
// 애플리케이션 종료 시 호출되는 메서드
void OnApplicationQuit()
{
Debug.Log("어플리케이션 종료"); // 애플리케이션 종료 로그
DisconnectFromServer(); // 서버 연결 종료
}
}
public class BinaryWriter
{
private byte[] buffer; // 데이터를 저장할 버퍼
private int position; // 현재 쓰기 위치
private int capacity; // 버퍼의 총 크기
// 초기 크기를 지정하여 생성자 호출
public BinaryWriter(int initialSize = 1024)
{
buffer = new byte[initialSize]; // 초기 버퍼 생성
position = 0; // 현재 쓰기 위치 초기화
capacity = initialSize; // 버퍼 용량 설정
}
// 필요한 용량을 확보하는 메서드
private void EnsureCapacity(int needed)
{
// 현재 위치와 필요한 용량이 버퍼 용량 이내인지 확인
if (position + needed <= capacity) return;
// 필요한 용량에 따라 새로운 버퍼 크기 결정
capacity = System.Math.Max(capacity * 2, position + needed);
byte[] newBuffer = new byte[capacity]; // 새로운 버퍼 생성
System.Buffer.BlockCopy(buffer, 0, newBuffer, 0, position); // 기존 데이터를 새로운 버퍼로 복사
buffer = newBuffer; // 버퍼 교체
}
// int 값을 버퍼에 쓰는 메서드
public void WriteInt32(int value)
{
EnsureCapacity(4); // 4바이트를 쓰기 위해 용량 확보
System.BitConverter.GetBytes(value).CopyTo(buffer, position); // 값을 바이트 배열로 변환하여 버퍼에 기록
position += 4; // 현재 위치 업데이트
}
// float 값을 버퍼에 쓰는 메서드
public void WriteFloat(float value)
{
EnsureCapacity(4); // 4바이트를 쓰기 위해 용량 확보
System.BitConverter.GetBytes(value).CopyTo(buffer, position); // 값을 바이트 배열로 변환하여 버퍼에 기록
position += 4; // 현재 위치 업데이트
}
// string 값을 버퍼에 쓰는 메서드 (길이 + 데이터)
public void WriteString(string value)
{
if (string.IsNullOrEmpty(value)) value = ""; // null 또는 빈 문자열 처리
byte[] strBytes = System.Text.Encoding.UTF8.GetBytes(value); // 문자열을 UTF-8 바이트 배열로 변환
WriteInt32(strBytes.Length); // 문자열 길이 기록
WriteBytes(strBytes); // 문자열 데이터 기록
}
// 바이트 배열을 버퍼에 쓰는 메서드
public void WriteBytes(byte[] data)
{
if (data == null || data.Length == 0) return; // null 또는 빈 배열 처리
EnsureCapacity(data.Length); // 배열 길이를 쓰기 위해 용량 확보
System.Buffer.BlockCopy(data, 0, buffer, position, data.Length); // 바이트 배열을 버퍼에 기록
position += data.Length; // 현재 위치 업데이트
}
// 최종 버퍼를 가져오는 메서드
public byte[] GetBuffer()
{
byte[] result = new byte[position]; // 현재까지 기록된 데이터를 위한 배열 생성
System.Buffer.BlockCopy(buffer, 0, result, 0, position); // 버퍼의 유효 데이터를 복사
return result; // 최종 바이트 배열 반환
}
}
public class BinaryReader
{
private byte[] buffer; // 읽을 데이터가 있는 버퍼
private int position; // 현재 읽기 위치
// 버퍼를 지정하여 생성자 호출
public BinaryReader(byte[] data)
{
buffer = data; // 읽을 데이터 설정
position = 0; // 현재 읽기 위치 초기화
}
// int 값을 읽는 메서드
public int ReadInt32()
{
CheckCanRead(4); // 남은 데이터가 충분한지 확인
int value = System.BitConverter.ToInt32(buffer, position); // 현재 위치에서 int 값을 읽음
position += 4; // 현재 위치 업데이트
return value; // 읽은 값 반환
}
// float 값을 읽는 메서드
public float ReadFloat()
{
CheckCanRead(4); // 남은 데이터가 충분한지 확인
float value = System.BitConverter.ToSingle(buffer, position); // 현재 위치에서 float 값을 읽음
position += 4; // 현재 위치 업데이트
return value; // 읽은 값 반환
}
// string 값을 읽는 메서드 (길이 + 데이터)
public string ReadString()
{
int length = ReadInt32(); // 문자열 길이 읽기
CheckCanRead(length); // 남은 데이터가 충분한지 확인
string value = System.Text.Encoding.UTF8.GetString(buffer, position, length); // 문자열 데이터 읽기
position += length; // 현재 위치 업데이트
return value; // 읽은 문자열 반환
}
// 지정한 길이만큼 바이트 배열을 읽는 메서드
public byte[] ReadBytes(int length)
{
if (length <= 0) return new byte[0]; // 0 또는 음수 길이 처리
CheckCanRead(length); // 남은 데이터가 충분한지 확인
byte[] result = new byte[length]; // 결과 배열 생성
System.Buffer.BlockCopy(buffer, position, result, 0, length); // 버퍼에서 데이터를 복사
position += length; // 현재 위치 업데이트
return result; // 읽은 바이트 배열 반환
}
// 남은 데이터가 충분한지 확인하는 메서드
private void CheckCanRead(int count)
{
if (position + count > buffer.Length) // 읽을 수 있는 데이터가 버퍼를 초과하는 경우
throw new System.Exception($"버퍼 읽기 실패: 필요한 크기 {count}, 남은 크기 {buffer.Length - position}"); // 예외 발생
}
}
using UnityEngine;
// 메시지 파서 클래스
public class MessageParser
{
// 메시지 타입을 정의하는 열거형
public enum MessageType
{
None = 0, // 기본값
Chat = 1, // 채팅 메시지
ServerResponse = 2, // 서버 응답 메시지
Position = 3 // 위치 정보 메시지
}
// 채팅 메시지 구조체 - 사용자 이름과 내용을 포함
public struct ChatMessage
{
public string userName; // 채팅을 보낸 사용자 이름
public string content; // 채팅 내용
}
// 위치 정보 구조체 - 3D 위치 좌표
public struct PositionMessage
{
public float x; // X 좌표
public float y; // Y 좌표
public float z; // Z 좌표
}
// 채팅 메시지를 직렬화하여 바이트 배열로 변환
public static byte[] SerializeChatMessage(string userName, string content)
{
var writer = new BinaryWriter(); // BinaryWriter 인스턴스 생성
writer.WriteString(userName); // 사용자 이름을 기록
writer.WriteString(content); // 채팅 내용을 기록
return writer.GetBuffer(); // 직렬화된 바이트 배열 반환
}
// 바이트 배열에서 채팅 메시지를 역직렬화하여 구조체로 변환
public static ChatMessage DeserializeChatMessage(byte[] payload)
{
var reader = new BinaryReader(payload); // BinaryReader 인스턴스 생성
return new ChatMessage
{
userName = reader.ReadString(), // 사용자 이름 읽기
content = reader.ReadString() // 채팅 내용 읽기
};
}
// 위치 정보를 직렬화하여 바이트 배열로 변환
public static byte[] SerializePosition(Vector3 position)
{
var writer = new BinaryWriter(); // BinaryWriter 인스턴스 생성
writer.WriteFloat(position.x); // X 좌표를 기록
writer.WriteFloat(position.y); // Y 좌표를 기록
writer.WriteFloat(position.z); // Z 좌표를 기록
return writer.GetBuffer(); // 직렬화된 바이트 배열 반환
}
// 바이트 배열에서 위치 정보를 역직렬화하여 구조체로 변환
public static PositionMessage DeserializePosition(byte[] payload)
{
var reader = new BinaryReader(payload); // BinaryReader 인스턴스 생성
return new PositionMessage
{
x = reader.ReadFloat(), // X 좌표 읽기
y = reader.ReadFloat(), // Y 좌표 읽기
z = reader.ReadFloat() // Z 좌표 읽기
};
}
// 메시지를 패킹하여 전체 메시지 생성 (길이 + 타입 + 페이로드)
public static byte[] PackMessage(MessageType type, byte[] payload)
{
var writer = new BinaryWriter(); // BinaryWriter 인스턴스 생성
writer.WriteInt32(payload.Length + 4); // 전체 메시지 길이 (페이로드 + 타입)
writer.WriteInt32((int)type); // 메시지 타입 기록
writer.WriteBytes(payload); // 페이로드 기록
return writer.GetBuffer(); // 최종 메시지 바이트 배열 반환
}
// 바이트 배열에서 메시지를 언패킹하여 길이, 타입, 페이로드 반환
public static (int messageLength, MessageType messageType, byte[] payload) UnpackMessage(byte[] message)
{
var reader = new BinaryReader(message); // BinaryReader 인스턴스 생성
int messageLength = reader.ReadInt32(); // 메시지 길이 읽기
MessageType messageType = (MessageType)reader.ReadInt32(); // 메시지 타입 읽기
byte[] payload = reader.ReadBytes(messageLength - 4); // 페이로드 읽기
return (messageLength, messageType, payload); // 메시지 길이, 타입, 페이로드 반환
}
}
// BinaryWriter 클래스: 데이터를 바이너리 형식으로 작성하는 데 사용
export class BinaryWriter {
constructor(initialSize = 1024) {
this.buffer = Buffer.alloc(initialSize); // 초기 버퍼 생성
this.position = 0; // 현재 쓰기 위치 초기화
this.capacity = initialSize; // 버퍼의 총 용량 설정
}
// 필요한 용량을 확보하는 메서드
ensureCapacity(needed) {
// 현재 위치와 필요한 용량이 버퍼 용량 이내인지 확인
if (this.position + needed <= this.capacity) return;
// 필요한 용량에 따라 새로운 버퍼 크기 결정
this.capacity = Math.max(this.capacity * 2, this.position + needed);
const newBuffer = Buffer.alloc(this.capacity); // 새로운 버퍼 생성
this.buffer.copy(newBuffer, 0, 0, this.position); // 기존 데이터를 새로운 버퍼로 복사
this.buffer = newBuffer; // 버퍼 교체
}
// int32 값을 버퍼에 쓰는 메서드
writeInt32(value) {
this.ensureCapacity(4); // 4바이트를 쓰기 위해 용량 확보
this.buffer.writeInt32LE(value, this.position); // 리틀 엔디안 방식으로 값 기록
this.position += 4; // 현재 위치 업데이트
}
// float 값을 버퍼에 쓰는 메서드
writeFloat(value) {
this.ensureCapacity(4); // 4바이트를 쓰기 위해 용량 확보
this.buffer.writeFloatLE(value, this.position); // 리틀 엔디안 방식으로 값 기록
this.position += 4; // 현재 위치 업데이트
}
// 문자열을 버퍼에 쓰는 메서드 (길이 + 데이터)
writeString(value) {
const str = value || ""; // null 처리
const strBuffer = Buffer.from(str, 'utf8'); // 문자열을 UTF-8 바이트 배열로 변환
this.writeInt32(strBuffer.length); // 문자열 길이 기록
this.writeBytes(strBuffer); // 문자열 데이터 기록
}
// 바이트 배열을 버퍼에 쓰는 메서드
writeBytes(data) {
if (!data || data.length === 0) return; // null 또는 빈 배열 처리
this.ensureCapacity(data.length); // 배열 길이를 쓰기 위해 용량 확보
data.copy(this.buffer, this.position); // 바이트 배열을 버퍼에 기록
this.position += data.length; // 현재 위치 업데이트
}
// 최종 버퍼를 가져오는 메서드
getBuffer() {
return Buffer.alloc(this.position).fill(this.buffer, 0, this.position); // 유효한 데이터로 새 버퍼 생성
}
}
// BinaryReader 클래스: 바이너리 형식으로 저장된 데이터를 읽는 데 사용
export class BinaryReader {
constructor(buffer) {
this.buffer = buffer; // 읽을 데이터가 있는 버퍼
this.position = 0; // 현재 읽기 위치 초기화
}
// 남은 데이터가 충분한지 확인하는 메서드
checkCanRead(count) {
if (this.position + count > this.buffer.length) {
throw new Error(`버퍼 읽기 실패: 필요한 크기 ${count}, 남은 크기 ${this.buffer.length - this.position}`); // 예외 발생
}
}
// int32 값을 읽는 메서드
readInt32() {
this.checkCanRead(4); // 남은 데이터가 충분한지 확인
const value = this.buffer.readInt32LE(this.position); // 현재 위치에서 int 값을 읽음
this.position += 4; // 현재 위치 업데이트
return value; // 읽은 값 반환
}
// float 값을 읽는 메서드
readFloat() {
this.checkCanRead(4); // 남은 데이터가 충분한지 확인
const value = this.buffer.readFloatLE(this.position); // 현재 위치에서 float 값을 읽음
this.position += 4; // 현재 위치 업데이트
return value; // 읽은 값 반환
}
// 문자열을 읽는 메서드 (길이 + 데이터)
readString() {
const length = this.readInt32(); // 문자열 길이 읽기
this.checkCanRead(length); // 남은 데이터가 충분한지 확인
const value = this.buffer.toString('utf8', this.position, this.position + length); // 문자열 데이터 읽기
this.position += length; // 현재 위치 업데이트
return value; // 읽은 문자열 반환
}
// 지정한 길이만큼 바이트 배열을 읽는 메서드
readBytes(length) {
if (length <= 0) return Buffer.alloc(0); // 0 또는 음수 길이 처리
this.checkCanRead(length); // 남은 데이터가 충분한지 확인
const result = Buffer.alloc(length); // 결과 배열 생성
this.buffer.copy(result, 0, this.position, this.position + length); // 버퍼에서 데이터를 복사
this.position += length; // 현재 위치 업데이트
return result; // 읽은 바이트 배열 반환
}
}
import { BinaryWriter, BinaryReader } from './binaryHelper.js'; // BinaryWriter와 BinaryReader 가져오기
// 메시지 타입을 정의하는 객체
export const MessageType = {
NONE: 0, // 기본값
CHAT: 1, // 채팅 메시지
SERVER_RESPONSE: 2, // 서버 응답 메시지
POSITION: 3 // 위치 정보 메시지
};
// 메시지 파서 클래스
export class MessageParser {
// 채팅 메시지를 직렬화하여 바이트 배열로 변환
static serializeChatMessage(userName, content) {
const writer = new BinaryWriter(); // BinaryWriter 인스턴스 생성
writer.writeString(userName); // 사용자 이름을 UTF-8 문자열로 기록
writer.writeString(content); // 채팅 내용을 UTF-8 문자열로 기록
return writer.getBuffer(); // 직렬화된 바이트 배열 반환
}
// 바이트 배열에서 채팅 메시지를 역직렬화하여 객체로 변환
static deserializeChatMessage(payload) {
const reader = new BinaryReader(payload); // BinaryReader 인스턴스 생성
return {
userName: reader.readString(), // 사용자 이름 읽기
content: reader.readString() // 채팅 내용 읽기
};
}
// 위치 정보를 직렬화하여 바이트 배열로 변환
static serializePosition(x, y, z) {
const writer = new BinaryWriter(); // BinaryWriter 인스턴스 생성
writer.writeFloat(x); // X 좌표를 기록
writer.writeFloat(y); // Y 좌표를 기록
writer.writeFloat(z); // Z 좌표를 기록
return writer.getBuffer(); // 직렬화된 바이트 배열 반환
}
// 바이트 배열에서 위치 정보를 역직렬화하여 객체로 변환
static deserializePosition(payload) {
const reader = new BinaryReader(payload); // BinaryReader 인스턴스 생성
return {
x: reader.readFloat(), // X 좌표 읽기
y: reader.readFloat(), // Y 좌표 읽기
z: reader.readFloat() // Z 좌표 읽기
};
}
// 메시지를 패킹하여 전체 메시지 생성 (길이 + 타입 + 페이로드)
static packMessage(type, payload) {
const writer = new BinaryWriter(); // BinaryWriter 인스턴스 생성
writer.writeInt32(payload.length + 4); // 전체 메시지 길이 (페이로드 + 타입)
writer.writeInt32(type); // 메시지 타입 기록
writer.writeBytes(payload); // 페이로드 기록
return writer.getBuffer(); // 최종 메시지 바이트 배열 반환
}
// 바이트 배열에서 메시지를 언패킹하여 길이, 타입, 페이로드 반환
static unpackMessage(message) {
const reader = new BinaryReader(message); // BinaryReader 인스턴스 생성
const messageLength = reader.readInt32(); // 메시지 길이 읽기
const messageType = reader.readInt32(); // 메시지 타입 읽기
const payload = reader.readBytes(messageLength - 4); // 페이로드 읽기
return { messageLength, messageType, payload }; // 메시지 길이, 타입, 페이로드 반환
}
}
// server.js
import net from 'net'; // net 모듈 가져오기
import { MessageParser, MessageType } from './messageParser.js'; // 메시지 파서와 타입 가져오기
// 연결된 클라이언트들을 저장할 Set
const connectedClients = new Set();
// TCP 서버 생성
const server = net.createServer((socket) => {
console.log('클라이언트가 연결됨'); // 클라이언트 연결 로그
// 클라이언트 추가
connectedClients.add(socket);
// 버퍼를 저장할 변수
let buffer = Buffer.alloc(0); // 초기 빈 버퍼 생성
// 연결된 클라이언트에게 환영 메시지 전송
sendChatMessage(socket, "Server", "환영합니다! 서버에 연결되었습니다.");
// 테스트용 메시지 전송
setInterval(() => {
// 랜덤 위치 생성
const x = Math.random() * 100;
const y = Math.random() * 100;
const z = Math.random() * 100;
sendPosition(socket, x, y, z);
}, 5000);
// 데이터 수신 이벤트
socket.on('data', (data) => {
try {
// 받은 데이터를 버퍼에 추가
buffer = Buffer.concat([buffer, data]);
// 메시지 처리
while (buffer.length >= 8) { // 헤더의 최소 크기 (길이 4 + 타입 4)
// 메시지 길이 확인
const messageLength = buffer.readInt32LE(0);
// 메시지 길이가 비정상적인 경우
if (messageLength <= 0 || messageLength > 1024 * 1024) {
console.error('Invalid message length:', messageLength);
buffer = Buffer.alloc(0); // 버퍼 초기화
break;
}
// 전체 메시지를 다 받았는지 확인
if (buffer.length < messageLength + 4) {
break; // 전체 메시지를 수신할 때까지 대기
}
try {
// 메시지 파싱 시도
const { messageType, payload } = MessageParser.unpackMessage(buffer);
// 메시지 타입에 따른 처리
handleMessage(messageType, payload, socket);
} catch (parseError) {
console.error('메시지 파싱 에러:', parseError); // 파싱 에러 로그
}
// 처리한 메시지를 버퍼에서 제거
buffer = buffer.subarray(messageLength + 4); // 처리된 메시지 길이만큼 버퍼 업데이트
}
} catch (error) {
console.error('데이터 처리 중 에러:', error); // 데이터 처리 중 에러 로그
buffer = Buffer.alloc(0); // 버퍼 초기화
}
});
// 연결 종료 이벤트
socket.on('end', () => {
connectedClients.delete(socket); // Set에서 클라이언트 제거
console.log('클라이언트 연결 종료'); // 클라이언트 연결 종료 로그
});
// 에러 이벤트
socket.on('error', (err) => {
connectedClients.delete(socket); // Set에서 클라이언트 제거
console.error('소켓 에러:', err); // 소켓 에러 로그
});
});
// 채팅 메시지 전송 함수
function sendChatMessage(socket, userName, content) {
try {
const payload = MessageParser.serializeChatMessage(userName, content);
const packedMessage = MessageParser.packMessage(MessageType.CHAT, payload);
socket.write(packedMessage);
console.log(`채팅 메시지 전송: ${userName}: ${content}`);
} catch (error) {
console.error('채팅 메시지 전송 중 에러:', error);
}
}
// 위치 정보 전송 함수
function sendPosition(socket, x, y, z) {
try {
const payload = MessageParser.serializePosition(x, y, z);
const packedMessage = MessageParser.packMessage(MessageType.POSITION, payload);
socket.write(packedMessage);
console.log(`위치 정보 전송: (${x}, ${y}, ${z})`);
} catch (error) {
console.error('위치 정보 전송 중 에러:', error);
}
}
// 모든 클라이언트에게 브로드캐스트
function broadcast(messageType, payload) {
for (const client of connectedClients) {
try {
const packedMessage = MessageParser.packMessage(messageType, payload);
client.write(packedMessage);
} catch (error) {
console.error('브로드캐스트 중 에러:', error);
}
}
}
// 메시지 처리 함수
function handleMessage(messageType, payload, socket) {
try {
switch (messageType) {
case MessageType.CHAT: { // 채팅 메시지 타입 처리
const chatMessage = MessageParser.deserializeChatMessage(payload); // 메시지 역직렬화
console.log('받은 메시지:', chatMessage); // 받은 메시지 로그
// 모든 클라이언트에게 채팅 메시지 브로드캐스트
broadcast(MessageType.CHAT, payload);
break;
}
case MessageType.POSITION: { // 위치 메시지 타입 처리
const position = MessageParser.deserializePosition(payload); // 위치 데이터 역직렬화
console.log('받은 위치:', position); // 받은 위치 로그
// 다른 클라이언트들에게 위치 정보 전달
for (const client of connectedClients) {
if (client !== socket) {
sendPosition(client, position.x, position.y, position.z);
}
}
break;
}
default:
console.log('알 수 없는 메시지 타입:', messageType); // 알 수 없는 메시지 타입 로그
break;
}
} catch (error) {
console.error('메시지 처리 중 에러:', error); // 메시지 처리 중 에러 로그
}
}
// 서버 응답 전송
function sendServerResponse(socket, message) {
try {
const payload = Buffer.from(message, 'utf8'); // 메시지를 UTF-8로 변환
const packedMessage = MessageParser.packMessage(MessageType.SERVER_RESPONSE, payload); // 메시지 패킹
socket.write(packedMessage); // 클라이언트에게 응답 전송
} catch (error) {
console.error('응답 전송 중 에러:', error); // 응답 전송 중 에러 로그
}
}
// 서버 시작
const PORT = 3000; // 서버 포트 설정
server.listen(PORT, () => {
console.log(`서버가 포트 ${PORT}에서 실행 중입니다.`); // 서버 실행 로그
});
'TIL' 카테고리의 다른 글
TIL_2025-01-16 (0) | 2025.01.17 |
---|---|
TIL_2025-01-15 (0) | 2025.01.16 |
TIL_2025-01-10 (0) | 2025.01.10 |
TIL_2025-01-09 (0) | 2025.01.10 |
TIL_2025-01-08 (0) | 2025.01.08 |
댓글