Go Version : 1.21.5 2024 컴퓨터공학과 캡스톤디자인 - Next Reality
이전 내용 : 2024-04-24-로그 데이터를 이용한 동기화 기획
UDP 테스트용 서버 제작
package main
import (
"fmt"
"net"
)
func main() {
// UDP 서버를 Listen할 주소 설정
address, err := net.ResolveUDPAddr("udp", ":8080")
if err != nil {
fmt.Println("Error resolving address:", err)
return
}
// UDP 연결 생성
connection, err := net.ListenUDP("udp", address)
if err != nil {
fmt.Println("Error listening:", err)
return
}
defer connection.Close()
fmt.Println("UDP server is listening on", address)
// 무한 루프를 통해 클라이언트로부터 메시지를 받음
buffer := make([]byte, 1024)
for {
n, clientAddress, err := connection.ReadFromUDP(buffer)
if err != nil {
fmt.Println("Error reading:", err)
continue
}
// 받은 메시지 출력
fmt.Println("Received message from", clientAddress, ":", string(buffer[:n]))
// 클라이언트에게 다시 메시지를 보냄
_, err = connection.WriteToUDP([]byte("Message received"), clientAddress)
if err != nil {
fmt.Println("Error replying:", err)
continue
}
}
}
접속 플레이어 관리
접속한 플레이어를 확인하기 위해, 플레이어의 주소(ip:port)와 플레이어 아이디를 키-값으로 짝지어서 저장함
// net address <-> user 용이하게 하기 위해 양방향으로 만듬
var AddrUser map[*net.UDPAddr]string = make(map[*net.UDPAddr]string)
var UserAddr map[string]*net.UDPAddr = make(map[string]*net.UDPAddr)
var UserMapid map[string]string = make(map[string]string) // 유저가 들어가있는 방 번호
var MapidUserList map[string][]string = make(map[string][]string) // 방 안에 있는 플레이어 리스트
스키마 수신 및 파싱
스키마(로그 데이터)에 대한 기획 (2024-04-24-로그 데이터를 이용한 동기화 기획)에 맞게 구현하기 위해, 우선 받아온 데이터에 대해 파싱하는 작업을 거치게 했음
package controller
import (
"strings"
)
// 모든 receiveMessage 는 commandName, senduserId, sendTime을 필수적으로 가지며,
// commandName에 따라 otherMessage의 길이가 달라질 수 있음.
// 따라서, 우선적으로 otherMessage의 길이를 검사함.
type ReceiveMessage struct {
CommandName string
SendUserId string
SendTime string
OtherMessage []string
}
func MessageParser(msg string) ReceiveMessage {
// fmt.Println("MessageParser messages : ", messages)
splitCommand := strings.Split(msg, "$")
parsingMessage := strings.Split(splitCommand[1], ";")
var _otherMessage []string = nil
if len(parsingMessage) > 2 {
_otherMessage = parsingMessage[2:]
}
recvMsg := ReceiveMessage{
CommandName: splitCommand[0],
SendUserId: parsingMessage[0],
SendTime: parsingMessage[1],
OtherMessage: _otherMessage,
}
return recvMsg
}
스키마 리스너 제작
스키마에서 주요 동작의 종류를 확인할 수 있는 부분은, 달러 표시($) 이전에 나타나는 커맨드 부분임. 리스너는 하나의 커맨드 종류 당 하나씩 존재하도록 했고
// Listener는 ReceiveMessage와 연결 정보를 받고
// 서버에 유효한 결과를 도출했는지 bool로 반환함
type listeners func(ReceiveMessage, *net.UDPAddr) bool
리스너라는 사용자 지정 타입을 만들고, String ←> listeners 관계가 되는 Map을 만듬
var ListenerMap = map[string]listeners{
"PlayerJoin": PlayerJoin,
"PlayerLeave": PlayerLeave,
"PlayerMove": PlayerMove,
}
커맨드에 따른 추가 메시지 검사
스키마는 커맨드의 종류에 따라 뒤에 붙는 메시지의 개수가 달라짐. 이를 검사하는 함수를 만들어 리스너 실행 초반에 검사하도록 함
func otherMessageLength(commandName string) int {
switch commandName {
case "PlayerJoin", "PlayerMove":
return 2
case "PlayerLeave":
return 0
}
return 0
}
PlayerJoin
플레이어가 방에 입장했을 때 서버로 송신하는 스키마다. 따라서, 서버에서는 해당 플레이어의 아이디와 주소를 접속자 리스트에 추가하는 작업이 필요함
func PlayerJoin(m ReceiveMessage, addr *net.UDPAddr) bool {
// PlayerJoin 메시지 형태 :
// PlayerJoin$sendUserId;sendTime;sendUserNickname;MapId
// otherMessage length: 2
if len(m.OtherMessage) == otherMessageLength(m.CommandName) {
mapid := m.OtherMessage[1]
if _, exist := UserAddr[m.SendUserId]; !exist {
UserAddr[m.SendUserId] = addr
AddrUser[addr] = m.SendUserId
UserMapid[m.SendUserId] = mapid
MapidUserList[mapid] = append(MapidUserList[mapid], m.SendUserId)
fmt.Println(
aurora.Sprintf(
aurora.Green("Success : User [%s] joined Map [%s]\n"), m.SendUserId, mapid))
return true
} else {
fmt.Println(
aurora.Sprintf(
aurora.Yellow("Error : User [%s] is already in this game.\n"), m.SendUserId))
return false
}
} else {
fmt.Println(
aurora.Sprintf(
aurora.Yellow("Error : PlayerJoin Message Length Error : need 2, received %d\n"), len(m.OtherMessage)))
return false
}
}
접속 플레이어 키-값 쌍에 플레이어가 없으면 접속 허용
PlayerLeave
func PlayerLeave(m ReceiveMessage, addr *net.UDPAddr) bool {
// PlayerLeave 형태 :
// PlayerLeave$sendUserId;sendTime
// otherMessage length : 0
if len(m.OtherMessage) == otherMessageLength(m.CommandName) {
if _, exist := UserAddr[m.SendUserId]; exist {
} else {
fmt.Println(
aurora.Sprintf(
aurora.Yellow("Error : Cannot Found User [%s]"), m.SendUserId))
return false
}
}
return false
}
접속 플레이어 키-값 쌍에 플레이어가 있으면 퇴장 허용
PlayerJoin
// PlayerMove 형태 :
// PlayerMove$sendUserId;sendTime;position;rotation
// otherMessage length : 2
func PlayerMove(m ReceiveMessage, addr *net.UDPAddr) bool {
return false
}
PlayerJoin Listener는 일단 만들어만 둠
현재까지의 서버 동작 과정
- 클라이언트가 전송한 String 스키마를 수신한다.
- String 스키마를 MessageParser()를 사용해 ReceiveMessage 형태로 파싱한다.
- ReceiveMessage 스키마의 CommandName에 맞는 리스너를 실행한다.