MFC 채팅 파일 전송

당근한박스·2023년 9월 18일
0

C++

목록 보기
16/23

OnReceive에 접속 요청, 파일 전송 요청 (do-while로 파일 끝까지 다 받기), 종료 요청 따로 따로 체크해서 처리
OnLbnDblclkList1에는 파일 오픈하는 부분만 있으면 됨


서버

// 접속된 클라이언트로부터 데이터 받아와서 리스트박스에 표시하는 역할 
// 데이터를 보내는 것은 소켓 클래스의 멤버 함수인 Send를 이용
// 데이터를 받을 때는 통신 소켓 클래스에 오버라이딩한 OnReceive 메시지 함수를 사용
LPARAM CChatServerDlg::OnReceive(UINT wParam, LPARAM lParam) {
	//접속된 곳에서 데이터가 도착했을 때
	char pTmp[256]; //pTmp : 데이터 임시저장 버퍼 (배열크기 256바이트로 설정)
	CString strTmp; // strTmp < MFC 라이브러리에서 제공하는 문자열 클래스
	// 메모리초기화 (메모리 크기를 변경할 메모리 시작 주소, 초기화 할 값, 크기 반환 값)
	memset(pTmp, '\0', 256); // pTmp버퍼를 널(0)로 초기화 (이전 데이터 초기화하고 새 데이터 받아올 준비함)

	// 데이터를 pTmp에 받는다.
	m_socCom[wParam]->Receive(pTmp, 256); // wParam = 클라이언트 번호, 데이터 길이는 256바이트
	strTmp.Format(_T("%s"), pTmp);

	// 클라이언트로부터 접속 종료
	if (strTmp.Compare(SOC_CLIENT_DISCONNECT) == 0) {
		// erase + remove : 특정 조건에 해당하는 원소 아예 지움
		//begin~end : 범위지정, wParam : 지울 값, m_using.end : 컨테이너의 end반복자
		m_using.erase(std::remove(m_using.begin(), m_using.end(), wParam),
			m_using.end());
		count -= 1;
		m_strCount.Format(_T("사용자 수 : %d"), count);
		if (count == 0) {
			m_strStatus = "접속상태 : 대기 중";
		}

		CString id;
		id.Format(_T("%d"), wParam);
		int i = m_list.GetCount();
		m_list.InsertString(i, (_T("사용자") + id + " : " + _T("접속을 종료하였습니다.")));
		UpdateData(false);

	}
	else if (strTmp.Compare(SOC_FILE_TRANSFER) == 0) {
		CString id;
		id.Format(_T("%d"), wParam);
		int i = m_list.GetCount();
		m_list.InsertString(i, (_T("사용자") + id + " : " + strTmp));

		for each (int i in m_using) {
			if (i != _ttoi(id)) { // 보낸 클라이언트 제외 모든 클라이언트한테 보냄
				m_socCom[i]->Send((_T("사용자") + id + " : " + strTmp), 256);
			}
		}
		receivedFile = strTmp.Mid(7);
		char fileBuffer[256];
		int bytesRead;
		CFile file;
		do {
			// 받은 데이터 fileBuffer에 저장하고 bytesRead만큼 데이터를 파일에 씀 
			bytesRead = m_socCom[g_wParam]->Receive(fileBuffer, sizeof(fileBuffer));
			if (bytesRead > 0) {
				file.Write(fileBuffer, bytesRead);
			}

		} while (bytesRead > 0);
	}

	else {
		// 리스트박스에 보여준다. 
		CString id;
		id.Format(_T("%d"), wParam);

		int i = m_list.GetCount();
		m_list.InsertString(i, (_T("사용자") + id + " : " + strTmp));

		// 이 부분 제외하면 서버만 다중 클라이언트로부터 채팅 가능
		for each (int i in m_using) {
			if (i != _ttoi(id)) { // 보낸 클라이언트 제외 모든 클라이언트한테 보냄
				m_socCom[i]->Send((_T("사용자") + id + " : " + strTmp), 256);
			}
		}
	}
	UpdateData(false);
	return TRUE;
} //end OnReceive()


