시연 영상
핵심 코드 설명
class Chat_Client
{
public string Type { get; set; } // 무슨 종류 데이터를 보내는 건지 (온라인 유저, 메시지 등)
public string Target { get; set; } // 전체 채팅인지, 1:1 채팅인지 등
public int Number { get; set; } // 방 번호
public string Sender { get; set; } // 보낸 유저 이름
public string Message { get; set; } // 보낸 메시지 내용
public List<string> Users { get; set; } // 접속한 유저 명단
public string RoomTitle { get; set; } // 채팅방 이름
}
class Chat
{
public string Type { get; set; } // 무슨 종류 데이터를 보내는 건지 (온라인 유저, 메시지 등)
public string Target { get; set; } // 전체 채팅인지, 1:1 채팅인지 등
public int Number { get; set; } // 방 번호
public string Sender { get; set; } // 보낸 유저 이름
public string Message { get; set; } // 보낸 메시지 내용
public List<string> Users { get; set; } // 접속한 유저 명단
public string RoomTitle { get; set; } // 채팅방 이름
}
서버와 클라이언트 프로젝트 각각 클래스 파일을 하나씩 만들어줍니다.
주고 받을 데이터 종류가 여러개일텐데, 어울리는 클래스의 변수에 저장해줍니다.
private void SendChatInfo()
{
if (ClientSocket == null || !ClientSocket.Connected)
return;
try
{
// 1. 전송할 데이터 엔터티 객체에 준비
var info = new Chat_Client
{
Type = "Message",
Target = chatSelect.Text,
Number = RoomNumber,
Sender = user.Name,
Message = chatBox.Text
};
// 2. 객체를 json 문자열로 직렬화
string json = JsonConvert.SerializeObject(info);
// 3. 문자열 byte 배열로 변환
byte[] bytesToSend = Encoding.UTF8.GetBytes(json);
// 4. SocketAsyncEventArgs 객체에 전송할 데이터 설정
var args = new SocketAsyncEventArgs();
args.SetBuffer(bytesToSend, 0, bytesToSend.Length);
// 5. 비동기적으로 전송
ClientSocket.SendAsync(args);
}
catch (Exception ex)
{
MessageBox.Show($"메시지 전송 중 오류 발생: {ex.Message}");
}
}
그리고, 지역변수를 호출해 클래스를 동적할당 시키고 클래스의 변수를 프로퍼티를 이용해 수정해줍니다.
수정한 데이터는 직렬화해서 서버로 보냅니다.
현재는 채팅을 보낼 때의 상황인데,
private void CreateRoom_Button_Click(object sender, RoutedEventArgs e)
{
try
{
if (chatTitle.Text.Length == 0)
{
MessageBox.Show("채팅방 이름을 정해주세요!!");
return;
}
string jsonList = JsonConvert.SerializeObject(new Chat_Client
{
Type = "Create",
Sender = host.Name,
Users = userList_create,
Message = $"{host.Name} 님이 {chatTitle.Text} 채팅방을 개설하셨습니다.",
RoomTitle = chatTitle.Text
});
byte[] bytesToSend = Encoding.UTF8.GetBytes(jsonList);
var args = new SocketAsyncEventArgs();
args.SetBuffer(bytesToSend, 0, bytesToSend.Length);
// 비동기적으로 전송
socket.SendAsync(args);
MessageBox.Show("채팅방 개설 완료!");
Close();
}
catch (Exception ex)
{
MessageBox.Show($"채팅방 개설 중 오류 발생: {ex.Message}");
}
}
채팅방을 만들 때의 상황의 경우를 추가로 보여드리며 예시를 들자면 다음과 같습니다.
Type 명이 바뀌고 메시지에서는 없었던 변수들의 값을 수정해주는 모습입니다.
private void DataReceived(object sender, SocketAsyncEventArgs e)
{
// 변수 다시 선언, 넘어온 소켓 데이터 받아서 집어넣음
Socket ClientSocket = (Socket)e.UserToken;
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
// 1. 도착한 바이트 배열을 Json 문자열로 변환
string json = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);
// 2. Json 문자열을 객체로 역직렬화
try
{
// 예외) 클라이언트가 연결될 때 계정 정보 받아오는 부분
if (userInfo.ContainsKey(ClientSocket) == false)
{
// 클라이언트에서 가져온 계정 정보를 지역 변수에 담기
var account = JsonConvert.DeserializeObject<Account>(json);
// lock 이용해서 딕셔너리에 가져온 계정 정보 추가
lock (lockObject)
{
userInfo.Add(ClientSocket, account);
}
// 추가했다는 것은, 입장과 똑같기 때문에 log로 남김
AddLog($"{account.Name} 님 입장");
// 온라인 유저 리스트에 담기
Dispatcher.Invoke(() =>
{
onlineUserList.Add($"{account.Name}");
});
SendOnlineUserList(ClientSocket);
}
else if (userInfo.ContainsKey(ClientSocket) && e.BytesTransferred > 0)
{
var userChat = JsonConvert.DeserializeObject<Chat>(json);
// 3. 객체의 내용을 UI에 반영, 클라이언트에 다시 보내기
AddLog($"{userChat.Target},{userChat.Sender},{userChat.Number},{userChat.Message}");
if (userChat.Type == "Message")
{
HandlingMessage(json, userChat);
}
else if (userChat.Type == "Create")
{
CreateChatRoom(json, userChat);
}
//// 4. 채팅 데이터베이스에 저장
//using (SQLiteConnection connection = new SQLiteConnection(App.databasePath))
//{
// // Chat 클래스 정의 기반으로 테이블 생성
// connection.CreateTable<Chat>();
// // UI 컨트롤에 입력된 데이터를 chat 객체 형태로 테이블에 삽입
// connection.Insert(userChat);
//}
}
}
catch (Exception ex)
{
MessageBox.Show($"DataReceived 메소드에서 오류 발생! : {ex.Message}");
}
// 4. 다시 수신 작업 수행
ReceiveInfo(ClientSocket);
}
서버입니다.
아까 넘긴 데이터들을 if문을 활용해 구별해주고, 구별한 조건문에 따라 메소드를 따로 만들어줍니다.
이 메소드들은 공통적으로, 서버에서 데이터를 활용하고 다시 클라이언트에게 받은 데이터를 보내줍니다.
private void ControlReceived(object sender, SocketAsyncEventArgs e)
{
try
{
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
// 서버에서 받은 JSON 데이터를 문자열로 변환
string json = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);
var receivedData = JsonConvert.DeserializeObject<dynamic>(json);
// dynamic 형태로 받은 데이터 string으로 변환
string serverSender = receivedData.Sender.ToString();
string serverMessage = receivedData.Message.ToString();
string serverRoomTitle = receivedData.RoomTitle.ToString();
if (receivedData.Type == "Message") // 문자열 용
{
if (receivedData.Target == "전체")
{
if (receivedData.Number == 0) // 채팅일 경우
{
// UI 스레드에서 채팅 리스트에 추가
Dispatcher.Invoke(() =>
{
messageList.Add($"({receivedData.Target}) {serverSender} : {serverMessage}");
});
}
else if (receivedData.Number == 1) // 온라인 유저 삭제
{
Dispatcher.Invoke(() =>
{
onlineUserList.Remove(serverSender);
messageList.Add(serverMessage);
});
}
}
else // 그 외 채팅방
{
Dispatcher.Invoke(() =>
{
messageList.Add($"({receivedData.Target}) {serverSender} : {serverMessage}");
});
}
}
else if (receivedData.Type == "UserList") // 리스트 용
{
Dispatcher.Invoke(() =>
{
onlineUserList.Clear();
foreach (var user in receivedData.Users)
{
onlineUserList.Add(user.ToString());
}
});
}
else if (receivedData.Type == "Create")
{
Dispatcher.Invoke(() =>
{
chatSelect.Items.Add(receivedData.RoomTitle);
messageList.Add(serverMessage);
chatRoomList.Add(serverRoomTitle);
});
}
// 다시 서버로부터 메시지를 받을 수 있도록 설정
ReceiveControl();
}
}
catch (Exception ex)
{
MessageBox.Show($"메시지 수신 중 오류 발생: {ex.Message}");
}
}
다시 클라이언트로 돌아와서,
받은 데이터를 Type으로 또 구분한 뒤에 UI에 행동을 취합니다.
중요한 점은, UI에 관련된 상호작용을 할 때는 Dispatcher.Invoke를 활용한 비동기 작업으로 이루어져야한다는 점입니다.
서버쪽에서는 lock도 사용해주었는데, 데이터 추가나 제거시 lock을 이용해주어야 데이터가 꼬이지 않고 관리됩니다.
전체 코드는 맨 밑의 GitHub 링크를 달아놓았으니 참고하시면 됩니다.

Project_09.zip
1.60MB
소스코드 GitHub 링크
https://github.com/kdoy99/LMS5_Project_09.git
'C#' 카테고리의 다른 글
LMS5_Project08 C# WPF 프로젝트 - Resource Monitoring Tool (0) | 2025.02.22 |
---|---|
C# WPF 데이터베이스 연결 (0) | 2025.02.05 |
C# 문제풀이 2 (0) | 2025.01.24 |
C# 문제풀이 1 (3) | 2025.01.24 |
C# 클래스 과제 마무리 (0) | 2025.01.24 |