본문 바로가기

TIPS/MFC

[TIPS] 13th 20160818 (list box / socket)

13번째 강좌 정리입니다~^^


<ListBox>


1. MFC프로젝트를 만든다.


2. 도구상자에서 리스트박스를 눌러 배치해준다.

listbox는 자료구조를 가진 컨트롤이다. 기본적으로 문자열을 다룰 수 있도록 되어있는데, 나중에 더 배우면 문자열 뿐만 아니라 다양하게 사용할 수 있다!



3. 다음과 같이 리스트 박스와 에디트컨트롤, 버튼을 배치해 준다.


4. 먼저 리스트박스의 ID를 IDC_CHAT_LIST로 바꾸어 준다.

   에디트컨트롤 ID를 IDC_CHAT_EDIT로 바꾸어 준다.

   버튼 ID의 캡션을 입력으로 바꾸어 준다.


5. 리스트박스를 우클릭하여 아래와 같이 변수를 추가한다.



6. 버튼을 더블클릭하여 함수를 만들고,


void CExamListBox2Dlg::OnBnClickedButton1()

{

CString str;

GetDlgItemText(IDC_CHAT_EDIT, str);

m_chat_list.AddString(str);

}


을 입력해준다. 가끔씩 오류가 없는데 밑줄이 생기는 경우도 있으나(특히 상수값) 컴파일시 오류가 없으면 괜찮은 것이다.


여기까지의 결과 : 에디트컨트롤에 문자열을 입력하고 입력버튼을 누르면 리스트박스에 뜨게 된다.



<아래쪽에 추가하기>


위 같은 결과에서는 입력을 누를 때 문자열이 위쪽에 추가된다. 채팅창에서는 최근 입력한 것이 아래쪽에 추가되므로 이를 변경해야한다.


리스트박스는 위에서 사용한 AddString말고 또 다른 함수를 제공한다.


InsertString(-1, str);


7.  AddString함수를 위와 같은 함수로 변경해준다.


InsertString함수의 첫번째 인자 정수값은 내가 추가시키고 싶은 위치이다. 예를들어 리스트박스에 항목이 하나도 없다면 0만 사용할 수 있다. 만약 두 항목이 있으면 0, 1, 2를 사용할 수 있다. 즉 추가할 위치에는 유효한 값을 넣어주어야 한다.


예외적으로 '-1'은 리스트박스에 추가된 목록 중 가장 마지막에 추가해달라는 뜻이다. 


하지만 -1의 의미를 모르는 사람들을 위해 다르게 표현할 수도 있다.


리스트박스의 항목의 갯수를 얻어서 (함수 : GetCount();) 이용하는 것이다.


int count = m_chat_list.GetCount();

m_chat_list.InsertString(count, str);

를 사용해줄 수 있다.



여기까지가 버튼클릭 함수이다.


<채팅 길이 제한하기>


채팅이 너무 길어지게 된다면 오류가 생기게 된다.


따라서 다음과 같은 함수를 추가해줄 수 있다.

if (m_chat_list.GetCount() > 3000) {

m_chat_list.DeleteString(0);

}


제일 위에 있는 문자열을 지우는 것이기 때문에 0을 써준 것이다.


여기서 좀 더 정확하게 표현한다면 if대신 while을 적어준다.

예를 들어 이미 4000개의 문자열이 있을 때 if문을 사용한다면 하나만 지우게 되니 while문으로 3000개까지 맞춰 지워주는 것이다.


<리스트박스 항목 클릭시 에디트컨트롤에 출력하기>


이제 리스트박스에 있는 문자열을 클릭하면 에디트컨트롤에 그 내용이 입력되도록 해보자.


(클래스위저드로 가능한데, 실습시 해상도 때문에 이벤트 처리기 마법사로 함)


리스트박스를 우클릭하여 이벤트 처리기 마법사를 실행한다.

(참고 : DBCLK는 더블클릭의 약자이다.)


참고로 많은 프로그램에서 무언가를 선택하면 점선의 박스가 표시되는 것을 볼 수 있다. 이러한 점선 박스가 표시될 때, setfocus라는 '입력이 가능한 상태'의 의미의 메세지가 발생한다. killfocus는 반대 의미의 메세지이다.


맨 위의 것을 추가 및 편집을 눌러

OnLbnSelchangeChatList() 함수를 생성한다. 그 함수에 다음과 같이 입력해준다.


CString str;

int index = m_chat_list.GetCurSel(); //Get Current Select의 약자.


만약 사용자가 클릭한 값이 없으면 -1이 반환된다.


이 경우에는 항목을 변경했을 경우에 실행되기 때문에 -1이 넘어올 일은 없지만...아래와 같이 써준다.


(-1을 써주어도 되는데 -1이 무엇을 의미하는지 모르는 사람들도 있기 때문에 LB_ERR로 써준다.)


