MFC TCP 통신 특정 클라이언트에게만 보내기 질문있습니다.
글쓴이: l595659 / 작성시간: 목, 2022/06/09 - 5:43오후
포럼을 잘못 설정해서 다시 올립니다.
C언어로 winsock이랑 MFC를 이용해서 채팅방 프로그램을 만들고있습니다.(두개의 쓰레드를 사용합니다)
클라이언트가 서버에 붙어서 데이터 통신은 되는데 서버단에서 특정 클라이언트를 누르고 send를 해줬을 때 그 클라이언트에게만 데이터가 가는 부분을 만들고 있습니다.
처음 들어오면 정상적으로 특정 클라이언트에게 보내기는 되는데 어떠한 클라이언트가 나갔다가 들어왔을 때는 동작하지가 않습니다.
또, 특정 클라이언트가 나갔는지 들어왔는지 리스트박스에 띄워주는데 마지막에 나간것부터 껏을땐 잘 지워지는데 중간거부터 지우면 제대로 동작하지가 않네요 순서 관련 문제인거 같은데 어떻게 수정해야할지 감이 안잡히네요
아래는 서버단 코드 일부입니다.
struct UserData { int num; int iter_info; char Ip_Address[16]; char port[10]; }; std::vector<SOCKET> client_list; #if 1 #define MAX_CLIENT_NUM 10 #define BUFSIZE 128 #define BUFFER_SIZE 1024 UserData m_user_list[MAX_CLIENT_NUM+1]; static UINT ServerFunc(LPVOID pVoid) //TCP { while (1) { #if 1 dlg->client_addr = { 0 }; dlg->client_addr.sin_family = AF_INET; dlg->client_addr.sin_port = htons(ServerPort_Num); dlg->client_addr.sin_addr.s_addr = htonl(INADDR_ANY); int size = sizeof(SOCKADDR_IN); #endif dlg->client_sock = accept(dlg->server_sock, (SOCKADDR*)&dlg->client_addr, &size); if (dlg->client_sock == SOCKET_ERROR) { AfxMessageBox("accept() Error"); continue; } client_list.push_back(dlg->client_sock); strcpy(m_user_list<ol> </ol> .Ip_Address, inet_ntoa(dlg->client_addr.sin_addr)); ConnectInfo_str.Format("IP : %s\n", inet_ntoa(dlg->client_addr.sin_addr)); ConnectInfo_str2.Format("IP : %s 가 접속했습니다 \n", inet_ntoa(dlg->client_addr.sin_addr)); dlg->m_ListClient.InsertString(-1, ConnectInfo_str); dlg->m_ListChat.InsertString(-1, ConnectInfo_str2); dlg->m_ListClient.SetItemData(index, dlg->client_sock); //dlg->m_ListClient.SetItemData(dlg->m_ListClient.FindString(-1, m_user_list->Ip_Address), dlg->client_sock); dlg->pThreadTcpServer_Data = AfxBeginThread(ServerRecvSendFunc, (void *)dlg->client_sock); index++; } closesocket(dlg->server_sock); WSACleanup; return 0; }
recv, 종료관련 코드 입니다.
static UINT ServerRecvSendFunc(void *p) { CServerDlg *dlg = (CServerDlg *)AfxGetApp()->m_pMainWnd; SOCKET socket = (SOCKET)p; char readbuffer[BUFSIZE + 1]; int recvsize; while (1) { recvsize = recv(socket, readbuffer, BUFSIZE, 0); if (recvsize <= 0) { break; } readbuffer[recvsize] = '\0'; CString strMsg2; CString strMsg; strMsg = CString(readbuffer, recvsize); dlg->m_ListChat.AddString(strMsg); strMsg = ""; #if 1 //로그위치 #endif //로그위치 #if 1 for (int i = 0; i < client_list.size(); i++) { int sendsize = send(client_list[i], readbuffer, strlen(readbuffer), 0); } #endif } #if 1 CString DisConnectInfo_str; CString DisConnectInfo_str2; //클라이언트가 접속 종료시 //------------------------------------------- for (int i = 0; i < client_list.size(); i++) { if (client_list[i] == socket) { //user_ip = m_user_list[i].Ip_Address; //AfxMessageBox(user_ip); DisConnectInfo_str.Format("IP : %s 가 종료했습니다. \n", m_user_list[i].Ip_Address); DisConnectInfo_str2.Format("IP : %s\n", m_user_list[i].Ip_Address); //DisConnectInfo_str.Format("접속 종료 IP : %s, Port : %d\n", inet_ntoa(dlg->client_addr.sin_addr), dlg->client_addr.sin_port); dlg->m_ListChat.AddString(DisConnectInfo_str); dlg->m_ListClient.DeleteString(dlg->m_ListClient.FindString(-1, DisConnectInfo_str2)); } } //------------------------------------------ std::vector<SOCKET>::iterator iter = client_list.begin(); for (int i = 0; i < client_list.size(); i++) { if (client_list[i] == socket) //clnt_list[i]의 값이 sock과 같을 때 { client_list.erase(iter); //clnt_list의 첫번째 원소 반복자를 제거 break; } iter++; } #endif if (socket == NULL) { } closesocket(socket); } #endif
버튼 눌렀을때 작동하는 코드입니다.
void CServerDlg::OnBnClickedButton2() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. CServerDlg *dlg = (CServerDlg *)AfxGetApp()->m_pMainWnd; CString str2; //TCP for (int i = 0; i < client_list.size(); i++) { if (m_ListClient.GetItemData((m_ListClient.GetCurSel())) == client_list[i]) { CString str; m_edit_chat.GetWindowText(str); int sendsize = send(client_list[i], str, strlen(str), 0); m_ListChat.AddString(str); } } if(m_ListClient.GetCurSel() == CB_ERR) { m_edit_chat.GetWindowText(str2); for (int i = 0; i < client_list.size(); i++) { int sendsize = send(client_list[i], str2, strlen(str2), 0); } m_ListChat.AddString(str2); } }
Forums:
if (client_list[i] == socket)
i값이 어떻게 변해도 iter는 변하지 않네요. 그러면 잘못된 원소가 지워질 수도 있겠죠.
vector iter로 어떻게 접근하는지 구글링해서 확인해 보세요.
std::vector
조건문이 break 되기 전까지 for문으로 iter++ 해주면서 변하게 해서 지우는데 이 방법이 틀린건가요?
맞습니다. 제가 잘못 봤네요.
맞습니다. 제가 잘못 봤네요.
어느 한 형식을 따라서 접근하시면 어떨까요?
또는
으로요. 서로 겹쳐서 사용할 필요가 없는 부분이거든요.
그리고 ServerRecvSendFunc() 함수내의 while(1) 루프에서 빠져 나오는 조건이 모호합니다.
수정해보겠습니다!
말씀해주신 형식대로 한번 수정해보겠습니다!
while문은 컴파일 하니까 클라이언트가 나가면 vector.erase 까지 해주고 나가길래 건들지는 않았습니다.
디버깅중에 확인해보니까 getItemdata부분에서 데이터를 잘 못가져오는것 같은데 이게 원인일수도 있는건가요...?
잘 모르겠습니다. 그게 원인은 아닐 거구요.
잘 모르겠습니다. 그게 원인은 아닐 거구요.
올려주신 코드로만 보면 단편적으로 파악이 안되네요.
단, 하고자 하는 기능은 보통 연결마다 thread로 만들지 않고, select 기능을 써서 합니다.
예를 들어 클라이언트가 100개가 붙으면 쓰레드가 100개가 생기는 것이거든요.
클라이언트가 나갈때도 서버랑 약속을 해서, 예를들면 9999를 보내면, 나가도록 하면 좋을 것 같네요.
메신저 패킷 구조가 필요할 수도 있습니다. (제어필드)(메시지 필드) 식으로요.
메시지를 보낼때는 (0)(Hello), 연결을 끊을때는 (9999)(-), 파일을 보낼때는 (1111)(file data....) 식으로요. 응용은 뭐 무궁무진하니까 딱히 정해져 있지는 않습니다.
다면 select를 이용한 다중 클라이언트 접속은 꼭 한번 살펴 보세요.
저게 코드 전문이긴 한데..
감사합니다 select 방식도 다음에 찾아보겠습니다.
이게 저런 방식으로 해서 여러명을 받는 느낌으로 하라고 해서 저런식으로 짰네요...
메인 코드는 전부 올린거긴한데 뭐가 문제인지 모르겠네요 힌트를 받았을때 벡터에서 소팅을 해줘야한다~ 뭐다 그랬었는데 벡터 erase해주면 앞으로 당겨져서 안해도 되는걸로 알고있거든요
SetItemData랑 GetItemData에서 디버깅때 문제가 생기긴 하는데..
Vector 요소를 지운후에 list의
Vector 요소를 지운후에 list의 GetItemData, SetItemData 가 그에 따라서 업데이트되는지 확인해 보세요.
업데이트가 되지 않으면 이미 없는 것을 가져오거나 다른 것을 가져오거나 할 수가 있을 것 같네요.
vector를 지우고 해보겠습니다
여러 클라이언트가 들어오게 하려고 vector를 쓴거니 지우고 하나만 받아서 테스트 해봐야겠네요!
테스트 해봤는데
vector 요소를 지우고 테스트 해봤는데 이러면 아예 전체에게도 메시지가 안가네요 특정 클라이언트한테만 보내는건 덤이구요
해결했습니다
맨 위 코드에서
요 코드에서 index++ 해주는데 클라이언트가 나갔을때 index-- 해주는 코드가 없다보니 SetItemData에서 0번째에 넣어줘야하는것을 계속 증가시켜서 넣어주다보니까 동작하지 않는 것 이었습니다.
친절한 답변 정말 감사합니다.
잘 해결하셨다니 다행입니다.
잘 해결하셨다니 다행입니다.
클라이언트가 나갈때 행해지는 코드에서 한꺼번에 관리가 되면 나중에 소스 관리에 좋을 것 같습니다.
예를들어 아래를 참조해 보세요.
// managing client out event
// 1) socket close
// 2) list index adjustment
// 3) etc
형식도 한번 바꿔보겠습니다
말씀하신대로 코드관리 용이하게 한번 바꿔보도록 하겠습니다 감사합니다~!
댓글 달기