티스토리 뷰

190709
 
교육을 받으면서 노트필기 했던 내용을 날것 그대로 업로드합니다. 

 

 
다중 처리 에코 서버를 구현해보자.
 
대기 소켓 스레드 1개, 통신 소켓 스레드 2개 (수신, 발신)가 필요하다.
이때 대기 소켓 스레드는 프라이머리 스레드가 수행하도록 하고
통신 소켓 스레드는 서브 스레드가 처리하도록 구현할 것이다.
 
peer : 통신에 참여하는 엔드시스템
getpeername(sock, (SOCKADDR*)& clientaddr, &addrLen);
getpeername() : 원격 IP, 포트번호 얻기
getsockanme() : 로컬 IP, 포트번호 얻기
SOCKADDR : 옛날꺼
SOCKADDR_IN
 

 
데이터의 경계 만들기
1. markup : 끝나면 #(표식)을 붙여줌, EOR(End of record) 사용 등.
2. 고정 길이 패킷 데이터 : 항상 같은 길이의 데이터를 주고받는다. 보통 2k 를 넘지 않게한다.
3. 가변 길이 패킷 데이터 : 앞에 헤더가 꼭 있어야 한다. 헤더는 고정길이로 송수신하고 헤더에 실제 데이터 크기 정보를 넣는다.
*(실제 패킷 (이더넷+ip+헤더) 이 아니라 애플리케이션 패킷을 뜻한다! 실제 패킷설계는 아님.)
 
윈도우 슬라이스 : 큰 데이터를 잘라서 보냄. (네트워크 개론)
 
enum : 정수형(기본정수형 int) 사이즈와 같다. sizeof(enum) == 4, int 형식이라고 봐도 된다.
 
 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct _DataPack {
    char head[16];// [길이|type|extra|사용안함]
    char data[496];
} DataPack, * PDataPack;
 
typedef enum _DataType { STRING, INT, DOUBLE } DataType;
 
int main() {
    DataPack dp;
 
    //*(int*)dp.head = 100;
    //*(DataType*)(dp.head + 4) = STRING;
    //*(int*)(dp.head + 8) = 12;
    //strcpy_s(dp.data, 4, "ABC");
 
    int len = 100;
    DataType dt = STRING;
    int extra = 12;
    memcpy(dp.head,      &len,   sizeof(int)); // 메모리 덤프
    memcpy(dp.head + 4 &dt,    sizeof(DataType));
    memcpy(dp.head + 8 &extra, sizeof(int));
 
    //memcpy(dp.data, "ABC", strlen("ABC")+1);
    strcpy_s(dp.data, 4, "ABC");
 
    printf("size : %d\n", sizeof(DataPack));
    printf("enum size : %d\n\n", sizeof(DataType));
    printf("%d %d %d\n",
        *(int*)dp.head,
        *(DataPack*)(dp.head + 4),
        *(int*)(dp.head + 8));
    printf("%s \n", dp.data);
}
 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
 
typedef struct _DataPack {
    char head[16];// [길이|type|extra|사용안함]
    char data[496];
} DataPack, * PDataPack;
 
typedef enum _DataType { STRING, INT, DOUBLE } DataType;
 
typedef struct _Head {
    int dataLen;
    DataType datatype; //4byte
    int extra1;
    int extra2;
}Head; //주의 패딩바이트 들어가면안된다. (4의 배수로 맞춰서 할당했다.)
 
int main() {
    DataPack dp;
 
    int len = 100;
    DataType dt = STRING;
    int extra = 12;
    memcpy(dp.head, &len, sizeof(int)); // 메모리 덤프
    memcpy(dp.head + 4, &dt, sizeof(DataType));
    memcpy(dp.head + 8, &extra, sizeof(int));
    strcpy_s(dp.data, 4, "ABC");
 
    Head* ph = (Head*)& dp.head;
    printf("%d %d %d\n", ph->dataLen, ph->datatype, ph->extra1);
    printf("%s\n", dp.data);
}
100 0 12
ABC
 
Server 코드
//ITCOOKBOOK 윈도우네트워크프로그래밍 김선우저 소스
// -- : 내가추가한 주석
#include <winsock2.h>
#include <stdlib.h>
#include <stdio.h>
#define BUFSIZE 512
#define HEADSIZE 16
 
typedef enum _DataType { STRING } DataType;
 
typedef struct _HEAD {
    union {
        char buf[HEADSIZE];
        struct {
            int dataLen;
            DataType dt;
            int extra;
        }part; //struct 변수
    }head; //union 변수
} HEAD, Head;
 
#pragma region err처리
// 소켓 함수 오류 출력 후 종료 --심각한 에러 표기
void err_quit(const char* msg)
{
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | // 오류 메시지 저장 메모리를 내부에서 할당하라
        FORMAT_MESSAGE_FROM_SYSTEM, //운영체제로 부터 오류 메시지를 가져온다
        NULL,
        WSAGetLastError(), //오류 코드 // -- WSA붙은건 소켓 함수 라고생각 // -- 0이 아닌값은 에러
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //언어(제어판 설정 언어)
        (LPTSTR)& lpMsgBuf, // 오류 메시지 outparam
        0, NULL);
    MessageBox(NULL, (LPCTSTR)lpMsgBuf, msg, MB_ICONERROR);
    LocalFree(lpMsgBuf); // 오류 메시지 저장 메모리 반환
    exit(-1); // -- 프로그램 종료 (exit : c런타임 라이브러리 함수, 내부적으로 시스템함수가 호출)
}
 
