소켓은 무엇인가 ?
위키에서 정의한 소켓을 찾아 보았다.
소켓이라 하는건 컴퓨터 프로세스 간 통신의 종착점
즉, 플러그인(?) 형태의 전화기를 생각하면 될 것 같다.
OS 에서 제공해주는 이 소켓은 사용자에게는 직접적인 권한이 없으며 운영체제에게 명령을 보내 제어를 하게 되는데
윈도우에서는 핸들을 가지고 제어를 하게 되고, 리눅스에서는 디스크립터로 제어를 하게 된다.
핸들이나 디스크립터나 비슷한 개념이지만 약간 다른점이 있다.
저 둘의 차이는 언젠간 정리를 해서 올려보도록 하겠다 .
이 소켓통신이라 함은 컴퓨터와 컴퓨터 사이 네트워크를 통해 상호통신을 하는것을 말하게 되는데
우리 입장에선 어플리케이션끼리의 통신이다보니 소켓 또한 IPC 통신에 해당한다고 볼 수도 있겠다.
소켓 통신의 순서는 전화하는것과 비슷하다 .
A 와 B 가 전화를 하려면 각각 전화기가 필요하고, 번호가 필요하고, 번호의 회선 개통, 전화 대기, 전화 받기 로 나누어 진다.
전화기 = socket()
전화번호 = IP, PORT
번호의 회선 개통 = bind()
전화대기 = listen()
전화받기 = accept()
전화 끊기 = closesocket()
함수들에 대한 설명은 밑에 하겠다 .
윤성우씨의 TCP/IP 소켓 프로그래밍을 보면 전화로 예를 들게 되는데 그걸로 예를 들면 될것 같다 .
1. socket() 함수로 전화기를 설치
2. SOCKADDR_IN 구조체 할당 전화기의 번호 할당
3. bind() 함수로 주소와 소켓을 묶음
4. listen() 함수로 소켓을 listening 상태로 전환
5. 들어온 연결요청을 수락 ( Blocking Function )
6. 통신 완료 시 소켓 통신 종료
소켓의 할당에는 약간의 준비과정이 필요하다 .
첫번째로 윈속헤더를 선언해준다
윈도우 소켓관련 함수를 담고있는 라이브러리는 ws2_32.lib 인데
기본적으로 포함되는 라이브러리가 아니기 때문에 따로 링크를 걸어주어야 한다.
( #pragma 를 이용하지 않고 프로젝트 속성에서 링커 -> 입력 -> 추가 종속성 에서 따로 추가를 해주어도 상관없다 . )
헤더파일을 포함시키고 라이브러리를 링크시켰다면 기본적인 준비는 끝났다 .
** 윈도우 에서는 WSAStartup() 이란 함수를 사용하여 WSADATA 형식의 구조체를 초기화 해준 후 할당 해야 한다 .
윈도우의 경우 소켓의 버전정보를 넘겨 주어야 하기 때문에 사용한다 .
버전 정보를 넘겨주기 위한 WSAStartup() 함수의 원형을 찾아보자 .
( https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms742213(v=vs.85).aspx )
첫번째 인자로 넘겨주는 값이 소켓에 버전정보에 해당하는 부분이다.
링크의 MSDN 의 정보를 보면 상위 바이트엔 마이너 버전, 하위 바이트엔 메이저 버전을 명시하도록 되어있다.
2바이트의 상위,하위 바이트를 하나하나 계산해가면서 넣기 힘들기 때문에 MS에서는 MAKEWORD 를 지원한다 .
MAKEWORD() 의 원형을 보자
( https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms632663(v=vs.85).aspx )
첫번째 인자로는 하위 바이트, 두번째 인자로는 상위 바이트를 넘기면 된다 .
윈속 2.1 버전을 넘기려 한다면 MAKEWORD(2,1) 을 넘기면 된다 .
socket() 함수의 원형을 찾아보자 .
( https://msdn.microsoft.com/en-us/library/windows/desktop/ms740506(v=vs.85).aspx )
첫번째 인자는 주소의 주소체계를 뜻한다 .
여러가지가 있지만 우린 IPv4 를 사용하므로 AF_INET 을 사용하도록 한다 .
두번째 인자는 소켓의 타입이다 .
통신의 소켓은 크게 TCP 와 UDP 로 나뉘게 되는데 ( 종류는 굉장히 많다 )
알아서 설정하자
SOCK_STREAM ( TCP )
SOCK_DGRAM ( UDP )
세번째 인자로는 프로토콜을 정해준다 .
TCP 와 UDP 마다 프로토콜이 다르게 나오는데 MSDN 을 참고하자
보통 0 을 주게 되면 소켓타입의 기본값이 설정되고
나머지를 확인하려면 MSDN 을 참고하자 .
리턴값으로는 에러가 발생하지 않는다면 소켓의 핸들을 반환한다 .
에러 발생시에는 INVALID_SOCKET 을 반환한다 .
소켓에 주소를 할당 해보자
bind() 함수를 사용하기 위해선 먼저 소켓에 할당할 주소를 지정해주는 일을 먼저 해야한다 .
네트워크를 공부한 사람이라면 TCP 헤더를 잘 알고있을것이다 .
하지만 우리는 운영체제가 제공하는 소켓을 사용하기 때문에 쓰기 좋게 만들어진 구조체가 존재한다 .
이건 Winsock2.h 에 포함이 되어있으며 그냥 가져다 쓰면 된다 ..-_-;
SOCKADDR_IN 이라는 구조체를 사용하여 주소를 그대로 넣고 소켓할당이 가능하다 .
사용 예를 보자 .
먼저 htonl() , htons() 를 알아야 하는데 이건 바이트 오더링 때문에 사용을 한다 .
리틀엔디언, 빅엔디언 이란 말을 들어본적이 있는가 ?
간단하게 말해서 표기식이 다르다. 왼쪽에서 오른쪽으로 읽냐 오른쪽에서 왼쪽으로 읽냐의 차이 .
인텔CPU 의 경우 리틀 엔디언 방식을 사용하는데 네트워크 통신시엔 빅 엔디언을 사용하게 된다 .
( CPU 가 빅엔디언 방식이라 해도 호환성을 위해서 그냥 써주자 )
htonl() 의 약자가 host to network long 의 약자이다 .
htons() 는 short 타입 .
우리가 사용하는 IPv4 주소체계는 32비트 주소 체계이다 ( 4바이트 == long )
Port 는 16비트 주소체계 ( 2바이트 == short )
이렇게만 알고 넘어가자 ....
Serv_addr.sin_addr.s_addr 은 IP 주소를 넣게 된다. 이건 그냥 외워야 한다 .
INADDR_ANY 는 서버에서 모든 NIC 를 할당하고 싶을때 사용하면 된다 . ( 너무 길어지니까 자세한건 찾아봐... )
서버에선 INADDR_ANY 를 사용하지만 클라이언트 입장에선 특정 주소로 연결요청을 넣어야 한다.
이때 사용하는 함수가 inet_addr() 함수이다 .
문자열로 된 값을 넘겨주면 네트워크 바이트로 변환하여 반환해준다 .
Ex. inet_addr("192.168.0.50")
Serv_addr.sin_port 는 당연히 포트번호를 정수로 주면 된다 .
( 위의 예제에선 #define PORT 9000 이 위에 선언된 상태 )
Serv_addr.sin_family 에서의 PF_INET 은 헤더를 까보면 알겠지만 AF_INET 과 똑같다 .
이번엔 bind() 함수의 원형을 찾아보자 .
( https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms737550(v=vs.85).aspx )
첫번째 인자로는 socket 핸들을 넘겨준다 .
두번째 인자는 아까 만든 SOCKADDR_IN 의 구조체를 넘겨주는데 타입이 SOCKADDR* 이다.
그런 고로 SOCKADDR* 로 캐스팅하여 인자를 넘겨주도록 하자 .
세번째 인자는 SOCKADDR_IN 의 크기를 넘겨주면 된다 .
SOCKADDR 의 크기와 같으므로 어떤 크기를 넘겨주던 상관 없다 .
리턴 값으로는 성공시 0, 실패시 SOCKET_ERROR 를 반환한다 .
통신을 위해선 포트가 열려있어야 하는데 소켓의 상태를 listen 상태로 전환을 해야한다.
그럼 listen 상태로 변환시켜주는 listen() 함수의 원형을 찾아보자 !
( https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms739168(v=vs.85).aspx )
정말 간단하다 .
첫번째 인자로 socket 핸들을 넘겨주고
두번째 인자로 백로그큐의 크기를 넘겨준다 .
여기서 백로그란 대기열을 생각하면 된다 .
계산을 하기위해 줄을 서있을때 한줄에 10명이 서있을 수 있다면 백로그가 10인 것이다 .
백로그큐에 들어온 정보로 accept() 를 호출하여 소켓에 연결하는데
백로그큐의 크기를 넘어 연결요청이 온다면 바로 실패를 하게 된다 .
리턴 값으로는 성공시 0, 실패시 SOCKET_ERROR 를 반환한다 .
그럼 연결요청을 수락해주는 accept() 함수의 원형을 찾아보자
( https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms737526(v=vs.85).aspx )
첫번째 인자로는 소켓의 핸들을 넘겨주고 ( 리슨상태중인 소켓 )
두번째 인자를 보면 SOCKADDR 의 주소를 넘겨주는데
여기는 Out 타입이다 . ( 중요 )
Out 타입이란 주소를 넘겨주어 함수 내에서 해당 주소에 값을 넣는것을 말한다.
즉, 메인함수에선 해당 변수에 값이 반환되어 나오는것.
주소를 반환 받는건 접속 요청들어온 클라이언트의 주소값을 넣어주기 떄문에
클라이언트의 정보를 담을 변수를 넣어줘야 한다.
세번째 인자는 SOCKADDR 의 크기를 넘겨주면 되는데
위의 bind() 함수와 다르게 Inout 타입이기 때문에 크기를 담은 변수를 만들어주고
그 변수의 주소를 넘겨주어야 한다 .
리턴 값으로는 성공 시 연결 요청이 들어온 소켓의 핸들을 반환하고,
실패시 INVALID_SOCKET 을 반환합니다 .
구체적인 오류 코드를 확인하려면 WSAGetLastError 를 호출함으로 알 수 있습니다.
마지막으로 연결을 종료하는 closesocket() 함수를 알아봅시다 .
정말 간단하다 .
인자로 연결을 끊을 소켓의 핸들을 넘겨주면 끝 !
리턴 값은 성공시 0 , 실패 시 SOCKET_ERROR 를 반환한다 .
이 또한 실패시 구체적인 에러내용을 아려면 WSAGetLastError 함수를 부르면 된다 .
기본적인 소켓 연결에 필요한 모든 함수들을 알아보았고,
TCP서버, TCP클라이언트의 간단한 예제 소스는 따로 올리도록 하겠습니다 .
( [Programing/Example Code] - TCP 서버, 클라이언트 연결 예제 )
잘못된 내용은 거침없이 지적해주세요 .
'Programing > Win32API' 카테고리의 다른 글
바이트 순서 ( 엔디언 표기법 ) (0) | 2015.11.18 |
---|---|
CreateProcess , CreateThread (0) | 2015.09.26 |
프로세스와 쓰레드 (0) | 2015.09.26 |
대화상자 모달, 모달리스 (0) | 2015.09.19 |
WinAPI 의 CALLBACK 함수 (0) | 2015.09.12 |