티스토리 뷰

190710
 

본 글은 교육을 받으면 노트필기 했던 내용을 그대로 올린내용입니다.


 
#include <WinSock2.h> // 윈도우즈 헤더보다 앞서서 정의한 것이 많다. 주의. 가장 (windows.h)위에 작성
// Windows.h 에서 이전 버전의 winsock.h 을 포함 시키기 떄문에 충돌이 난다.
#include <windows.h>
#include <TCHAR.H>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
        return -1;
    
    WSACleanup();
    return 0;
}
 
다이얼로그 베이스 기반
#include <WinSock2.h>
#include <windows.h>
#include <TCHAR.H>
#include "resource.h"
 
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
        return -1;
 
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1),NULL/*메인윈도는 부모가 널*/, DlgProc);
    
    WSACleanup();
    return 0;
}
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
    switch (iMsg) {
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case IDOK:
            EndDialog(hwnd, IDOK);
            break;
        case IDCANCEL:
            EndDialog(hwnd, IDCANCEL);
            break;
        }
        break;
    }
    return FALSE;
}
 
cf. 메인 윈도우는 부모가 NULL 이다. (바탕화면이 NULL 이기 때문에)
 
    SOCKADDR_IN clientaddr;
    int addrlen = sizeof(clientaddr);
    getpeername(sock, (SOCKADDR*)& clientaddr, &addrlen);
 
스레드의 순서제어
 
TCP는 경계가 없어서 개발자가 만들어 주어야한다.
 
            memcpy(sendBuf, &dataLen, sizeof(int));
            memcpy(sendBuf + 4, &dataType, sizeof(int));
 

String
 
문자열 저장방식 2가지
1. 길이+문자집합 (객체지향에서 많이 쓰인다.)
2. 문자집합+끝표식(NULL문자 '\0') (C/C++ 에서 쓰인다.)
 
이미 정해져 있는 상수는 리터럴 상수("ABC",'a' 등) 는 모두 GD메모리에 자리를 잡는다.
GD는 상수영역과 비상수 영역이 있다.
"ABC"는 "ABC"+'\0' 이 저장된 상수영역의 시작주소를 의미한다.
enum,const,define 으로 상수 만들 수도 있다.
 

서버코드
#include <WinSock2.h>
#include <windows.h>
#include <TCHAR.H>
#include <vector>
#include <algorithm>
#include "resource.h"
using namespace std;
 
#define HEADSIZE 8
 
enum { STRTYPE, FILETYPE };
 
CRITICAL_SECTION cs;//LOCK
BOOL CreateListenSock(SOCKET& listen_sock);
DWORD __stdcall AcceptThread(LPVOID param);
DWORD __stdcall ClientThread(LPVOID param);
void AddSocketList(vector<SOCKET>& sockList, SOCKET sock);
void RemoveSocketList(vector<SOCKET>& sockList, SOCKET sock);
void SendSocketList(vector<SOCKET>& sockList, const TCHAR* head, const TCHAR* buf, const int len);
int recvn(SOCKET s, char* buf, int len, int flags);
int sendn(SOCKET sock, const char* buf, int len, int flag);
 
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
void AddListBox(const TCHAR* msg);
 
HWND hDlg;
vector<SOCKET> sockList;
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
        return -1;
    InitializeCriticalSection(&cs); //LOCK기법 사용
    SOCKET listen_sock;
    if (CreateListenSock(listen_sock)) { //CreateListenSock 소켓->바인드->대기 까지함
        DWORD id;
        CloseHandle(CreateThread(NULL, 0, AcceptThread, (LPVOID)listen_sock, 0, &id));
 
        DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL/*메인윈도는 부모가 널*/, DlgProc);
 
        closesocket(listen_sock);
    }
    DeleteCriticalSection(&cs);
    WSACleanup();
    return 0;
}
 
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
 
    switch (iMsg) {
    case WM_INITDIALOG:
        ::hDlg = hwnd;
        break;
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case IDOK:
            EndDialog(hwnd, IDOK);
            break;
        case IDCANCEL:
            EndDialog(hwnd, IDCANCEL);
            break;
        }
        break;
    }
    return FALSE;
}
BOOL CreateListenSock(SOCKET& listen_sock) {
    #pragma region socket() 
    // socket()
    listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock == INVALID_SOCKET) return FALSE;
    #pragma endregion
 
    #pragma region bind()
    // bind()
    SOCKADDR_IN serveraddr;
    ZeroMemory(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(9000);
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    int retval = bind(listen_sock, (SOCKADDR*)& serveraddr, sizeof(serveraddr));
    if (retval == SOCKET_ERROR) return FALSE;
    #pragma endregion
 
    #pragma region listen()
    // listen()
    retval = listen(listen_sock, SOMAXCONN);
    if (retval == SOCKET_ERROR) return FALSE;// 소켓의 listen() 단계까지 기본적으로 처리
    #pragma endregion
 
    return true;
}
DWORD __stdcall AcceptThread(LPVOID param) {
    SOCKET listen_sock = (SOCKET)param;
    SOCKET client_sock;
    SOCKADDR_IN clientaddr;
    int addrlen;
 
 
    while (1) {
        #pragma region accept()
        addrlen = sizeof(clientaddr);
        client_sock = accept(listen_sock, (SOCKADDR*)& clientaddr, &addrlen);
        if (client_sock == INVALID_SOCKET)
            continue;
        #pragma endregion
        TCHAR buf[500];
        wsprintf(buf, "\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
        AddListBox(buf);
        AddSocketList(sockList, client_sock);
        //sockInfo[client_sock] = clientaddr;
 
        #pragma region Thread 생성
        DWORD threadID;
        HANDLE hThread;
        hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)client_sock, 0, &threadID);//accept() 된 클라이언트소켓으로 통신하는 쓰레드를 생성
        CloseHandle(hThread);
        #pragma endregion
    }
 
    return 0;
}
 
DWORD __stdcall ClientThread(LPVOID param) {
    SOCKET sock = (SOCKET)param;
    char head[HEADSIZE];
    char* buf; // 동적으로 사이즈가 변한다.
    int retval;
 
    while (1) {
        //recv
        retval = recvn(sock, head, HEADSIZE, 0); // 헤드 받기
        if (retval <= 0)
            break;
        int dataLen;
        int dataType;
        memcpy(&dataLen, head, sizeof(int));
        memcpy(&dataType, head + 4, sizeof(int));
        switch (dataType){ // 헤더에 있는 정보로 알맞게 데이터를 처리한다.
            case STRTYPE:
            {
                buf = new char[dataLen];
 
                retval = recvn(sock, buf, dataLen, 0); // 데이터 받기
                if (retval <= 0) {
                    delete[] buf;
                    break;
                }
 
                SOCKADDR_IN clientaddr;
                int addrlen = sizeof(clientaddr);
                getpeername(sock, (SOCKADDR*)& clientaddr, &addrlen);
 
                TCHAR msg[1024];
                wsprintf(msg, "받은 데이터[%s, %d:%d] : %s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), sock, buf);
                AddListBox(msg);
 
                SendSocketList(sockList, head, buf, retval); // 받은 데이터를 현재 생성된 모든 클라이언트 소켓들에게 보내기 위해서 호출한다.
                delete[] buf;
            }
                break;
            case FILETYPE:
                break;
        }
    }
    char msg[1024];
    SOCKADDR_IN clientaddr;
    int addrlen = sizeof(clientaddr);
    getpeername(sock, (SOCKADDR*)& clientaddr, &addrlen);
 
    wsprintf(msg, "\n[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d, sock:%d\n",
        inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), sock);
 
    AddListBox(msg); // 종료되었다는 메세지를 리스트 박스에 추가한다.
    RemoveSocketList(sockList, sock); // 클라이언트 소켓 종료시, 리스트에서도 제거한다.
    closesocket(sock);
 
 
    //sockInfo.erase(sock);
 
    return 0;
}
void AddListBox(const TCHAR* msg) {
    HWND hList = GetDlgItem(::hDlg, IDC_LIST1);
    SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
    int count = SendMessage(hList, LB_GETCOUNT, 0, 0);
    SendMessage(hList, LB_SETCURSEL, count - 1, 0);
    SendMessage(hList, LB_SETCURSEL, -1, 0); // 선택 제거 추가 될 때 마다 스크롤을 아래로 내려주기 위해서
}
 
