티스토리 뷰
이전 포스팅
소켓 프로그래밍 : 소켓을 이용한 채팅 프로그램 만들기 예제(서버)
http://andrew0409.tistory.com/103
클라이언트 역시 대화상자 형식으로 생성하도록 한다.
왼쪽 그림과 같이 만들면 되겠다. 주소, 포트, 채팅, 입력이 써있는 스태틱 텍스트와, 입력할 수 있도록 주소,포트,입력은 에티트 컨트롤, 채팅은 리스트박스를 만들어 두자.
그리고 연결, 끊기, 보내기 세개의 버튼을 만든다.
에디트 컨트롤과 리스트박스는 변수추가를 하도록 하자. 나는 순서대로 mIp, mPort, mHistory, mInput이라는 이름으로 변수를 추가했다.
마지막으로 세개의 버튼을 각각 더블클릭해서 처리기를 만들면 초반 설정은 끝난다. 아 언급하지 않았지만 app에서 WSAStartup이나 WSACleanup을 꼭 하길 바란다. 너무 기본적인것이어서 언급을 안했고, 앞으로도 안 할 예정이다.
dlg의 헤더파일에 CClientSocket 타입의 mClient를 선언하고 시작하자.
1 2 3 4 5 6 7 8 9 10 11 12 | BOOL CDay_06_23_Client_ChatDlg::OnInitDialog() { CDialogEx::OnInitDialog(); mIp.SetWindowText(L"127.0.0.1"); mPort.SetWindowText(L"28000"); GetDlgItem(IDC_DISCONNECT)->EnableWindow(FALSE); mInput.SetFocus(); return FALSE; } | cs |
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 32 33 34 | void CDay_06_23_Client_ChatDlg::OnBnClickedConnect() { USES_CONVERSION; CString ipText, portText; mIp.GetWindowText(ipText); mPort.GetWindowText(portText); if (ipText.GetLength() == 0 || portText.GetLength() == 0) { MessageBox(L"빈칸이 있습니다.", L"알림"); return; } if (mClient.Create() == false) { MessageBox(L"소켓 생성 실패", L"알림"); return; } if (mClient.Connect(W2A(ipText), atoi(W2A(portText))) == false) { mClient.Close(); MessageBox(L"서버 연결 실패", L"알림"); return; } mHistory.AddString(L"서버에 연결했습니다."); ::WSAAsyncSelect(mClient.mSock, m_hWnd, WM_CLIENT, FD_READ | FD_CLOSE); GetDlgItem(IDC_CONNECT)->EnableWindow(FALSE); GetDlgItem(IDC_DISCONNECT)->EnableWindow(TRUE); } | cs |
에디트 박스로부터 데이터를 GetWindowText로 전달받기 위해 CString 타입의 변수를 두개 선언하고 바로 전달받아 변수에 저장한다.
빈칸이 있을때에 대한 예외처리를 하고 소켓을 만든다.
W2A 라는 생소한 명령어가 있다. 제일 첫중에 USES_CONVERSION 이라는 명령어와 세트가 되는데, 이것은 유니코드를 아스키로 바꾸어주는 기능을 한다. W2A(ipText)라고 함은 ipText라는 유니코드 문자열을 아스키코드로 바꾸어 전달한다는 뜻이다. 포트번호 역시 아스키코드로 바꾼뒤 atoi 함수를 통하여 int형으로 바꾸어 전달한다.
성공적으로 연결에 성공하면 리스트박스에 서버에 연결했습니다. 라는 문자열이 출력된다.
1 | ::WSAAsyncSelect(mClient.mSock, m_hWnd, WM_CLIENT, FD_READ | FD_CLOSE); | cs |
1 2 3 4 5 6 7 | int WSAAsyncSelect { SOCKET s, HWND hWnd, unsigned int uMsg, long iEvent }; | cs |
1 | #define WM_CLIENT (WM_USER+1) | cs |
그리고 메세지맵에 다음을 입력해서 등록한다.(헤더부분)
1 | afx_msg LRESULT OnClient(WPARAM wParam, LPARAM lParam); | cs |
cpp부분
1 | ON_MESSAGE(WM_CLIENT, &CDay_06_23_Client_ChatDlg::OnClient) | cs |
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 32 33 34 35 36 37 38 39 40 | LRESULT CDay_06_23_Client_ChatDlg::OnClient(WPARAM wParam, LPARAM lParam) { int event = WSAGETSELECTEVENT(lParam); if (event == FD_READ) { wchar_t buf[256]; int total = 0; while (true) { int ret = mClient.Recv(((char *)buf)+total, sizeof(buf) - total); if (ret == 0 || ret == SOCKET_ERROR) break; total += ret; if (total < 4) continue; int packetSize = *((int*)buf); if (total < packetSize) continue; //패킷 길이인 4바이트 skip. mHistory.AddString(buf + 2); total -= packetSize; } } else if (event == FD_CLOSE) { mClient.Close(); mHistory.AddString(L"쫒겨났습니다."); } return 0; } | cs |
이제 OnClient 함수에 대해서 알아보자. 이번 포스팅의 메인이라고 할 수 있겠다.
조금전 FD_READ나 FD_CLOSE가 발생했을때 WM_CLIENT 메세지를 발생시킨다고 했다.
그래서 함수는 시작하고 곧바로 event를 선언하여 WSAGETSELECTEVENT 함수를 통해 발생한 이벤트가 무엇인지 입력받는다. (만약 사용하는 네트워크 이벤트가 하나라면 꼭 이벤트를 입력받을 필요가 없다, 반면 두개보다 많을때는 if문으로 구분하기 보다는 switch 문으로 구분하는것이 사용에 더 용이할 것이다.)
그래서 이벤트가 FD_READ일때를 먼저 살펴보자.
이전 포스팅에서 공부했던 메인코드와 흡사하다. 유니코드형 버퍼를 생성하고, 토탈을 선언하여 0으로 초기화 시킨다.
클라이언트를 통해서 버퍼에 값을 전달받고, 버퍼의 첫번째 방은 패킷 사이즈이기 때문에 형변환을 통해서 저장하고 그 다음 방부터 mHistory에 저장시킨다. buf + 1이 아니라 +2 인 이유는 유니코드를 사용했기 때문에 한글자가 1바이트가 아니라 2바이트이기 때문이다. 이전 포스팅에서 설명한것과 똑같은 내용이기 때문에 추가로 설명할 부분은 없어 보인다.
event가 FD_CLOSE라면 클라이언트 소켓을 닫고 쫒겨났습니다 메세지를 리스트 박스에 출력한다.
이제 나머지 코드에 대한 설명을 하겠다.
1 2 3 4 5 6 7 8 | void CDay_06_23_Client_ChatDlg::OnBnClickedDisconnect() { mClient.Close(); mHistory.AddString(L"서버와 연결을 끊었습니다."); GetDlgItem(IDC_CONNECT)->EnableWindow(TRUE); GetDlgItem(IDC_DISCONNECT)->EnableWindow(FALSE); } | cs |
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 | void CDay_06_23_Client_ChatDlg::OnBnClickedSend() { USES_CONVERSION; if (mClient.IsValid() == false) return; CString input; mInput.GetWindowText(input); if (input.GetLength() == 0) return; wchar_t buf[256]; int *p1 = (int*)buf; wchar_t *p2 = buf + 2; wcscpy(p2, input.GetBuffer()); *p1 = 4 + wcslen(p2) * 2 + 2; //패킷길이 + 실제 데이터 mClient.Send((char *)buf, *p1); mInput.SetWindowText(L""); mInput.SetFocus(); } | cs |
input 창으로부터 메세지를 가져오고, int형 포인터를 선언해서 buf의 첫번째 방을 가리키게 한다.
그리고 유니코드 문자열 포인터를 선언하여 buf의 두번째방을 가리키게 한다(정확히 말하자면 세번째 방이나, 유니코드는 한개의 글자를 2바이트로 처리하므로 편의상 두번째 방이라 하겠다)
이제 input에서 읽어온 데이터를 buf의 두번째방에서부터 저장하고
첫번째 방에는 4를 더하고(첫번째 방이 int형이므로) p2의 길이에 2를 곱하고(한글자당 바이트) 2를 더해준다.(null문자 또한 2바이트)
이렇게 패킷사이즈를 설정한뒤에 서버로 send한다.
메세지를 보냈으므로 input 에디트 박스는 빈칸으로 초기화하고 다시 포커스를 준다.
이상으로 채팅프로그램 예제를 공부해 보았다.
예제부터 급하게 푸느라 소켓에서의 각종 메세지나 기타 개념에 대한 정리를 못했는데 다음 포스팅에서는 소켓의 여러가지에 대하여 다루어 볼 예정이다.
'C,C++' 카테고리의 다른 글
C++ : Critical Section [임계영역] (0) | 2015.06.25 |
---|---|
리틀 엔디안과 빅 엔디안 (Little Endian, Big Endian) (1) | 2015.06.25 |
소켓 프로그래밍 : 소켓을 이용한 채팅 프로그램 만들기 예제(서버) (3) | 2015.06.25 |
윈도우즈 MFC 소켓프로그래밍 : 소켓 기본 클래스 소스코드 5 (클라이언트 메인.cpp) (1) | 2015.06.25 |
윈도우즈 MFC 소켓프로그래밍 : 소켓 기본 클래스 소스코드 4 (메인.cpp) (10) | 2015.06.25 |
- Total
- Today
- Yesterday
- 악보
- 리눅스
- 라즈베리파이
- 유즈케이스
- 파이썬
- C++
- C
- Sort
- 파이썬예제
- 클라이언트
- 소켓 프로그래밍
- 안드로이드
- 쓰레드
- 프로세스
- 터미널
- 데이터베이스
- 소켓
- 파일
- 자료구조
- 액터
- 정렬
- 디렉터리
- socket
- UML
- MFC
- 티그널
- 티라노 시그널
- 클래스
- C/C++
- 스레드
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |