跳转到主要内容

聊天系统概述

PlayNANOO聊天系统提供基于实时WebSocket的聊天功能。本文档介绍聊天系统的整体结构和使用方法。

系统 组成

聊天系统由3个核心类组成。

作用
ChatManager单例管理器,管理聊天服务器连接及API调用
ChatClient处理WebSocket连接及消息收发的核心类
ChatModels定义数据模型和事件监听器接口
必填 实现 说明

以上3个类是必须实现的类,可以根据用户的项目结构自由变更。

必须实现的要素:

  • 实现IChatListener接口的所有回调方法
  • 调用用于WebSocket连接的服务器列表API (PUT /chat/v20211101/server)
  • 实现HMAC-SHA256哈希认证逻辑 (参考ChatClient)

模块依赖 (Build.cs):

PublicDependencyModuleNames.AddRange(new string[]
{
"WebSockets", // WebSocket 연결
"OpenSSL" // HMAC-SHA256 해시
});

工作流程

1. ChatManager.Connect() 호출

2. 서버 목록 API 요청 (PUT /chat/v20211101/server)

3. 서버 목록 수신 후 랜덤 서버 선택

4. ChatClient가 WebSocket 연결 수행

5. 연결 성공 시 IChatListener.OnConnected() 호출

6. 채널 구독 및 메시지 송수신

第1阶段:实现监听器

要接收聊天事件,需要实现IChatListener接口。

// MyChatHandler.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ChatModels.h"
#include "MyChatHandler.generated.h"

UCLASS()
class YOURPROJECT_API AMyChatHandler : public AActor, public IChatListener
{
GENERATED_BODY()

public:
// IChatListener 구현
virtual void OnConnected() override
{
// 서버 연결 성공
UE_LOG(LogTemp, Log, TEXT("채팅 서버에 연결되었습니다."));
}

virtual void OnDisconnected() override
{
// 서버 연결 해제
UE_LOG(LogTemp, Log, TEXT("채팅 서버와 연결이 해제되었습니다."));
}

virtual void OnError(const FString& Code, const FString& Message) override
{
// 오류 발생
UE_LOG(LogTemp, Error, TEXT("오류: [%s] %s"), *Code, *Message);
}

virtual void OnChannels(const TArray<FChatChannelInfo>& Channels) override
{
// 채널 목록 수신
for (const auto& Ch : Channels)
{
UE_LOG(LogTemp, Log, TEXT("채널: %s, 접속자: %d명"), *Ch.Channel, Ch.Count);
}
}

virtual void OnSubscribed(const FChatUserInfo& User) override
{
// 사용자가 채널에 입장
UE_LOG(LogTemp, Log, TEXT("%s님이 입장했습니다."), *User.VisitorName);
}

virtual void OnUnSubscribed(const FChatUserInfo& User) override
{
// 사용자가 채널에서 퇴장
UE_LOG(LogTemp, Log, TEXT("%s님이 퇴장했습니다."), *User.VisitorName);
}

virtual void OnPublicMessage(const FChatUserInfo& Sender, const FString& Message) override
{
// 공개 메시지 수신
UE_LOG(LogTemp, Log, TEXT("[%s]: %s"), *Sender.VisitorName, *Message);
}

virtual void OnPrivateMessage(const FChatUserInfo& Sender, const FString& Message) override
{
// 귓속말 수신
UE_LOG(LogTemp, Log, TEXT("[귓속말][%s]: %s"), *Sender.VisitorName, *Message);
}

virtual void OnNotifyMessage(const FChatUserInfo& Sender, const FString& Message) override
{
// 시스템 알림 수신
UE_LOG(LogTemp, Log, TEXT("[시스템]: %s"), *Message);
}

virtual void OnPlayerOnline(const TArray<FChatPlayerInfo>& Players) override
{
// 플레이어 온라인 상태 수신
for (const auto& P : Players)
{
FString Status = (P.Online == TEXT("Y")) ? TEXT("온라인") : TEXT("오프라인");
UE_LOG(LogTemp, Log, TEXT("%s: %s"), *P.UserUniqueId, *Status);
}
}
};

2阶段: 聊天 服务器 连接

调用ChatManager::GetInstance().Connect()连接聊天服务器。

void AMyChatHandler::BeginPlay()
{
Super::BeginPlay();

// IChatListener를 구현한 객체를 전달
UChatManager::GetInstance()->Connect(this);
}

连接成功后将调用OnConnected()回调。