void AddSocketList(vector<SOCKET>& sockList, SOCKET sock) {
    EnterCriticalSection(&cs);
    sockList.push_back(sock);
    LeaveCriticalSection(&cs);
}
void RemoveSocketList(vector<SOCKET>& sockList, SOCKET sock) {
    EnterCriticalSection(&cs);
    sockList.erase(find(sockList.begin(), sockList.end(), sock));
    LeaveCriticalSection(&cs);
}
void SendSocketList(vector<SOCKET>& sockList,const TCHAR* head, const TCHAR* buf, const int len) {
    EnterCriticalSection(&cs);
    for (unsigned i = 0; i < sockList.size(); ++i) {
        //send모든 클라이언트 소켓에게 전달한다. (같은 채팅방에 있는 것처럼 체감하도록)
        int retval = sendn(sockList[i], head, HEADSIZE, 0); //헤더 보내고
        if (retval <= 0)
            break;
 
        retval = sendn(sockList[i], buf, len, 0); //데이터 보내고
        if (retval <= 0)
            break;
    }
    LeaveCriticalSection(&cs);
}
int recvn(SOCKET s, char* buf, int len, int flags)
{
    int received;
    char* ptr = buf;
    int left = len;// 받아야할 데이터 크기
 
    while (left > 0) {
        received = recv(s, ptr, left, flags);
        if (received == SOCKET_ERROR)
            return SOCKET_ERROR;
        else if (received == 0)
            break;
        left -= received;// 받은 만큼 left에서 뺀다.
        ptr += received;// 이후 받아야 되는 부분의 시작주소(offset)
    }
 
    return (len - left); // 정상 수행됬으면 받아야하는 크기가 반환된다.
}
int sendn(SOCKET sock, const char* buf, int len, int flag) {
    const char* ptr = buf;
    int left = len;
    while (left > 0) { // 보낼 데이터가 남아있다면
        int sendLen = send(sock, ptr, left, flag);
        if (sendLen <= 0)
            break;
        left -= sendLen;// 보낸 만큼 left에서 뺀다.
        ptr += sendLen; // 보내야할 시작 주소를 갱신한다.
    }
    return len - left;
}

클라이언트코드
#include <WinSock2.h>
#include <windows.h>
#include <TCHAR.H>
#include <string.h>
#include "resource.h"
 
#define HEADSIZE 8
 
enum { STRTYPE, FILETYPE };
 