// 소켓 함수 오류 출력 --비교적 덜 심각한 에러 표기
void err_display(const char* msg)
{
    LPVOID lpMsgBuf;
    int eCode = WSAGetLastError(); //-- 에러코드
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, eCode,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)& lpMsgBuf, 0, NULL);
    printf("[%s] %s", msg, (LPCTSTR)lpMsgBuf);
    LocalFree(lpMsgBuf); //-- 문자열 메모리를 할당하기 때문에 꼭 해제해야한다.
}
#pragma endregion
 
// 사용자 정의 데이터 수신 함수
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);
}
 
DWORD WINAPI ClientThread(LPVOID param) {
    SOCKET sock = (SOCKET)param;
    char buf[BUFSIZE]; //--리시브 버퍼
    int retval; //--리턴 값
 
    // 클라이언트와 데이터 통신
    while (1) {
        #pragma region recv()
        // 헤더 받기
        Head h;
        
        retval = recvn(sock,h.head.buf, HEADSIZE, 0);
        if (retval<=0)
            break;
        int dataLen =h.head.part.dataLen;
        DataType dt = h.head.part.dt;
 
        // 데이터 받기
        retval = recvn(sock, buf, dataLen, 0);
        if (retval <= 0)
            break;
        else {
            SOCKADDR_IN clientaddr;
            int addrLen = sizeof(clientaddr);
            getpeername(sock, (SOCKADDR*)&clientaddr, &addrLen);
 
            printf("받은 데이터[%s, %d:%d] : %s\t(%d byte)\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), (int)sock, buf, retval);
 
            #pragma region send()
            Head h;
            h.head.part.dataLen = dataLen;
            h.head.part.dt = dt;
 
            retval = send(sock, h.head.buf, HEADSIZE, 0); //-- 헤더보내기
            if (retval <= 0)
                break;
 
            retval = send(sock, buf, dataLen, 0); //-- 헤더보내기
            if (retval <= 0)
                break;
            printf("보낸 데이터 : %s\t(%d byte)\n\n", buf, retval);
            #pragma endregion
        }
        #pragma endregion
    }
 
    SOCKADDR_IN clientaddr;
    int addrLen = sizeof(clientaddr);
    getpeername(sock, (SOCKADDR*)& clientaddr, &addrLen);
    printf("\n[TCP 서버] 클라이언트 종료 : IP 주소=%s, 포트 번호=%d\n",
        inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    printf("[TCP 서버] 클라이언트 종료 : %d\n", (int)sock);
 
    // closesocket()
    closesocket(sock);
 
    return 0;
}
 
int main(int argc, char* argv[])
{
    int retval;
 
    #pragma region WSAStartup
    // 윈속 초기화
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
        return -1;
    #pragma endregion
 
    #pragma region socket()
    // socket()
    SOCKET listen_sock = socket( //--소켓을생성 (TCP)
        AF_INET, //주소체계: 통신 영역 설정, 인터넷 영역을 사용하며 리모트 컴퓨터 사이의 통신을 사용, IPv4
        SOCK_STREAM, //프로토콜유형(타입): TCP/IP 기반 사용 (-- SOCK_DGRAM :UDP)
        0 /*IPPROTO_TCP*/ //앞 두 인자로 프로토콜 결정이 명확하면 0사용, IPPROTO_TCP, IPPROTO_UDP
    );
    if (listen_sock == INVALID_SOCKET) err_quit("socket()");
    #pragma endregion
 
    #pragma region bind()
    // bind()
    SOCKADDR_IN serveraddr;
    ZeroMemory(&serveraddr, sizeof(serveraddr)); // memset() 과 똑같다. 0으로 set한다.
    serveraddr.sin_family = AF_INET; //주소체계 (인터넷 주소체계를 사용 : IP)
    serveraddr.sin_port = htons(9000); //지역포트번호
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //지역IP 주소 (아이피 상관없이 접근가능)
    retval = bind(
        listen_sock, //-- 해당 소켓에 연결 짓는다.
        (SOCKADDR*)& serveraddr, //-- 구조체의 주소
        sizeof(serveraddr) //-- 구조체의 크기
    ); //--속성부여, 연결 //--바인딩 : 소켓에 속성을 부여하는 작업
    if (retval == SOCKET_ERROR) err_quit("bind()");
    #pragma endregion
 
    #pragma region listen()
    // listen()
    retval = listen(
        listen_sock,
        SOMAXCONN //접속대기 큐의 크기
    ); // TCP 상태를 LISTENING 변경
    if (retval == SOCKET_ERROR) err_quit("listen()");
    #pragma endregion
 
    #pragma region variable
    // 데이터 통신에 사용할 변수
    SOCKET client_sock;
    SOCKADDR_IN clientaddr;
    int addrlen;
    //char buf[BUFSIZE + 1];
    #pragma endregion
 
    while (1) {
        #pragma region accept()
        // accept() : 접속 대기큐데 들어온 클라이언트의 정보를 꺼내온다.
        addrlen = sizeof(clientaddr);
        client_sock = accept(
            listen_sock, //대기 소켓
            (SOCKADDR*)& clientaddr, //클라이언트의 정보 out param
            &addrlen //주소구조체형식의크기, in(크기지정), out(초기화한크기반환) param
        ); //통신소켓 생성: 원격 IP, 원격 포트 결정
        if (client_sock == INVALID_SOCKET) {
            err_display("accept()");
            continue;
        }
        #pragma endregion
 
        printf("\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n",
            inet_ntoa(clientaddr.sin_addr), //문자열로 IP주소 변환 --클라이언트의 주소 정보
            ntohs(clientaddr.sin_port) // 포트번호 network to host --클라이언트의 포트 정보
        );
 
        #pragma region Thread 생성
        //-- 독립적인 스레드를 띠워서 소켓정보를 스레드에 전달해서 독립적으로 동작하도록 하자.
        //-- 클라이언트와 통신을 위해서 스레드를 생성하는 작업을 아래 한다.
        DWORD threadID;
        HANDLE hThread;
        hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)client_sock, 0, &threadID);
        CloseHandle(hThread);
        #pragma endregion
    }
 
    // closesocket()
    closesocket(listen_sock);
 
    // 윈속 종료
    WSACleanup();
    return 0;
}
 