3阶段: 频道订阅

连接后订阅所需频道。

void AMyChatHandler::OnConnected()
{
// 글로벌 채널 구독
UChatManager::GetInstance()->Subscribe(TEXT("global"));

// 또는 특정 채널 구독
UChatManager::GetInstance()->Subscribe(TEXT("room_123"));
}

4阶段: 消息 发送

公开消息 发送

频道的所有 用户给 消息发送。

void AMyChatHandler::SendChatMessage(const FString& Text)
{
UChatManager::GetInstance()->SendPublicMessage(TEXT("global"), Text);
}

私聊 发送

仅向特定用户发送消息。

void AMyChatHandler::SendWhisper(const FString& TargetUserId, const FString& Text)
{
UChatManager::GetInstance()->SendPrivateMessage(TargetUserId, Text);
}

5阶段: 其他 功能

频道列表 查询

UChatManager::GetInstance()->GetChannels();
// 결과는 OnChannels() 콜백으로 수신

频道订阅 解除

UChatManager::GetInstance()->Unsubscribe(TEXT("global"));

玩家 在线 状态 查询

TArray<FString> UserIds = {TEXT("user_001"), TEXT("user_002"), TEXT("user_003")};
UChatManager::GetInstance()->GetPlayersOnline(UserIds);
// 결과는 OnPlayerOnline() 콜백으로 수신

连接 状态 确认

if (UChatManager::GetInstance()->IsConnected())
{
UE_LOG(LogTemp, Log, TEXT("채팅 서버에 연결되어 있습니다."));
}

全部 使用示例

// ChatExample.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ChatManager.h"
#include "ChatModels.h"
#include "ChatExample.generated.h"

UCLASS()
class YOURPROJECT_API AChatExample : public AActor, public IChatListener
{
GENERATED_BODY()

public:
virtual void BeginPlay() override
{
Super::BeginPlay();

// 1. 채팅 서버 연결
UChatManager::GetInstance()->Connect(this);
}

// 2. 연결 성공 시 채널 구독
virtual void OnConnected() override
{
UE_LOG(LogTemp, Log, TEXT("연결 성공!"));
UChatManager::GetInstance()->Subscribe(TEXT("global"));
}

// 3. 채널 입장 알림
virtual void OnSubscribed(const FChatUserInfo& User) override
{
UE_LOG(LogTemp, Log, TEXT("%s님이 입장했습니다."), *User.VisitorName);
}

// 4. 메시지 수신
virtual void OnPublicMessage(const FChatUserInfo& Sender, const FString& Message) override
{
UE_LOG(LogTemp, Log, TEXT("[%s]: %s"), *Sender.VisitorName, *Message);
}

// 5. 메시지 전송 (UI 버튼에서 호출)
UFUNCTION(BlueprintCallable, Category = "Chat")
void OnSendButtonClick()
{
UChatManager::GetInstance()->SendPublicMessage(TEXT("global"), TEXT("안녕하세요!"));
}

// 기타 콜백 구현
virtual void OnDisconnected() override { UE_LOG(LogTemp, Log, TEXT("연결 해제됨")); }
virtual void OnError(const FString& Code, const FString& Message) override
{
UE_LOG(LogTemp, Error, TEXT("[%s] %s"), *Code, *Message);
}
virtual void OnChannels(const TArray<FChatChannelInfo>& Channels) override {}
virtual void OnUnSubscribed(const FChatUserInfo& User) override
{
UE_LOG(LogTemp, Log, TEXT("%s님이 퇴장했습니다."), *User.VisitorName);
}
virtual void OnPrivateMessage(const FChatUserInfo& Sender, const FString& Message) override
{
UE_LOG(LogTemp, Log, TEXT("[귓속말] %s"), *Message);
}
virtual void OnNotifyMessage(const FChatUserInfo& Sender, const FString& Message) override
{
UE_LOG(LogTemp, Log, TEXT("[알림] %s"), *Message);
}
virtual void OnPlayerOnline(const TArray<FChatPlayerInfo>& Players) override {}
};

注意事项

  1. 在Tick中调用Service: ChatManager的Tick()中调用ChatClient.Service(),在主线程中处理WebSocket回调。直接修改ChatManager时需要保持此部分。

  2. 单例模式: ChatManager以单例方式实现,通过UChatManager::GetInstance()访问。关卡切换时也会保持。

  3. 断开连接: 应用退出时,ChatManager的析构函数会自动断开连接。