if (index != LB_ERR) {

m_chat_list.GetText(index, str); //index의 문자열을 str에 저장한다.

SetDlgItemText(IDC_CHAT_EDIT, str);

}


여기까지 실행하면,



다음과 같이 리스트박스의 문자열을 선택하면 그 문자열이 에디트박스에 출력된다.


참고로 리스트박스는 linked list로 구성되어있다.



<최근입력 문자열이 화면에 나오도록 설정하기>


여기까지는 문자열이 계속 추가되더라도 스크롤만 생기고 리스트박스가 추가된 것으로 넘어가지 않는다.


따라서 버튼클릭함수의 m_chat_list.InsertString(count, str); int index 로 받고,

m_chat_list.SetCurSel(index); 를 추가하면 해결이 된다.



<list box reset>


이제 채팅을 reset하는 방법을 살펴보겠다.


int count + m_.GetCount();

for(int i =0; i<count ; i++) {

m_chat_list.DeleteString(0);

}

여기서 주의할 점은 DeleteString함수에서 i가 아닌 0을 써주어야 한다는 점이다. i를 쓰게되면 띄엄띄엄 지워지게 된다.


하지만 이렇게 사용하는 경우보다는 아래와 같이 쓰는 것이 좋다.


***(놓쳤다...)SetContext??



<문자열 검색하기>


리스트박스에서 특정문자열을 찾기 위해서는


m_chat_list.FindString(-1,L"abc");


첫번째 인자 변수명을 보면 nStartAfter이다. 이것은 내가 명시한 것의 다음 것 이기 때문에 0을 입력하면 0을 제외한 1부터 찾기시작한다.


StartAfter인 이유는 중복되는 단어가 여러개 중 첫번째 것을 찾았을 때 그 값이 반환될텐데, 만일 반환된 값부터 다시 찾게된다면 계속해서 처음 찾았던 문자열만 계속 찾게된다. 따라서 찾은 그 다음 부분부터 찾기 위해 startAfter이 된다.


위와 같은 경우에는 abc를 (앞쪽에)포함한 문자열을 찾는다.


FindStringExact(-1,L"홍길");

exact를 붙인 이 함수의 경우 '홍길동'은 찾을 수 없고 독립적으로 '홍길; 이라고 적혀있는 문자열만을 찾는다.


이러한 방법으로 버튼을 하나 추가해서 문자열을 검색할 수 있다.


참고 : strstr함수는 문자열안에서 문자열을 찾는 함수이다.

   Cstring에 Find라는 함수도 있다.


---------------------------------------------------------------------------------------------------


<소켓통신(1)>


TCP : 기종이 다른 기기간의 통신을 사용하는 프로토콜이다. TCP의 속도자체는 느리다.

TCP는 일종의 규약이고,  TCP에 대한 이해도가 낮더라도 사용할 수 있도록 나온 라이브러리가 소켓이다.


소켓이 사용하기 좋다보니 요즘은 TCP 뿐만 아니라 블루투스까지도 사용할 수 있게 되었다.


MS에서는 소켓이 금방 죽을 것이라고 생각해서 윈도우즈에 반영하지 않았었다. 즉 윈도우즈의 기본이 아니기 때문에 사용하기가 조금 힘들 수도 있다.


소켓은 함수는 어렵지 않지만, 프로그래머가 어떻게 서비스할지가 어려운 방식이다.


소켓은 Sever와 Client로 나뉜다.


server는 서비스를 제공하는 좋은 사양의 컴퓨터로 보고, client는 서비스를 받는 낮은 사양의 컴퓨터로 본다.

컴퓨터 사양이 좋아져 90년대에 sever와 client의 경계가 모호해지기 시작했고, peer to peer이 성행하였다.

요즘은 모바일 시대로 바뀌어 server와 client의 사양이 차이가 나게되어서 다시 서버와 클라이언트가 유행하기 시작하였다.



<실습>


1. MFC 응용프로그램으로 프로젝트를 만든다. 만드는 과정에 고급기능에서 windows 소켓이 있는데 똑같이 선택하지 않는다!!

(windows 소켓은 MFC소켓을 사용할 때 체크한다.) 우리는 API제공 소켓을 이용하니 선택하지 않는다.


2. 서버프로그램에는 로그라고 현재 상태를 표시하는 것이 있는데 리스트박스를 이용해서 표시해본다.

리스트박스 ID를 IDC_EVENT_LIST로 변경한다.


3. 리스트박스를 우측클릭하여 변수추가로 m_event_list 변수를 추가한다.(private로)


4. 헷갈리니 잠시 프로젝트속성에서 문자 집합을 설정안함으로 변경한다.



<자주쓰는 함수 추가하기>

5. 리스트박스의 항목 갯수 제한, 문자열 추가, 커서의 위치를 마지막으로 하는 문법들을 하나의 함수로 추가해준다.


  ExamServerDlg.cpp에 void CExamServerDlg::AddEventString(const char *ap_string)를 추가한다.

  앞에 노란전구도 눌려서 헤더에도 선언을 추가하고 저장한다.


