Computer Science 기본 지식/소켓 프로그래밍

[TCP/IP 소켓 프로그래밍] (9) TCP 긴급 메세지

로파이 2021. 4. 1. 21:14

출처 : 열혈 TCP/IP 소켓 프로그래밍 윤성우 저

 

Linux에서 read/write 함수외에 윈도우에도 존재하는 send와 recv함수를 사용할 수 있다.

 

- send 함수

ssize_t send(int sockfd, const void* buf, size_t nbytes, int flags);

- recv 함수

ssize_t recv(int sockfd, void* buf, size_t nbytes, int flags);

 

read/write와의 차이점은 소켓 옵션을 flags로 전달가능한데, 사용가능한 옵션의 예는 다음과 같이 있다.

  • MSG_OOB: 긴급 데이터(Out-of-band data)의 전송을 위한 옵션
  • MSG_PEEK: 입력 버퍼에 수신된 데이터의 존재 유무를 확인을 위한 옵션
  • MSG_DONTROUTE: 데이터 전송과정에서 라우팅 테이블을 참조하지 않음. 로컬 네트워크 상의 목적지를 이용할 때 사용하는 옵션
  • MSG_DONTWAIT: 입출력 함수 호출과정에서 블로킹 되지 않을 것을 요구하기 위한 옵션.
  • MSG_WAITALL: 요청한 바이트 수에 해당하는 데이터가 전부 수신될 때까지 호출된 함수가 반환되는 것을 막기 위한 옵션.

긴급 데이터 모드

소켓 옵션에 MSG_OOB를 추가하면 긴급 메세지를 전송할 수 있다. 긴급 메세지는 특별한 통신 채널을 쓰는 것이 아니라 수신자 측에서 긴급 메세지 비트 URG를 참조하여 해당 패킷에 대한 데이터를 특별하게 처리해달라는 의미로 사용한다.

 

- 서버

write(sock, "1234", 4, MSG_OOB);

송신자 코드에는 옵션에 MSG_OOB를 전달하면 된다.

 

- 클라이언트

긴급 데이터를 수신하는 수신자는 해당 소켓에서 SIGURG 시그널이 발생한다. 따라서 긴급 메세지를 처리하는 시그널 핸들링 함수를 등록해야한다. 게다가 소켓을 여러 프로세스에서 소유할 수 있기 때문에 소켓이 직접 시그널을 전달할 프로세스를 특정해야 올바른 시그널 핸들링이 된다.

fcntl(recv_sock, F_SETOWN, getpid());

위 함수는 recv_sock에서 발생하는 시그널 처리를 getpid(): 현재 실행중인 프로세스가 담당하도록 지정한다.

void urg_handler(int signo);

//...
struct sigaction act;
act.sa_handler = urg_handler;
sigemptyset(&act.sa_mak);
act.sa_flags=0;

//...
// 시그널 등록
fcntl(recv_sock, F_SETOWN, getpid());
int state = sigaction(SIGURG, &act, 0);

 

- urg_handler

SIGURG를 핸들링하는 함수 내부에서 MSG_OOB를 옵션으로 주어 recv 데이터를 수신하면 긴급 메세지의 마지막 1 바이트만 반환하게 된다.

"1234" 긴급 메세지 -> "4"를 반환

void urg_handler(int signo)
{
  int str_len;
  char buf[BUF_SIZE];
  str_len = recv(recv_sock, buf, sizeof(buf)-1, MSG_OOB);
  buf[str_len]=0; // "1234" 중에 "4"만 buf에 쓰여짐
  printf("Urgent message : %s \n, buf);
}

URG 패킷은 TCP 헤더에 URG 비트가 1로 설정되어 있고 메세지 길이가 URG_POINTER로 쓰여있다.

 

입력버퍼 검사

MSG_PEEK: recv 함수 호출시 입력버퍼의 데이터를 읽더라도 기존 버퍼를 지우지 않는 옵션이다.

MSG_DONWAIT: 입력 버퍼 데이터가 없다면 바로 반환하도록하여 블로킹되지 않도록 해준다.

 

 

readv와 writev

위 두 함수는 여러 메세지를 한꺼번에 전송하거나 수신하는 함수로 기존 read/write 함수를 여러번 호출하는 것을 절약한다.

ssize_t writev(int filedes, const struct iovec* iov, int iovcnt);
  • filedes: 출력 버퍼가 담긴 파일 디스크립터를 전달한다.
  • iov: 구조체 iovec의 배열의 주소를 전달한다. iovec에는 전송할 데이터 열과 길이가 담긴다.
  • iovcnt: 배열의 길이를 전달한다.
ssize_t readv(int filedes, const struct iovec* iov, int iovcnt);
  • filedes: 입력 버퍼가 담긴 파일 디스크립터를 전달한다.
  • iov: 구조체 iovec의 배열의 주소를 전달한다. iovec에는 전송할 데이터 열과 길이가 담긴다.
  • iovcnt: 배열의 길이를 전달한다.

- iovec 구조체

struct iovec
{
 void* iov_base; // 버퍼 주소
 size_t iov_len // 버퍼 크기
}

iovec 구조체 하나에 메세지 하나를 담을 수 있으며 내용과 길이를 설정한다.

iovec 구조체 배열을 한꺼번에 전송하기 때문에 writev를 사용하면 여러 메세지를 한꺼번에 일렬로 보낼 수 있다.