-
개요
메인 윈도우 목적 Pc 두대 이더넷 연결을 통해 데이터 주고 받는 코드 구현
(Soket 통신 TCP/IP)환경 노트북 2대 LAN 선으로 연결 프로그램주요 내용 피시1을 서버로 만들고 피시2에서 클라이언트로 접속하여 데이터를 주고 받을 수 있도록 구현
+ Json 형식 데이터에서 원하는 값 추출통신 순서 통신 순서는 이렇게 진행이 된다. 서버에서 소켓을 오픈을 하면 연결 대기 (Listen) 하다가 클라이언트에서 연결(Connect)시도를 하면 허락을 한 뒤 데이터 수송신이 이뤄진다.
소켓 통신 기본 설정 - CSocket
‘Csocket’ : base class undefined 발생 시 소켓통신을 하기 위해서는 CSocket 이라는 클래스가 필요한데 초기 프로그램 생성시 window socket 체크를 해주던가 체크를 안해주었으면 프로그램 내 StdAfx.h 혹은 Pch.h에 afxsock.h를 참조해 주면 된다
Main 코드
기본 클래스 : CWnd
GUI 구현을 위한 메인창의 코드입니다.
main.cpp
// CmmuniCationView.cpp: CCmmuniCationView 클래스의 구현 // #include "pch.h" #include "framework.h" // SHARED_HANDLERS는 미리 보기, 축소판 그림 및 검색 필터 처리기를 구현하는 ATL 프로젝트에서 정의할 수 있으며 // 해당 프로젝트와 문서 코드를 공유하도록 해 줍니다. #ifndef SHARED_HANDLERS #include "CmmuniCation.h" #endif #include "CmmuniCationDoc.h" #include "CmmuniCationView.h" #include "ServerSock.h" #include "ClientSock.h" // json 데이터 #include "rapidjson/document.h" using namespace rapidjson; #ifdef _DEBUG #define new DEBUG_NEW #endif // CCmmuniCationView IMPLEMENT_DYNCREATE(CCmmuniCationView, CFormView) BEGIN_MESSAGE_MAP(CCmmuniCationView, CFormView) ON_MESSAGE(UM_CLIENT_ACCEPT, onAccept) ON_MESSAGE(UM_DATA_RECEIVE, onReceve) ON_BN_CLICKED(IDC_BUTTON_IP_ADD, &CCmmuniCationView::OnBnClickedButtonIpAdd) ON_BN_CLICKED(IDC_BUTTON_SEND_DATA, &CCmmuniCationView::OnBnClickedButtonSendData) ON_BN_CLICKED(IDC_RADIO_RS232, &CCmmuniCationView::OnBnClickedRadioRs232) ON_BN_CLICKED(IDC_RADIO_RS485, &CCmmuniCationView::OnBnClickedRadioRs485) ON_BN_CLICKED(IDC_RADIO_IP, &CCmmuniCationView::OnBnClickedRadioIp) ON_BN_CLICKED(IDC_BUTTON_IP_ADD2, &CCmmuniCationView::OnBnClickedButtonIpAdd2) END_MESSAGE_MAP() // CCmmuniCationView 생성/소멸 CCmmuniCationView::CCmmuniCationView() noexcept : CFormView(IDD_CMMUNICATION_FORM) , m_edit_Ip_Add(_T("127.0.0.1")) , m_edit_SendData(_T("")) , m_edit_ReplyData(_T("")) , m_static_Lot(_T("")) , m_static_Dblink(_T("")) , m_static_Client_Ip(_T("접속 대기 중 ..")) , IP_Addr(0) , m_Radio(0) , m_btnServer(false) { // TODO: 여기에 생성 코드를 추가합니다. } CCmmuniCationView::~CCmmuniCationView() { } LRESULT CCmmuniCationView::onAccept(WPARAM wParam, LPARAM lParam) { m_static_Client_Ip = m_serverSock.strClientIPAddress; m_static_Client_Ip += L" 접속 중"; UpdateData(false); return 0; } LRESULT CCmmuniCationView::onReceve(WPARAM wParam, LPARAM lParam) { wchar_t* pBuffer = (wchar_t*)wParam; m_edit_ReplyData.Format(L"%s", pBuffer); char* pszBuff;// rapidjson 관련 함수 사용시 char 형을 써야함 int strSize = WideCharToMultiByte(CP_ACP, 0, pBuffer, -1, NULL, 0, NULL, NULL); // wchar_t 변수 길이 구하기 pszBuff = new char[strSize]; memset(pszBuff, 0, sizeof(pszBuff));// char 변수 초기화 WideCharToMultiByte(CP_ACP, 0, pBuffer, -1, pszBuff, strSize, 0, 0); // wchar_t -> char 에 문자 넣어 주기 Document doc; doc.Parse(pszBuff); const char* findKey1 = ""; const char* findKey2 = "LOT"; if (doc.IsObject()) // 제이슨 형식인가 { Value& v = doc; if (v.HasMember(findKey1)) // 해당 멤버가 있는지 확인 m_static_Dblink = CA2T(v["DBLINK"].GetString(), CP_UTF8); if (v.HasMember(findKey2)) m_static_Lot = CA2T(v["LOT"].GetString(), CP_UTF8); } UpdateData(false); return 0; } void CCmmuniCationView::DoDataExchange(CDataExchange* pDX) { CFormView::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT_IP_ADD, m_edit_Ip_Add); DDX_Text(pDX, IDC_EDIT_SEND_DATA, m_edit_SendData); DDX_Text(pDX, IDC_EDIT_REPLY_DATA, m_edit_ReplyData); DDX_Text(pDX, IDC_STATIC_LOT, m_static_Lot); DDX_Text(pDX, IDC_STATIC_DBLINK, m_static_Dblink); DDX_Text(pDX, IDC_STATIC_Client_Ip, m_static_Client_Ip); DDX_IPAddress(pDX, IDC_IPADDRESS, IP_Addr); DDX_Radio(pDX, IDC_RADIO_IP, m_Radio); DDX_Control(pDX, IDC_STATIC_INFO, m_static_Info); DDX_Control(pDX, IDC_COMBO, m_combo); } BOOL CCmmuniCationView::PreCreateWindow(CREATESTRUCT& cs) { // TODO: CREATESTRUCT cs를 수정하여 여기에서 // Window 클래스 또는 스타일을 수정합니다. return CFormView::PreCreateWindow(cs); } void CCmmuniCationView::OnInitialUpdate() { CFormView::OnInitialUpdate(); GetParentFrame()->RecalcLayout(); ResizeParentToFit(); wchar_t comNum[MAX_BUFF]; for (int i = 1; i <= 20; i++) { swprintf_s(comNum, MAX_BUFF, L"COM %d", i); m_combo.AddString(comNum); } GetDlgItem(IDC_COMBO)->ShowWindow(false); } // CCmmuniCationView 진단 #ifdef _DEBUG void CCmmuniCationView::AssertValid() const { CFormView::AssertValid(); } void CCmmuniCationView::Dump(CDumpContext& dc) const { CFormView::Dump(dc); } CCmmuniCationDoc* CCmmuniCationView::GetDocument() const // 디버그되지 않은 버전은 인라인으로 지정됩니다. { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CCmmuniCationDoc))); return (CCmmuniCationDoc*)m_pDocument; } #endif //_DEBUG // CCmmuniCationView 메시지 처리기 void CCmmuniCationView::OnBnClickedButtonIpAdd() { UpdateData(true); if(m_Radio == 0) { if (m_clientSock.m_bIsConnect) { GetDlgItem(IDC_BUTTON_IP_ADD)->SetWindowText(L"Ok"); m_clientSock.CloseClientSocket(); } else { CString str_IP_Addr; str_IP_Addr.Format(L"%lu", IP_Addr); m_clientSock.Init(this, str_IP_Addr); if (m_clientSock.m_bIsConnect) GetDlgItem(IDC_BUTTON_IP_ADD)->SetWindowText(L"Exit"); } m_edit_ReplyData = L""; m_edit_SendData = L""; UpdateData(false); } else { if (m_combo.GetCurSel() > 9) { } } } void CCmmuniCationView::OnBnClickedButtonSendData() { UpdateData(true); if (m_clientSock.m_bIsConnect) { UpdateData(true); m_clientSock.SendToServer(m_edit_SendData); m_edit_ReplyData = L""; UpdateData(false); } else { m_serverSock.SendToClient(m_edit_SendData); } } void CCmmuniCationView::OnBnClickedRadioIp() { GetDlgItem(IDC_COMBO)->ShowWindow(false); GetDlgItem(IDC_IPADDRESS)->ShowWindow(true); GetDlgItem(IDC_STATIC_INFO)->SetWindowText(L"IP Address : "); GetDlgItem(IDC_BUTTON_SERVER)->ShowWindow(true); } void CCmmuniCationView::OnBnClickedRadioRs232() { GetDlgItem(IDC_IPADDRESS)->ShowWindow(false); GetDlgItem(IDC_COMBO)->ShowWindow(true); GetDlgItem(IDC_STATIC_INFO)->SetWindowText(L"Port : "); GetDlgItem(IDC_BUTTON_SERVER)->ShowWindow(false); m_combo.SetCurSel(0); } void CCmmuniCationView::OnBnClickedRadioRs485() { GetDlgItem(IDC_IPADDRESS)->ShowWindow(false); GetDlgItem(IDC_COMBO)->ShowWindow(true); GetDlgItem(IDC_STATIC_INFO)->SetWindowText(L"Port : "); GetDlgItem(IDC_BUTTON_SERVER)->ShowWindow(false); m_combo.SetCurSel(0); } void CCmmuniCationView::OnBnClickedButtonIpAdd2() { if (!m_btnServer) { m_serverSock.Init(this); m_btnServer = true; GetDlgItem(IDC_IPADDRESS)->EnableWindow(false); GetDlgItem(IDC_COMBO)->EnableWindow(false); GetDlgItem(IDC_BUTTON_IP_ADD)->EnableWindow(false); } else { m_serverSock.CloseServerSocket(); m_btnServer = false; GetDlgItem(IDC_IPADDRESS)->EnableWindow(true); GetDlgItem(IDC_COMBO)->EnableWindow(true); GetDlgItem(IDC_BUTTON_IP_ADD)->EnableWindow(true); m_static_Client_Ip = L"대기 중"; } UpdateData(false); }
main.h
// CmmuniCationView.h: CCmmuniCationView 클래스의 인터페이스 #pragma once #include "afxwin.h" #include "afxcmn.h" #include "ServerSock.h" #include "ClientSock.h" class CCmmuniCationView : public CFormView { protected: // serialization에서만 만들어집니다. CCmmuniCationView() noexcept; DECLARE_DYNCREATE(CCmmuniCationView) public: #ifdef AFX_DESIGN_TIME enum{ IDD = IDD_CMMUNICATION_FORM }; #endif // 특성입니다. public: CCmmuniCationDoc* GetDocument() const; // 작업입니다. public: CClientSock m_clientSock; CServerSock m_serverSock; // 재정의입니다. public: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 지원입니다. virtual void OnInitialUpdate(); // 생성 후 처음 호출되었습니다. // 구현입니다. public: virtual ~CCmmuniCationView(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // 생성된 메시지 맵 함수 protected: DECLARE_MESSAGE_MAP() public: bool m_bServerReadyFlag; bool m_bServerResetFlag; CString m_edit_Ip_Add; CString m_edit_SendData; CString m_edit_ReplyData; LRESULT onReceve(WPARAM wParam, LPARAM lParam); LRESULT onAccept(WPARAM wParam, LPARAM lParam); afx_msg void OnBnClickedButtonIpAdd(); afx_msg void OnBnClickedButtonSendData(); CString m_static_Lot; CString m_static_Dblink; CString m_static_Client_Ip; DWORD IP_Addr; int m_Radio; CStatic m_static_Info; afx_msg void OnBnClickedRadioRs232(); afx_msg void OnBnClickedRadioRs485(); afx_msg void OnBnClickedRadioIp(); CComboBox m_combo; afx_msg void OnBnClickedButtonIpAdd2(); bool m_btnServer; }; #ifndef _DEBUG // CmmuniCationView.cpp의 디버그 버전 inline CCmmuniCationDoc* CCmmuniCationView::GetDocument() const { return reinterpret_cast<CCmmuniCationDoc*>(m_pDocument); } #endif
SERVER 코드
기본 클래스 : CAsyncSocket
serverSock.cpp
// ServerSock.cpp: 구현 파일 #include "pch.h" #include "CmmuniCation.h" #include "ServerSock.h" #include "ClientSock.h" // CServerSock CServerSock::CServerSock() { m_bIsListen = false; } CServerSock::~CServerSock() { } // CServerSock 멤버 함수 bool CServerSock::Init(CWnd* pWnd) { if (m_bIsListen) return false; m_pWnd = pWnd; // 소켓 클래스와 메인 윈도우 연결 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { AfxMessageBox(L"WSAStartup() Error"); return false; } if (Create(SERVER_PORT_NUMBER)) // 연결하려는 포트로 서버를 생성 (Creat 함수 안에 bind 함수가 있음) Bind : 아이피와 포트를 묶음 { if (!Listen()) //연결을 받아들일 소켓 : 클라이언트는 connect를 호출해 연결을 시도 { AfxMessageBox(_T("ListenSocket ERROR : Listen() returns FALSE!")); return false; } } else { AfxMessageBox(_T("ListenSocket ERROR : Failed to create listen socket!")); return false; } AfxMessageBox(_T("SERVER OPEN")); m_bIsListen = true; return true; } void CServerSock::OnAccept(int nErrorCode) //클라이언트에서 접속 요청이 오면 OnAccept 함수 호출 { CClientSock* pClient = new CClientSock; //연결 소켓 생성 // if (accept(client_sock, (SOCKADDR *)&clientaddrv4,&addrlen)) if (Accept(*pClient)) //만들어진 연결 소켓을 이용해 클라이언트와 통신 { UINT nClientPortNum; pClient->GetPeerName(strClientIPAddress, nClientPortNum); //클라이언트 IP주소 및 포트 정보를 얻음 pClient->Init(m_pWnd, (CAsyncSocket*)this); m_ptrClientSocketList.AddTail(pClient); //서버에 연결된 클라이언트 리스트에 리스트업 pClient->m_bIsConnect = true; m_pWnd->SendMessage(UM_CLIENT_ACCEPT); //유저 메세지 호출 } else { delete pClient; AfxMessageBox(_T("ListenSocket ERROR : Failed to accept new client!")); } CAsyncSocket::OnAccept(nErrorCode); } void CServerSock::CloseServerSocket(void) //서버에서 소켓을 닫을 때 { if (m_bIsListen) { POSITION pos; pos = m_ptrClientSocketList.GetHeadPosition(); if (pos != 0) { CClientSock* pDataSocket = NULL; while (pos) { pDataSocket = (CClientSock*)m_ptrClientSocketList.GetNext(pos); //클라이언트 목록을 돌면서 하나씩 닫아준다. if (pDataSocket) { pDataSocket->ShutDown(); pDataSocket->Close(); delete pDataSocket; } } } ShutDown(); Close(); WSACleanup(); m_bIsListen = false; } } void CServerSock::OnCloseClientSocket(CSocket* pClientSocket) //클라이언트에서 접속을 끊었을 때 { POSITION pos; pos = m_ptrClientSocketList.Find((CClientSock*)pClientSocket); //클라이언트 목록에서 연결이 끊어진 클라이언트를 찾음 if (pos) //연결이 끊어진 클라이언트 목록에서 제거 및 연결 소켓 삭제 { m_ptrClientSocketList.RemoveAt(pos); delete pClientSocket; } } bool CServerSock::SendToClient(CString strMessage) //서버에서 클라이언트에 데이터 전송 시 호출 { if (m_ptrClientSocketList.GetCount() == 0) return false; POSITION pos; pos = m_ptrClientSocketList.GetHeadPosition(); CClientSock* pClient = NULL; pClient = (CClientSock*)m_ptrClientSocketList.GetNext(pos); if (pClient != NULL) { // Send함수의 두번째 인자는 메모리의 크기인데 유니코드를 사용하고 있으므로 *2를 한 크기가 된다. // 이 함수는 전송한 데이터의 길이를 반환한다. int checkLenOfData = pClient->Send(strMessage, strMessage.GetLength() * sizeof(wchar_t)); if (checkLenOfData != lstrlen(strMessage) * 2) { AfxMessageBox(_T("일부 데이터가 정상적을 전송되지 못했습니다!")); return false; } delete pClient; } return true; }
serversock.h
#pragma once // CServerSock 명령 대상 #define UM_CLIENT_ACCEPT (WM_USER+100) //#define SERVER_IP_ADDRESS _T("127.0.0.1") #define SERVER_PORT_NUMBER 5000 #define STRING_LENGTH 255 class CServerSock : public CAsyncSocket { public: CServerSock(); virtual ~CServerSock(); WSADATA wsaData; //Socket 사용을 알리는 함수 CWnd* m_pWnd; CPtrList m_ptrClientSocketList; //Client 리스트 bool m_bIsListen; //Listen 상태확인 Flag CString strClientIPAddress; bool Init(CWnd* pWnd); virtual void OnAccept(int nErrorCode); void CloseServerSocket(void); void OnCloseClientSocket(CSocket* pDataSocket); bool SendToClient(CString strMessage); };
CLIENT 코드
기본 클래스 : CSocket
clientSock.cpp
// ClientSock.cpp: 구현 파일 // #include "pch.h" #include "ClientSock.h" #include "ServerSock.h" CString Addr; // CClientSock CClientSock::CClientSock() { m_pServerSocket = NULL; m_pBuffer = NULL; m_bIsConnect = false; m_pBuffer = new wchar_t[BUFFER_LENGTH]; Addr = L"127.0.0.1"; } CClientSock::~CClientSock() { delete[] m_pBuffer; } // CClientSock 멤버 함수 bool CClientSock::Init(CWnd* pWnd, CString strAddr, CAsyncSocket* pServerSocket) { if (m_bIsConnect) return false; Addr = strAddr; m_pWnd = pWnd; m_pServerSocket = pServerSocket; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { // WSAStartup : WSACleanup 함수와 쌍을 이뤄 소켓 프로그램의 시작과 끝을 나타냄 // 윈속 동적 연결 라이브러리를 초기화하고 윈속 구현이 애플리케이션 요구사항을 충족하는지 확인 AfxMessageBox(L"WSAStartup() Error"); return false; } if (!m_pServerSocket) { Create(); // 클라이언트 생성 m_pWnd->SetTimer(CONNECT_TIMEER, 1000, NULL); // Timer 아이디 CONNECT_TIMEER, 시간간격 5초의 타이머 설치 if (!Connect(Addr, SERVER_PORT_NUMBER)) //서버와 연결 { m_pWnd->KillTimer(CONNECT_TIMEER); AfxMessageBox(_T("연결 시간 초과"), MB_OK | MB_ICONINFORMATION); m_pWnd = NULL; m_pServerSocket = NULL; ShutDown(); Close(); return false; } else { m_pWnd->KillTimer(CONNECT_TIMEER); AfxMessageBox(_T("Successfully connected to Server"), MB_OK | MB_ICONINFORMATION); memset(m_pBuffer, 0, sizeof(wchar_t) * BUFFER_LENGTH); } } m_bIsConnect = true; return true; } bool CClientSock::Init(CWnd* pWnd, CAsyncSocket* pServerSocket) { if (m_bIsConnect) { AfxMessageBox(_T("서버와 연결 확인 요망")); return false; } m_pWnd = pWnd; m_pServerSocket = pServerSocket; if (!m_pServerSocket) { Create(); m_pWnd->SetTimer(CONNECT_TIMEER, 1000, NULL); // Timer 아이디 CONNECT_TIMEER, 시간간격 5초의 타이머 설치 if (!Connect(Addr, SERVER_PORT_NUMBER)) //서버와 연결 { m_pWnd->KillTimer(CONNECT_TIMEER); m_pWnd = NULL; m_pServerSocket = NULL; ShutDown(); Close(); return false; } else { m_pWnd->KillTimer(CONNECT_TIMEER); } } memset(m_pBuffer, 0, sizeof(wchar_t) * BUFFER_LENGTH); m_bIsConnect = true; return true; } bool CClientSock::SendToServer(CString strMessage) { if (!m_bIsConnect) { AfxMessageBox(_T("서버와 연결 확인 요망")); return false; } if (Send(strMessage, strMessage.GetLength() * sizeof(wchar_t)) == strMessage.GetLength() * sizeof(wchar_t)) return true; else return false; } bool CClientSock::SendToServer(wchar_t* wstrMessage) { if (!m_bIsConnect) { AfxMessageBox(_T("서버와 연결 확인 요망")); return false; } if (Send((LPVOID)(LPCTSTR)wstrMessage, (int)wcslen(wstrMessage) * sizeof(wchar_t)) == (int)wcslen(wstrMessage) * sizeof(wchar_t)) return true; else return false; } void CClientSock::OnReceive(int nErrorCode) { int iLen = 0; memset(m_pBuffer, 0, sizeof(wchar_t) * BUFFER_LENGTH); if (Receive(m_pBuffer, sizeof(wchar_t) * BUFFER_LENGTH) > 0) { iLen = (int)wcslen(m_pBuffer); m_pWnd->SendMessage(UM_DATA_RECEIVE, (WPARAM)m_pBuffer); } CSocket::OnReceive(nErrorCode); } void CClientSock::OnClose(int nErrorCode) { CloseClientSocket(); m_pWnd->SendMessage(UM_DATASOCKET_CLOSE); m_bIsConnect = false; CSocket::OnClose(nErrorCode); // if Server Data Socket, Delete it from List of ListenSocket, Delete this Object if (m_pServerSocket) ((CServerSock*)m_pServerSocket)->OnCloseClientSocket((CSocket*)this); CSocket::OnClose(nErrorCode); } void CClientSock::CloseClientSocket() { if (m_bIsConnect) { ShutDown(); Close(); WSACleanup(); m_bIsConnect = false; } } BOOL CClientSock::OnMessagePending() { MSG msg; if (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE)) { if (msg.message == CONNECT_TIMEER) { PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE); CancelBlockingCall(); Close(); } } return CSocket::OnMessagePending(); }
clientSock.h
#pragma once // CClientSock 명령 대상 // CClientSocket 명령 대상입니다. #define UM_DATA_RECEIVE (WM_USER+901) #define UM_DATASOCKET_CLOSE (WM_USER+902) #define BUFFER_LENGTH (1024*1024) class CClientSock : public CSocket { public: CClientSock(); virtual ~CClientSock(); HWND m_hWnd; WSADATA wsaData; //Socket 사용을 알리는 함수 CWnd* m_pWnd; CAsyncSocket* m_pServerSocket; //Server Socket 연결에 사용할 포인터 wchar_t* m_pBuffer; //Server에 보낼 메세지 데이터 bool m_bIsConnect; //Server와 Client와의 연결 확인 Flag bool Init(CWnd* pWnd, CString strAddr, CAsyncSocket* pServerSocket = NULL); bool Init(CWnd* pWnd, CAsyncSocket* pServerSocket = NULL); //ClientSocket Init & ServerSocket 연결 virtual void OnReceive(int nErrorCode); virtual void OnClose(int nErrorCode); void CloseClientSocket(); bool SendToServer(CString strMessage); //Server에 Message를 보냄 bool SendToServer(wchar_t* wstrMessage); //Server에 Message를 보냄 virtual BOOL OnMessagePending(); };
'정보공유 > 공부' 카테고리의 다른 글
MFC - 블락킹 현상(메세지 큐 지우기) (0) 2024.03.15 [MFC] What is Window Message Number??/ 윈도우 메세지 넘버 확인 / PeekMessage() / OnMessagePending() (1) 2024.01.10 [MFC] C++에서 JSON 다루기 / rapid JSON / JSON / 예외처리 (1) 2023.12.27 [MFC] 상단바 없애기 (0) 2023.12.18 [MFC] 조명 컨트롤러 연결 (Modbus 형식) + RS485 케이블을 이용한 컨트롤 (0) 2023.12.13