SOCKET ConnectServer(const TCHAR * ip, short port);
DWORD __stdcall RecvThread(LPVOID param);
 
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
void AddListBox(const TCHAR* msg);
int recvn(SOCKET s, char* buf, int len, int flags);
int sendn(SOCKET sock,const char* buf, int len, int flag);
HWND hDlg;
SOCKET sock;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
        return -1;
 
    sock = ConnectServer("127.0.0.1", 9000);// 주어진 ip,port로 connect까지 완료한다. (즉 통신소켓 반환받는다.)
    if (NULL != sock) {
        DWORD id;
        CloseHandle(CreateThread(NULL, 0, RecvThread, (LPVOID)sock, 0, &id)); //통신 스레드 생성후 다이얼로그 박스를 띄운다. (항상 recv를 하기위해서. 서버가 전체에 보낼 때마다 받기위해서)
        DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL/*메인윈도는 부모가 널*/, DlgProc);
 
        closesocket(sock);
    }
 
    WSACleanup();
 
    return 0;
}
 
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
    switch (iMsg) {
    case WM_INITDIALOG:
        hDlg = hwnd;
        break;
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case IDOK:
            EndDialog(hwnd, IDOK);
            break;
        case IDCANCEL:
            EndDialog(hwnd, IDCANCEL);
            break;
        case IDC_BUTTON1: // 전송 버튼을 누르면 전송한다.
        {
            TCHAR msg[1024];
            GetDlgItemText(hwnd, IDC_EDIT1, msg, 1024); // 에디트 박스에 입력한 문자를 가져온다.
            TCHAR buf[1124];
            wsprintf(buf, "[보낸데이터]:%s\n", msg); // 리스트 박스에 출력
            AddListBox(buf);
 
            int dataLen = lstrlen(msg) + 1;
            int dataType = STRTYPE;
            char* sendBuf = new char[HEADSIZE + dataLen];
            memcpy(sendBuf, &dataLen, sizeof(int));
            memcpy(sendBuf + 4, &dataType, sizeof(int));
            strcpy_s(sendBuf + HEADSIZE, dataLen, msg);
 
            int retval = sendn(sock, sendBuf, HEADSIZE+dataLen, 0);// 헤더까지 포함하여 한번에 보낸다.
 
            SetDlgItemText(hwnd, IDC_EDIT1, ""); // 보내고 나서는 에디트 박스 내용 초기화
        }
        break;
        }
        break;
    }
    return FALSE;
}
void AddListBox(const TCHAR* msg) {
    HWND hList = GetDlgItem(::hDlg, IDC_LIST1);
    SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
    int count = SendMessage(hList, LB_GETCOUNT, 0, 0);
    SendMessage(hList, LB_SETCURSEL, count - 1, 0);
    SendMessage(hList, LB_SETCURSEL, -1, 0); // 선택 제거
}
SOCKET ConnectServer(const TCHAR* ip, short port) {
    // socket()
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) return NULL;
 
    // connect()
    SOCKADDR_IN serveraddr;
    ZeroMemory(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(port);
    serveraddr.sin_addr.s_addr = inet_addr(ip);
    int retval = connect(sock, (SOCKADDR*)& serveraddr, sizeof(serveraddr));
    if (retval == SOCKET_ERROR)
        return NULL;
 
 
    return sock;
}
DWORD __stdcall RecvThread(LPVOID param) {
    SOCKET sock = (SOCKET)param;
    TCHAR head[HEADSIZE];
    TCHAR* buf;
    int retval;
 
    while (true)
    {
        retval = recvn(sock, head, HEADSIZE, 0); // 헤드를 받음
        if (retval <= 0)
            break;
        int dataLen;
        int dataType;
        memcpy(&dataLen, head, sizeof(int));
        memcpy(&dataType, head + 4, sizeof(int));
 
        switch (dataType) { // 헤드를 파악하여 알맞게 데이터를 처리한다.
        case STRTYPE:
            {
                buf = new char[dataLen];
                retval = recvn(sock, buf, dataLen, 0); // 헤더에 있는 데이터 길이만큼 데이터를 받는다.
                if (retval <= 0) {
                    delete[] buf;
                    break;
                }
                TCHAR msg[1024];
                wsprintf(msg, "[받은데이터] : %s\n", buf);
                AddListBox(msg);
                delete[] buf;
            }
            break;
        case FILETYPE:
            break;
        }
    }
    
    return 0;
}
int recvn(SOCKET s, char* buf, int len, int flags)
{
    int received;
    char* ptr = buf;
    int left = len;
 
    while (left > 0) {
        received = recv(s, ptr, left, flags);
        if (received == SOCKET_ERROR)
            return SOCKET_ERROR;
        else if (received == 0)
            break;
        left -= received;
        ptr += received;
    }
 
    return (len - left);
}
int sendn(SOCKET sock,const char* buf, int len, int flag) {
    const char* ptr = buf;
    int left = len;
    while (left > 0) { // 남아있는 바이트가 있다면,
        int sendLen = send(sock, ptr, left, flag);
        if (sendLen <= 0)
            break;
        left -= sendLen;
        ptr += sendLen;
    }
    return len - left;
}
 
댓글
댓글쓰기 폼