kakasoo

[TCP/IP] 윈도우 기반 소켓 구현 본문

프로그래밍/네트워크

[TCP/IP] 윈도우 기반 소켓 구현

카카수(kakasoo) 2020. 7. 13. 12:47
반응형

윈속(윈도우 소켓)을 기반으로 하는 프로그램을 개발하기 위해서는 다음 2가지를 선행해야 한다.

1. header file로 winsock2.h 를 포함시킨다.

2. ws2_32.lib 라이브러리를 링크시켜야 한다.

해당 내용은 설정에서, 추가 종속성의 우편에 적어주면 된다. (ws2_32.lib)

이를 통해 헤더 파일로 추가하면 윈속과 관련된 함수들을 마음대로 호출할 수 있게 된다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <WinSock2.h>
#include <iostream>
using namespace std;
 
void ErrorHandling(const char* message) {
    fputs(message, stderr); // buffer 문제를 없애기 위해서 stderr를 전달 => 기본출력장치(모니터), 에러
    fputc('\n', stderr);
}
 
int main(void) {
    WSADATA wsaData; // WSAStartup의 두번째 매개변수로 보낼 구조체
    if (WSAStartup(MAKEWORD(22), &wsaData) != 0)
        ErrorHandling("WSAStartup() error!");
 
    return 0;
}
 

 

 

일단 반드시 WSAstartup() 함수를 호출해야 하는데, 이는 아마도(?) Window Socket Api를 의미하는 걸로 보인다.

여기서 두 개의 매개 변수를 받아서 함수를 호출해줘야 하는데,

첫번째 매개변수는 socket의 version이다.

버전은 다시 주 version과 부 version으로 나뉘게 되는데, 이는 0x0000의 꼴의 값을 가지기 때문에,

이를 MAKEWORD() 함수에 주와 부 값을 차례대로 입력함으로써 변환시킬 수 있다.

변환한 값을 첫번째로 넣고 나면, 두번째 매개변수는 WSADATA의 주소 값을 전달해야 한다, 특별한 의미는 없다.

한 가지 더 함수를 말하자면, WSACleanup(void) 함수가 있다.

이 함수는, 윈속 라이브러리를 운영체제에 반환시키면서 더 이상 함수의 윈도호출을 불가능하게 막는 함수이다.

최종적으로 프로그래밍이 종료될 때에 입력한다.

 


 

시작과 종료하는 법을 배웠으니, 다음으로 윈도우 기반으로 소켓을 다루는 함수들을 말해보겠다.

이는 어제 말한 것과 동일한 순서로, socket - bind - listen - accept 의 순서를 지니고 있다.

반대쪽 클라이언트 socket에서는 다시, connect와 closesocket이라는 함수들을 가지고 있다.

함수의 명칭은 동일할지 몰라도, (colsesocket을 제외하고) 반환형과 매개변수에서 리눅스와 차이가 있는 경우가 있으니 두 운영체제에서의 함수가 같다고 혼동하는 일이 없도록 주의하자.

모든 함수들은, 성공 시에는 소캣핸들이나 0을 반환하는 반면, 실패 시에는 ERROR를 반환한다.

 

윈도우에서의 파일 핸들과 소켓 핸들

리눅스에서는 파일 또는 소켓이 파일 디스크립터를 반환받는다고 말한 바 있다, 그래서 차례대로 넘버링이 된다.

하지만 윈도우에서는 파일 핸들과 소켓 핸들 (핸들이라는 명칭이지만, 디스크립터와 같다.)을 반환받긴 하더라도, 이 둘을 서로 다른 것으로 구분하고 있다.

이러한 차이가, 함수에도 영향을 미쳐서 매개변수와 반환형에 차이를 만들었다.

리눅스에서는 모든 함수를 int 값을 반환하면 되지만, 윈도우에서는 소켓의 생성과 요청 승인 단계에서 SOCKET을 반환해주는 것이 가장 큰 차이다.

 

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
41
42
43
44
45
46
47
48
49
50
#include <WinSock2.h>
#include <iostream>
using namespace std;
 