함수의 인자를 const char *로 한 이유 : char*는 const char*로 대입이 가능하지만 const char*sms char*로 대입이 불가능하다.

예를 들면


CString str = "abc";

char *p = (char*)(const char*)str;


이렇게 긴 캐스팅을 해야한다.

하지만 처음부터 char*가 아닌 const char *p = str;로 적으면 간단하다. 따라서 위에 만든 함수에서는 CString을 많이 쓸 것이기 때문에 const char*를 사용한 것이다.


LPCTSTR     //const char *


이 함수는 앞서 list box실습에서 한 것과 같은 방법으로

void CExamServerDlg::AddEventString(const char *ap_string)

{

while (m_event_list.GetCount() > 3000) {

m_event_list.DeleteString(0);

}

int index = m_event_list.InsertString(-1, ap_string);

m_event_list.SetCurSel(index);

}


이렇게 구성해준다.


6. OnInitDialog()에 AddEventString("서비스를 시작합니다.");를 추가해준다.



여기까지의 실행결과이다.




<소켓라이브러리 추가>


stdafx.h에

#include <WinSock2.h> 를 추가해준다.


stdafx.cpp에

#pragma comment (lib, "WS2_32.lib")를 추가해준다. 이제 컴파일러가 소켓함수를 사용할 수 있다. (여기서 32는 32비트의 의미이다.)



<소켓등록과 해제>

ExamServer.cpp에서 InitInstance()함수에서 항상 해왔듯이 필요없는 것은 지우고 이것만 남긴다.


BOOL CExamServerApp::InitInstance()

{

CWinApp::InitInstance();


CExamServerDlg dlg;

m_pMainWnd = &dlg;

dlg.DoModal();

return FALSE;


이제 여기서 대화상자가 뜨기 전에 소켓을 등록 해주겠다.


WSADATA temp;

WSAStartup(0x0202, &temp);


WSA는 Windows socket API를 의미하고 0x0202는 버전을 의미한다.


마지막에는 WSACleanup();를 써서 소켓 사용을 종료한다. (하지만 실제로 사용 핸들이 없어질 때 까지는 없어지지 않는다.)





<소켓 만들기>


다시 ExamServerDlg.cpp의 OnInitDialog()로 돌아온다.


SOCKET h_socket = socket(AF_INET, SOCK_STREAM, 0);


첫번째 인자 : 주소체계

AF_INET : 인터넷을 쓰겠다는 주소체계.

AF_BTH : 블루투스 주소체계.


두번째 인자 : 소켓방식


UDP는 방송이나 화상회의 등에서만 빠르다.

그 이외는 굳이 UDP를 사용할 필요가없다. TCP를 사용함.


세번째 인자 : 프로토콜

0을 적으면 두번째 인자에서 사용하는 방식을 따라온다.



SOCKET h_socket = socket(AF_INET, SOCK_STREAM, 0);


sockaddr_in srv_addr; //등록하는 서류같은 것


srv_addr.sin_family = AF_INET; //위에 socket함수에 적은 것을 따라온다.


srv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //127.0.0.1은 예외사항으로 자신의 로컬컴퓨터에서만 사용한다는 뜻이다.

이 부분에는 자신의 IP를 직접 확인하여 넣는다.


srv_addr.sin_port = htons(18000); //어떤 서버를 접속하는지 명시적으로 표시하는 것이 port이다. 유명한 포트는 사용하면 밀리니까 특징적으로 정하여 사용해야한다. 가능하면 15000이상의 포트를 사용하도록 한다.


bind(h_socket, (LPSOCKADDR)&srv_addr, sizeof(srv_addr));


link :보통 물리적으로 두개를 연결하는데 반드시 연결되어야 하는 경우에 사용한다.(자동차 몸체에 바퀴를 link한다.)

bind : 개별적인 시스템을 연결하는 것이다. (지하철역에 지하철이 들어오는 행위가 bind. 기기를 플러그에 꼽는 행위가 bind.)


(참고로 요즘 노트북을 많이 사용하기 때문에 네트워크카드가 여러개가 있다는 가정을 하고 프로그래밍을 해야한다. 지금처럼 이렇게 하나로 생각하고 프로그래밍하면 손해볼 수 있다.)


여기까지 하고 실행을 하면 방화벽이 뜬다. 만약 실수로 차단을 누르면 소켓을 사용할 수 없게된다.


제어판에서 "windows방화벽 허용되는 앱"에 자기 프로그램이 있어야 실행이 된다. 

또한 개인과 공용이 모두 체크되어 있어야 네트워크가 바뀔 때 실행되지 않는 일이 없다.




www.tipssoft.com


'TIPS > MFC' 카테고리의 다른 글

[TIPS} 15th 20160825  (0) 2016.08.28
[TIPS] 11th 20160808  (0) 2016.08.20
[TIPS] 10th 20160804  (0) 2016.08.20