Client 코드
//ITCOOKBOOK 윈도우네트워크프로그래밍 김선우저 소스
#include <winsock2.h>
#include <stdlib.h>
#include <stdio.h>
 
#define BUFSIZE 512
#define HEADSIZE 16
 
typedef enum _DataType { STRING } DataType;
 
typedef struct _Data {
    union {
        char buf[HEADSIZE];
        struct {
            int dataLen;
            DataType dt;
            int extra;
        }part; //struct 변수
    }head; //union 변수
    char buf[1];
} Data;
 
#pragma region Error 처리
// 소켓 함수 오류 출력 후 종료
void err_quit(const char* msg)
{
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, WSAGetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)& lpMsgBuf, 0, NULL);
    MessageBox(NULL, (LPCTSTR)lpMsgBuf, msg, MB_ICONERROR);
    LocalFree(lpMsgBuf);
    exit(-1);
}
 
// 소켓 함수 오류 출력
void err_display(const char* msg)
{
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, WSAGetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)& lpMsgBuf, 0, NULL);
    printf("[%s] %s", msg, (LPCTSTR)lpMsgBuf);
    LocalFree(lpMsgBuf);
}
#pragma endregion
 
// 사용자 정의 데이터 수신 함수
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 main(int argc, char* argv[])
{
    int retval;
 
    // 윈속 초기화
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
        return -1;
 
    // socket()
    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) err_quit("socket()");
 
    // connect()
    SOCKADDR_IN serveraddr;
    ZeroMemory(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(9000); // 대기소켓의 포트번호
    serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    retval = connect( // 접속요청
        sock, //소켓핸들 (-- connect 성공시 연결된다.)
        (SOCKADDR*)& serveraddr, //접속 서버 주소값 (서버정보)
        sizeof(serveraddr) //주소값 크기
    ); // 서버에 접속 요청(성공하면 자동으로 지역포트, 지역주소를 할당)
    if (retval == SOCKET_ERROR) err_quit("connect()");
 
    // 데이터 통신에 사용할 변수
    char buf[BUFSIZE];
    int len;
    Data* pd = (Data*)buf;
 
    // 서버와 데이터 통신
    while (1) {
        // 데이터 입력
        ZeroMemory(pd->buf, BUFSIZE - HEADSIZE);
        printf("\n[보낼 데이터] ");
        if (fgets(pd->buf, BUFSIZE - HEADSIZE, stdin) == NULL)
            break;
 
        // '\n' 문자 제거
        len = (int)strlen(pd->buf);
        if (pd->buf[len - 1] == '\n')
            pd->buf[len - 1] = '\0';
        if (strlen(pd->buf) == 0)
            break;
        
        pd->head.part.dataLen = len + 1;
        pd->head.part.dt = STRING;
    
 
        // 데이터 보내기
        retval = send(sock, buf, HEADSIZE +pd->head.part.dataLen, 0);
        if (retval<=0)
            break;
        printf("[TCP 클라이언트] %d바이트를 보냈습니다.\n\n", HEADSIZE + pd->head.part.dataLen);
 
        retval = recvn(sock, buf, HEADSIZE + pd->head.part.dataLen, 0);
        if (retval <= 0)
            break;
        printf("[TCP 받은 데이터] %s\n", pd->buf);
 
    }
 
    // closesocket()
    closesocket(sock);
 
    // 윈속 종료
    WSACleanup();
    return 0;
}
댓글
댓글쓰기 폼
네이버 이웃추가
«   2019/10   »
    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    
글 보관함