void ErrorHandling(const char* message) {
    fputs(message, stderr); // buffer 문제를 없애기 위해서 stderr를 전달 => 기본출력장치(모니터), 에러
    fputc('\n', stderr);
}
 
// argc == 옵션의 개수
// argv == 옵션의 값, 명칭
int main(int argc, char* argv[]) {
    WSADATA wsaData;
    SOCKET hServSock, hClntSock;
    SOCKADDR_IN servAddr, clntAddr; // server와 client socket을 만들고 주소 변수도 생성했다.
 
    int szClntAddr;
    char message[] = "hello, World!"// 보내고 싶은 메시지를 배열에 저장하였다.
 
    if (argc != 2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(-1);
    }
 
    if (WSAStartup(MAKEWORD(22), &wsaData) != 0// WSA를 start한다.
        ErrorHandling("WSAStartup() error!");
 
    hServSock = socket(PF_INET, SOCK_STREAM, 0); // PF_INET = IPv4의 프로토콜
    if (hServSock == INVALID_SOCKET) // socket() 함수는 SOCKET 구조체 또는 Error 값을 반환한다.
        ErrorHandling("socket() error!");
 
    memset(&servAddr, 0sizeof(servAddr)); // servAddr을 모두 0으로 초기화하였다.
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1])); // 주소 설정하는 방법은 추후에.
 
    if (bind(hServSock, (SOCKADDR*&servAddr, sizeof(servAddr)) == SOCKET_ERROR) // 소켓에 주소 할당
        ErrorHandling("bind() error!");
    
    szClntAddr = sizeof(clntAddr);
    hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr); // hClntSock을 ServSock에 접근 허용
    if (hClntSock == INVALID_SOCKET)
        ErrorHandling("accept() error!");
 
    send(hClntSock, message, sizeof(message), 0); // 클라이언트에 데이터를 전송한다.
    closesocket(hClntSock);
    closesocket(hServSock);
    WSACleanup();
    return 0;
}
cs

 

server 측 socket만 정리했음에도 모르는 함수가 있을 것이고, 갑작스럽게 코드가 길어지고 복잡해진 게 보인다.

하지만 주석만 가져다가 따져보면 다음의 순서와 같다.

1. WSAStartup() -> 이를 위해서 MAKEWORD() 함수가 필요했고, WSAData 구조체 하나를 생성해야 했다.

2. socket() -> 소켓을 만들기 위해서 망을 명시해서 넣어줘야 했고, 소켓 타입을 명시했다, 또 프로토콜을 넣어줬다.

3. bind() -> 그렇게 만든 소켓에 주소(전화번호에 해당)를 할당해주었다.

4. accept() -> ClntSock이 ServSock에 접근할 수 있게 해주었고,

5. send() -> message를 전달해보았다.

6. close() -> 다 끝났으므로 socket을 닫아주고,

7. WSACleanup() -> 더 이상의 함수 호출이 불가능하게, 함수들을 운영체제에 반환했다.

조건문을 통해 error인 경우를 모두 처리해주고, 또한 중간 중간 필요한 값들을 가져다 왔기 때문에 코드가 길어보인 것.

 

물론 이것만 있어서는 동작하지 않고, 클라이언트 측의 소스 코드도 작성해주어야 한다.

 

순서가 같은데,

1. WSAStartup()

2. socket() -> 이번에는 ClntSock을 선언해주는 것이다.

3. cconect() -> 이번에는 bind 없이 연결만 해주면 된다.

4. 연결되면 자동적으로 message가 오기 때문에, 이것이 정상적으로 왔는지 체크해줄 필요가 있다.

5. 문자열 길이를 판별하여 정상적인 전달이 이루어지지 않았으면 error message를 반환해주자.

6. close() and WSACleanup()

 

위의 코드에 잘못된 게 있는데, listen() 함수가 빠져있다.

다음 포스팅에서, listen()을 포함하여 코드를 게시하고, 실행파일 사용법을 작성하겠다.

반응형