Windows TCP서버 소켓 통신 시작하기
이전의 글에서 소켓을 만드는 법을 배웠다. 이제는 만든 소켓을 이용해서 두 프로세스 간의 통신을 하는 작업을 할 것이다.
여기서 TCP환경과 UDP환경에서 필요한 작업이 다르다. 그래서 이번 포스팅에서는 TCP에서의 통신을 먼저 알아보려 한다. 만들어진 소켓을 이용해서 서버가 TCP통신을 시작하는 일련의 과정들을 포스팅한다.
SOCKET servSocket, clntSocket;
//1. 소켓 생성하기
servSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2. 소켓 주소 구조체 만들기
sockaddr_in servAddr;
ZeroMemory( &servAddr, sizeof(servAddr) );
//3. 소켓 주소 구조체에 정보 작성하기
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servAddr.sin_port = htons(11021);
//4. 소켓에 주소 bind하기
if ( bind(servSocket, (sockaddr*)& servAddr, sizeof(servAddr)) != 0 ) {
printf("socket bind error!");
return;
}
//5. 소켓을 listen상태로 만들기
if ( listen(servSocket, 0) != 0 ) {
printf("socket listen error!\n");
return;
}
//6. 클라이언트의 연결이 오면 accept하기
int clntAddrLen = sizeof(clntAddr);
clntSocket = accept( servSocket, (sockaddr*)&clntAddr, &clntAddrLen);
if (clntSocket == -1) {
printf("socket accept error!\n");
return;
}
1) 소켓주소 구조체 정의하기
먼저 소켓을 통신하기 전에, 소켓의 주소를 할당하는 작업이 필요하다.
소켓이 연결할 주소를 저장하는 구조체를 윈도우즈 소켓 라이브러리가 제공해준다.
소켓 구조체중에 대표적인 2가지인 sockaddr구조체와 sockaddr_in구조체를 알아보자.
struct sockaddr {
ushort sa_family;
char sa_data[14];
};
sockaddr은 2byte의 주소체계를 지정하는 부분과, 14byte의 주소정보를 지정하는 부분이 있다.
sockaddr 구조체의 주소 데이터를 저장하는 sa_data배열에는 ip주소(ex 127.0.0.1)과 port번호(8080), 공백문자(sockaddr구조체의 총 크기를 16byte로 맞추기위함)로 구성되어 있다. 공백문자들로 구성되어있다.
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
struct in_addr {
unsigned long s_addr;
};
반면 sockaddr_in은 2byte의 주소체계를 지정하는 부분과, 2byte의 포트를 지정하는 부분, 4byte 크기의 주소를 저장하는 in_addr구조체로 구성된 sin_addr, 그 외에 8byte의 0으로만 구성된 sin_zero필드가 구성되어있다.
sin_zero는 sockaddr_in구조체가 sockaddr와 크기를 맞추기위해 존재하지만, 아무런 의미를 가지지 않고있다.
사실 이후 다루게 될 소켓에 주소를 할당하는 bind함수는 sockaddr 구조체를 사용한다. 따라서 연결할 주소정보와 포트정보가 분리된 sockaddr_in 구조체는 sockaddr정보로 변환되어 할당된다.
2) bind(): 소켓의 주소 할당하기
소켓 주소 구조체를 정의하였으면, bind함수를 통해 정의된 주소를 소켓에 할당할 수 있다.
bind 함수는 소켓에 ip주소를 할당하는 역할을 한다.
int bind(
SOCKET s,
const sockaddr* addr,
int addrlen
);
1번째 인자: 주소를 할당할 소켓
2번째 인자: 소켓의 주소
3번째 인자: 소켓의 주소정보의 길이
이 bind 함수는 정상적으로 할당된다면 0을 반환한다. 만일 오류가 발생한다면 SOCKET_ERROR(-1 값)을 반환한다.
3) listen(): 소켓을 클라이언트를 받을 수 있는 상태로 만들기
bind까지 마친 소켓은 주소를 가진 소켓이 된다. 여기에서 다른 곳에서 접속을 시도하는 클라이언트를 받을 수 있게 하려면, 해당 소켓을 접속 대기상태로 만들어줘야한다. 이는 listen함수를 이용하여 진행한다.
int WSAAPI listen(
SOCKET s,
int backlog
);
1번째 인자: listen상태로 만들 소켓
2번째 인자: 대기 가능한 최대 연결 갯수. 0을 지정하면 default값으로 지정된다.
이 작업을 진행한 소켓은 이제 수동적으로 연결이 오기를 기다리게 된다. listen함수를 성공하면 0, 실패하면 -1을 반환한다.
4) accept(): 클라이언트로부터 오는 연결을 수락한다
listen상태가 된 소켓은 클라이언트로부터 연결이 오면, accept함수를 이용해 그 정보를 저장한다.
이 함수는 연결된 클라이언트를 정보로 담은 소켓을 반환값으로 return한다. 만일 accept에 실패하면 -1을 반환한다.
accept함수는 블로킹 함수이다. 따라서 이 함수가 실행될 때 까지 해당 스레드는 대기하게 된다.
SOCKET WSAAPI accept(
SOCKET s,
sockaddr* addr,
int* addrlen
);
1번째 인자: listen 상태가 된 서버소켓
2번째 인자: 연결된 클라이언트의 주소정보를 담을 sockaddr 구조체. 연결이 되면 해당 구조체에 클라이언트 주소정보가 저장된다.
3번째 인자: 2번째 인자로 넘겨준 구조체의 크기
여기서 2번째와 3번째 인자는 옵션이므로 NULL로 생략이 가능하다.
위의 과정을 거쳐서 서버의 소켓을 생성하였다. 다음 번에는 클라이언트 프로그램에서 소켓을 생성하는 부분과, 서버와의 통신에 대해 포스팅해보겠다.