MFC TCP 통신 특정 클라이언트에게만 보내기 질문있습니다.

l595659의 이미지

포럼을 잘못 설정해서 다시 올립니다.

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);
	}
 
}
라스코니의 이미지

if (client_list[i] == socket) //clnt_list[i]의 값이 sock과 같을 때
{
    client_list.erase(iter); //clnt_list의 첫번째 원소 반복자를 제거
    break;
}

i값이 어떻게 변해도 iter는 변하지 않네요. 그러면 잘못된 원소가 지워질 수도 있겠죠.
vector iter로 어떻게 접근하는지 구글링해서 확인해 보세요.
l595659의 이미지

if (client_list[i] == socket) //clnt_list[i]의 값이 sock과 같을 때
		{
			client_list.erase(iter); //clnt_list의 첫번째 원소 반복자를 제거
			break;
		}

조건문이 break 되기 전까지 for문으로 iter++ 해주면서 변하게 해서 지우는데 이 방법이 틀린건가요?
라스코니의 이미지

맞습니다. 제가 잘못 봤네요.
어느 한 형식을 따라서 접근하시면 어떨까요?

for (iter = v.begin(); iter != v.end(); iter++)
{
    cout << *iter << endl;
}

또는
for (int i = 0; i < size; i++)
{
    cout << client_list[i] << endl;
}

으로요. 서로 겹쳐서 사용할 필요가 없는 부분이거든요.

그리고 ServerRecvSendFunc() 함수내의 while(1) 루프에서 빠져 나오는 조건이 모호합니다.

l595659의 이미지

말씀해주신 형식대로 한번 수정해보겠습니다!
while문은 컴파일 하니까 클라이언트가 나가면 vector.erase 까지 해주고 나가길래 건들지는 않았습니다.
디버깅중에 확인해보니까 getItemdata부분에서 데이터를 잘 못가져오는것 같은데 이게 원인일수도 있는건가요...?

라스코니의 이미지

잘 모르겠습니다. 그게 원인은 아닐 거구요.
올려주신 코드로만 보면 단편적으로 파악이 안되네요.

단, 하고자 하는 기능은 보통 연결마다 thread로 만들지 않고, select 기능을 써서 합니다.
예를 들어 클라이언트가 100개가 붙으면 쓰레드가 100개가 생기는 것이거든요.

클라이언트가 나갈때도 서버랑 약속을 해서, 예를들면 9999를 보내면, 나가도록 하면 좋을 것 같네요.
메신저 패킷 구조가 필요할 수도 있습니다. (제어필드)(메시지 필드) 식으로요.
메시지를 보낼때는 (0)(Hello), 연결을 끊을때는 (9999)(-), 파일을 보낼때는 (1111)(file data....) 식으로요. 응용은 뭐 무궁무진하니까 딱히 정해져 있지는 않습니다.

다면 select를 이용한 다중 클라이언트 접속은 꼭 한번 살펴 보세요.

l595659의 이미지

감사합니다 select 방식도 다음에 찾아보겠습니다.

이게 저런 방식으로 해서 여러명을 받는 느낌으로 하라고 해서 저런식으로 짰네요...

메인 코드는 전부 올린거긴한데 뭐가 문제인지 모르겠네요 힌트를 받았을때 벡터에서 소팅을 해줘야한다~ 뭐다 그랬었는데 벡터 erase해주면 앞으로 당겨져서 안해도 되는걸로 알고있거든요

SetItemData랑 GetItemData에서 디버깅때 문제가 생기긴 하는데..

라스코니의 이미지

Vector 요소를 지운후에 list의 GetItemData, SetItemData 가 그에 따라서 업데이트되는지 확인해 보세요.
업데이트가 되지 않으면 이미 없는 것을 가져오거나 다른 것을 가져오거나 할 수가 있을 것 같네요.

l595659의 이미지

여러 클라이언트가 들어오게 하려고 vector를 쓴거니 지우고 하나만 받아서 테스트 해봐야겠네요!

l595659의 이미지

vector 요소를 지우고 테스트 해봤는데 이러면 아예 전체에게도 메시지가 안가네요 특정 클라이언트한테만 보내는건 덤이구요

l595659의 이미지

맨 위 코드에서

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++;

요 코드에서 index++ 해주는데 클라이언트가 나갔을때 index-- 해주는 코드가 없다보니 SetItemData에서 0번째에 넣어줘야하는것을 계속 증가시켜서 넣어주다보니까 동작하지 않는 것 이었습니다.

친절한 답변 정말 감사합니다.

라스코니의 이미지

잘 해결하셨다니 다행입니다.
클라이언트가 나갈때 행해지는 코드에서 한꺼번에 관리가 되면 나중에 소스 관리에 좋을 것 같습니다.

예를들어 아래를 참조해 보세요.
// managing client out event

// 1) socket close

// 2) list index adjustment

// 3) etc

l595659의 이미지

말씀하신대로 코드관리 용이하게 한번 바꿔보도록 하겠습니다 감사합니다~!

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.