Windows Socket

Windows TCP서버 소켓 통신 시작하기

코카(Coca) 2020. 8. 9. 01:49

이전의 글에서 소켓을 만드는 법을 배웠다. 이제는 만든 소켓을 이용해서 두 프로세스 간의 통신을 하는 작업을 할 것이다.
여기서 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와 크기를 맞추기위해 존재하지만, 아무런 의미를 가지지 않고있다. 

[출처] https://topic.alibabacloud.com/a/the-difference-between-sockaddr-and-sockaddr_in-reproduced_8_8_31409815.html

 

The difference between sockaddr and sockaddr_in (reproduced)

Original link: http://kenby.iteye.com/blog/1149001 struct sockaddr and struct sockaddr_in are two structures used to handle the addresses of network traffic. In various system calls or functions, these two structures are used whenever dealing with network

topic.alibabacloud.com

사실 이후 다루게 될 소켓에 주소를 할당하는 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로 생략이 가능하다.


위의 과정을 거쳐서 서버의 소켓을 생성하였다. 다음 번에는 클라이언트 프로그램에서 소켓을 생성하는 부분과, 서버와의 통신에 대해 포스팅해보겠다.