(2-5 그림)
이번에는 소켓을 생성하고 닫는 방법을 알아볼 것이다.
소켓을 사용해 통신을 하기 위한 기본 요건은 통신 양단이 같은 프로토콜을 사용하는 것이다.
socket()
함수는 사용자가 요청한 프로토콜을 사용해 통신할 수 있도록 내부적으로 리소스를 할당하고, 접근할 수 있는 일종의 핸들 값(SOCKET 타입, 32비트 정수)을 리턴한다. 이 값을 소켓 디스크립터(socket descriptor)라고 하고, 각종 소켓 함수를 호출할 때 인자로 전달하여 사용한다.
// 성공: 새로운 소켓, 실패: INVALID_SOCKET
SOCKET socket(
int af,
int type,
int protocol
)
통신을 하기 위해 통신 상태를 유일하게 지정할 수 있는 주소가 필요하다. 주소 체계(address family)란 이러한 주소 지정 방법을 지칭하는 용어이다. 주소 체계는 네트워크 프로토콜의 종류에 따라 달라진다.
winsock2.h
또는 ws2def.h
파일을 찾아보면 다음과 같이 AF_
로 시작하는 상수를 찾을 수 있다. 자신이 사용할 프로토콜에 대응하는 값을 선택하여 socket()
함수에 사용하면 된다. 예를 들어 IPv4 기반 TCP나 UDP 프로토콜을 사용하려면 AF_INET
값을 선택하면 된다.
...
#define AF_INET 2 // Internetwork: UDP, TCP, etc.
...
#define AF_INET6 23 // Internetwork Version 6
...
#define AF_IRDA 26 // IrDA
...
#define AF_BTH 32 // Blutooth RFCOMM/L2CAP protocols
...
소켓 타입은 사용할 프로토콜의 특성을 나타내는 값이다. 자주 사용하는 소켓 타입을 요약하면 아래와 같다.
소켓 타입 | 특성 |
---|---|
SOCK_STREAM | 신뢰성 있는 데이터 전송 기능 제공, 연결형 프로토콜 |
SOCK_DGRAM | 신뢰성 없는 데이터 전송 기능 제공, 비연결형 프로토콜 |
소켓 타입은 네트워크 프로토콜의 종류에 따라 달라진다. 주로 사용되는 TCP나 UDP 프로토콜을 사용하려면 주소 체계와 소켓 타입을 아래 같이 설정하면 된다.
사용할 프로토콜 | 주소 체계 | 소켓 타입 |
---|---|---|
TCP | AF_INET 또는 AF_INET6 | SOCK_STREAM |
UDP | AF_INET 또는 AF_INET6 | SOCK_DGRAM |
AF_INET
과PF_INET
간혹 주소체계로AF_
대신PF_
(Protocol Family)로 시작하는 상수를 사용하는 코드를 볼 수 있다. 이런 코드는 초기 버클리 유닉스에서 소켓 시스템 콜을 설계할 당시socket()
함수에는PF_*
상수를 사용하고, 소켓 주소 구조체에는AF_*
상수를 사용하도록 정의했다는 사실에서 유래한 것이다. 그러나PF_*
와AF_*
상수의 실제 정의가 같고, 현실적으로AF_*
형태를 많이 사용한다.
위의 표와 같이 주소 체계와 소켓 타입만으로 프로토콜을 결정할 수 있는 경우가 있다. 그러나 일반적으로 주소 체계와 소켓 타입이 같아도 이에 해당하는 프로토콜이 2개 이상 존재할 수 있다. 이 경우 프로토콜을 명시적으로 지정해줘야 한다.
주로 사용되는 TCP나 UDP 프로토콜을 사용하려면 주소 체계와 소켓 타입 그리고 프로토콜을 아리와 같이 설정할 수 있다.
사용할 프로토콜 | 주소 체계 | 소켓 타입 | 프로토콜 |
---|---|---|---|
TCP | AF_INET 또는 AF_INET6 | SOCK_STREAM | IPPROTO_TCP |
UDP | AF_INET 또는 AF_INET6 | SOCK_DGRAM | IPPROTO_UDP |
그러나 TCP나 UDP 프로토콜의 경우 주소 체계나 소켓 타입만으로도 프로토콜을 결정할 수 있기 때문에 일반적으로 프로토콜 부분에 0을 사용한다.
소켓을 사용한 통신을 마치면 관련 리소스를 반환해야 한다. closesocket()
함수는 해당 소켓을 닫고 관련 리소스를 반환한다.
// 성공: 0, 실패: SOCKET_ERROR
int closesocket(
SOCKET s
);
아래 코드는 위에서 살펴본 것을 활용해 소켓을 생성하고 닫는 예제이다.
#pragma comment(lib, "ws2_32")
#include <winsock2.h>
// 소켓 함수 오류 출력 후 종료
void err_quit(char *msg)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMATE_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);
}
int main(int argc, char *argv[])
{
// 윈속 초기화
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return 1;
MessageBox(NULL, "윈속 초기화 성공", "알림", MB_OK);
// socket() - 소켓 생성
SOCKET tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_sock == INVALID_SOCKET) err_quit("socket()");
MessageBox(NULL, "TCP 소켓 생성 성공", "알림", MB_OK);
// closesocket() - 소켓 닫기
closesocket(tcp_socket);
// 윈속 종료
WSACleanup();
return 0;
}
캄피일, 링크 후 실행 결과는 다음과 같다. 소켓 생성이 성공했음을 알 수 있다.
참고 자료
김성우 저, "TCP/IP 윈도우 소켓 프로그래밍", 한빛아카데미, 2018