void CChatServerDlg::OnLbnDblclkList1()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	int selectedIndex = m_list.GetCurSel(); //선택된 항목 인덱스 저장
	if (selectedIndex != LB_ERR) // 오류가 아닐 때
	{
		CString selectedText;
		m_list.GetText(selectedIndex, selectedText); //Index에 해당하는 텍스트를 Text에 저장

		// 사용자가 리스트 박스에서 파일 이름을 더블클릭했을 때 ( -1 : 실패)
		if (selectedText.Find(_T("[File]")) != -1) {
			// 파일 저장 대화상자 열기
			//FALSE : 파일 저장 대화상자 열기, nullptr : 부모윈도우핸들, 
			CFileDialog dlg(FALSE, nullptr, receivedFile, OFN_OVERWRITEPROMPT, _T("All Files (*.*)|*.*||")); // < 모든 파일 선택

			if (dlg.DoModal() == IDOK) {
				CString savePath = dlg.GetPathName(); // 사용자가 선택한 저장 경로 및 파일 이름

				// 파일 데이터를 수신하는 함수를 호출하여 파일을 받아옴
				CFile file;
				// 파일 생성(이미 있으면 덮어쓰기), 쓰기모드, 이진 파일로 열기
				if (file.Open(savePath, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary)) {
					//char fileBuffer[256];
					//int bytesRead;
					//do {
					//	// 받은 데이터 fileBuffer에 저장하고 bytesRead만큼 데이터를 파일에 씀 
					//	bytesRead = m_socCom[g_wParam]->Receive(fileBuffer, sizeof(fileBuffer));
					//	if (bytesRead > 0) {
					//		file.Write(fileBuffer, bytesRead);
					//	}
					//} while (bytesRead > 0);

					m_list.AddString(_T("file.Close()"));
					file.Close();
					AfxMessageBox(_T("파일 저장이 완료되었습니다."));
				}
			}
		}
        
		else {
			return;
		}
	}
    
} //end OnLbnDblclkList1()


클라이언트

void CChatClientDlg::OnBnClickedFileSend()
{
	// 파일 선택 대화 상자를 열어 사용자가 전송할 파일을 선택
	CFileDialog dlg(TRUE); // TRUE = 파일을 열기 위한 대화 상자 생성
	if (dlg.DoModal() == IDOK) //사용자가 OK 버튼 눌렀을 때 조건문 실행
	{
		CString filePath = dlg.GetPathName(); //파일 경로
		CString fileName = dlg.GetFileName(); //파일 이름

		// 서버에 파일 이름 전송
		CString request = _T("[File] ") + fileName;

		// request.GetLength() * sizeof(TCHAR) < 전송할 데이터(request) 크기 (문자열 길이 * 데이터 타입 크기)
		m_socCom.Send(request, request.GetLength() * sizeof(TCHAR));

		// 파일 오픈
		CFile file;
		if (file.Open(filePath, CFile::modeRead | CFile::typeBinary)) // 파일 읽기 모드로 오픈/ 바이너리 모드로 오픈
		{
			// 파일 크기 가져오기
			// (UINT) < 형변환 명시
			UINT fileSize = (UINT)file.GetLength(); //GetLength() < CFile 클래스의 멤버함수로 열린 파일의 크기 반환
			int nNameLen = fileName.GetLength(); // 파일 이름 길이 저장

			// 파일 이름 길이와 파일 이름을 서버로 전송
			m_socCom.Send(&nNameLen, sizeof(int)); // 파일 이름 길이 전달
			m_socCom.Send((LPCTSTR)fileName, nNameLen * sizeof(TCHAR)); // 파일 이름 전달, LPCTSTR < 문자열 가리키는 포인터

			// 파일 크기를 서버로 전송 (파일 크기를 알아야 서버에서 파일을 수신할 수 있기 때문)
			m_socCom.Send(&fileSize, sizeof(UINT));

			// 파일 데이터를 읽어와 소켓을 통해 전송
			CByteArray fileData;
			fileData.SetSize(fileSize); // 객체 크기를 fileSize(파일크기)로 설정
			file.Read(fileData.GetData(), fileSize); //filesize만큼 파일을 읽어옴
			file.Close();

			// 소켓을 통해 파일 데이터를 서버에 전송
			m_socCom.Send(fileData.GetData(), fileSize); // fileData.GetData() < 파일데이터의 시작위치, fileSize < 전송할 데이터 크기
			m_socCom.Send(SOC_FILE_TRANSFER, 256);

			// 전송한 데이터 리스트 박스에 보여줌
			CString strTmp;
			strTmp.Format(_T("나 : [File] %s"), fileName);
			int i = m_list.GetCount();
			m_list.InsertString(i, strTmp);
			UpdateData(FALSE);

			// 파일 전송 완료 메시지 표시
			MessageBox(_T("파일 전송이 완료되었습니다."));
			
		}
		else
		{
			LPCTSTR pAppNameTemp = AfxGetApp()->m_pszAppName;
			AfxGetApp()->m_pszAppName = _T("ERROR");
			MessageBox(_T("파일을 열 수 없습니다."));
			AfxGetApp()->m_pszAppName = pAppNameTemp;
			
		}
	}
}// end OnBnClickedFileSend()

0개의 댓글