일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- diamond inheritance
- pointer to member data
- std::cout
- vector capacity
- constructor
- member function pointer
- delete function
- dynamic_cast
- virtual function
- new&delete
- C++
- c++ basic practice
- discord bot
- return by reference
- increment operator
- virtual inheritance
- std::ostream
- placement new
- suffix return type
- std::endl
- this call
- std::vector
- virtual function table
- operator overloading
- vector size
- 더 지니어스 양면포커
- virtual destructor
- c++ multi chatting room
- base from member
- conversion constructor
- Today
- Total
I'm FanJae.
[C++ Server, C# Client] Day 6. Client Event 구현 II, Server 동기화 처리 본문
[C++ Server, C# Client] Day 6. Client Event 구현 II, Server 동기화 처리
FanJae 2024. 10. 1. 19:351. Client Event 구현
1-1. Chatting Room 레이아웃 설계
- 채팅창 레이아웃은 다음과 같이 설계하였다.
- 원래라면 누군가 입장했다는 정보나 ID 등도 추가해야겠지만, 이번 토이 프로젝트에서는 넣지 않았다.
- **님이 입장하였습니다.가 없다. (다음 토이 프로젝트에서는 좀 넣어서 깔끔하게 만들어보려 한다.)
1-2. 메시지 수신 처리
private void Receive()
{
try
{
while (isRunning)
{
byte[] buffer = new byte[4096];
int recv_length = 0;
try
{
recv_length = socket.Receive(buffer);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.TimedOut)
{
continue;
}
else
{
throw;
}
}
if (recv_length > 0)
{
string response = Encoding.UTF8.GetString(buffer, 0, recv_length);
Invoke(new Action(() =>
{
chattingRoomTextBox.AppendText(response);
chattingRoomTextBox.AppendText("\r\n");
}));
}
}
}
catch (SocketException ex)
{
if (isRunning)
{
MessageBox.Show("서버와의 연결이 끊어졌습니다. " + ex.Message);
}
}
catch (ObjectDisposedException)
{
}
catch (InvalidOperationException)
{
}
}
- 처음에 이 부분 처리에서 상당히 애먹었다.
C#의 경우 보통 Main Thread만이 UI에 대한 Control을 조작할 수 있게 함 (Thread Affinitiy)
- 응용 프로그램이 실행될 경우 기본적으로 하나의 Thread가 발생 : Main Thread
- Thread는 Main Form의 Event 처리 및 Main Form의 각종 Control들에 대한 읽기 / 쓰기 작업 수행
- Main Form에서 다른 Form을 띄울 경우에도 기본적으로 Main Thread가 자식 Form의 Control까지 모두 소유
이 때, Main Thread 외의 Thread에서 UI 상의 Control(Label, Textbox 등)에 접근할 경우 Cross Thread Error 발생
출처 :https://youngseong.tistory.com/365
- Invoke를 사용해서 다른 Thread에서 직접 접근할 수 없는 Control에 대한 작업을 Main Thread에 위임 가능하다고 한다.
- 앞으로 C# Multi Thread를 이용한 UI 갱신을 위해서라도 잘 알아둬야 할 것 같다.
1-3. 메시지 보내기 기능 구현
private void SendButton_Click(object sender, EventArgs e)
{
if (IsSocketConnected(socket))
{
string request = sendTextBox.Text;
byte[] buffer = Encoding.UTF8.GetBytes(request);
socket.Send(buffer);
sendTextBox.Text = "";
}
}
private void sendTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
e.SuppressKeyPress = true;
this.SendButton_Click(sender, e);
}
}
- 보내기 버튼을 누르면 일반 메시지를 보내기 처리한다.
- sendTextBox_KeyDown 버튼의 경우는 사실 크게 필요한 기능은 아니다. (엔터 누르면 채팅 보내지는 기능이다.)
private void ExitButton_Click(object sender, EventArgs e)
{
this.Close();
}
private void ChattingRoom_FormClosing(object sender, FormClosingEventArgs e)
{
if (IsSocketConnected(socket))
{
string request = "/Exit_Room";
byte[] buffer = Encoding.UTF8.GetBytes(request);
socket.Send(buffer);
}
isRunning = false;
if (receiveThread != null && receiveThread.IsAlive)
{
receiveThread.Join();
}
}
- Windows Form 닫기 기능이다. 기본적으로 /Exit_Room 이라는 명령을 보내서 해당 방에서 떠났음을 표시한다.
- 또한, 기존 이 폼에서 메시지를 받는 Thread를 종료하게 만든 이후 나가도록 처리한다.
1-4. 상수값 처리
public static class Constants
{
public const string CLOSE_SOCKET = "/Close_Socket";
public const string COMPLETE_CREATE_ROOM = "/Complete_Create_Room";
public const string EXIST_ROOM = "/Exist_Room";
public const string NOT_EXIST_ROOM = "/Not_Exist_Room";
public const string NO_ROOM = "/No_Room";
public const string EXIT_ROOM = "/Exit_Room";
public const string GET_CHATTING_ROOM = "/Get_Chatting_Room";
public const string CREATE_CHATTING_ROOM = "/Create_Chatting_Room ";
public const string JOIN_CHATTING_ROOM = "/Join_Chatting_Room ";
}
- 메시지에 사용되는 값들은 이와 같이 처리하여 사용해준다.
- 앞서 서버에서 다뤘던 Client_Handle.h와 동일한 형태이다.
2. Server
2-1. 동기화 문제
- 사실 이 Toy project에서 동기화 처리를 해줄 만한 부분은 그렇게 많지 않다.
- 방 관련 해서만 처리해주면 된다. 각 쓰레드가 공유 자원으로 쓰는것은 오직 방 관련한 접근 뿐이다.
※ Lock을 걸때는 문제가 되는 부분에 걸어준다. (중요도에 따라서 Lock을 걸지 않는 부분도 있다.)
① 일반 메시지 전송 - Lock 미필요
std::cout << "[Log] : roomName : " << this->roomName << std::endl;
for (SOCKET target_socket : chatRooms[this->roomName])
{
send(target_socket, message.c_str(), message.length(), 0);
}
- chatRooms 값이 변하는 순간은 방에서 누가 들어오거나 나가는 상황이다.
- 데이터 일관성이 깨질 수 있지만, 일반 메시지 전송에서 데이터 일관성이 깨지는건 Critical 한 문제는 아니다.
② 방 리스트 얻어오기 - Lock 미필요
void ClientEventHandler::Handle_Get_Chatting_Room()
{
std::string room_List = "";
std::cout << "[Log] : " << chatRooms.size() << std::endl;
if (chatRooms.empty())
{
room_List += NO_ROOM;
}
else
{
for (auto& room : chatRooms)
{
room_List += room.first + "\n";
}
}
send(socket, room_List.c_str(), room_List.length(), 0);
}
- 방의 리스트를 불러오는 부분에서는 락을 걸지 않았다.
- 방 리스트를 불러오려는 순간 방이 만들어지거나 파괴(아무도 없어서 삭제) 되는 경우는 있겠지만 중요도가 낮다.
③ 방 나가기 - Lock 필요
void ClientEventHandler::Handle_Exit_Room()
{
std::cout << "[Log] : " << "Handle_exit_Room" << std::endl;
{
const std::lock_guard<std::mutex> lock(chatRoom_mutex);
if (chatRooms.find(roomName) != chatRooms.end())
{
send_message = EXIST_ROOM;
}
else
{
std::cout << "[Log] : Client Create Room : " << message << std::endl;
chatRooms[message].insert(socket);
send_message = roomName;
}
}
}
- 방을 입장하는 상황과 방을 삭제하는 것은 데이터 일관성이 중요하다.
- 방을 단순히 나가는 것뿐만 아니라, 삭제될 방에 입장한다는건 불가능하기 때문이다.
④ 방 입장 - Lock 필요
void ClientEventHandler::Handle_Join_Chatting_Room(const std::string& message)
{
this->roomName = message;
std::string send_message = "";
{
const std::lock_guard<std::mutex> lock(chatRoom_mutex);
if (chatRooms.find(roomName) != chatRooms.end())
{
send_message = roomName;
chatRooms[message].insert(socket);
}
else
{
send_message = NOT_EXIST_ROOM;
}
}
send(socket, send_message.c_str(), send_message.length(), 0);
}
- 방을 입장할때는 Lock()을 처리해야 한다. 삭제될 방 처리와 겹쳐서 처리하면 안되기 때문이다.
3. TODO
1) Client
- 필요시 일부 코드 개선
2) Server
- 구조도 작성
'Toy Project > Multi Room Cheating Server' 카테고리의 다른 글
[C++ Server, C# Client] Day 7. 서버 구조 정리 (0) | 2024.10.03 |
---|---|
[C++ Server, C# Client] Day 5. Server 오류 수정 및 Client Event 구현 I (0) | 2024.09.30 |
[C++ Server, C# Client] Day 4. Server 핸들러 로직 분할 (2) | 2024.09.29 |
[C++ Server, C# Client] Day 3. GUI 클라이언트 설계 및 Server 버그 수정 (0) | 2024.09.26 |
[C++ Server] Day 2. Thread 적용 및 Multi Chatting Room 구현 (3) | 2024.09.25 |