다양한 입출력 함수

send & recv 입출력 함수

윈도우 기반이 아닌 리눅스의 send & recv 함수는 다음과 같다.

#include <sys/socket.h>

ssize_t send(int sockfd, const void * buf, size_t nbytes, int flags);
// 성공 시 전송된 바이트 수, 실패 시 -1 반환 
  • sockfd: 데이터 전송 대상과의 연결을 의미하는 소켓의 파일 디스크립터 전달
  • buf: 전송할 데이터를 저장하고 있는 버퍼의 주소 값 전달
  • nbytes: 전송할 바이트 수 전달
  • flags: 데이터 전송 시 적용할 다양한 옵션 정보 전달
#include <sys/socket.h>

ssize_t recv(int sockfd, void * buf, size_t nbytes, int flags);
// 성공 시 수신한 바이트 수(단 EOF 전송 시 0), 실패 시 -1 반환
  • sockfd: 데이터 수신 대상과의 연결을 의미하는 소켓의 파일 디스크립터 전달
  • buf: 수신된 데이터를 저장할 버퍼의 주소값 전달
  • nbytes: 수신할 수 있는 최대 바이트 수 전달
  • flags: 데이터 수신 시 적용할 다양한 옵션 정보 전달

flags 매개변수에 전달 가능한 옵션 목록

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

MSG_OOB: 긴급 메세지의 전송

옵션 MSG_OOB는 ‘Out-of-band data’라 블리는 긴급 메시지의 전송에 사용된다.

옵션 MSG_OOB는 긴급으로 전송해야 할 메시지가 있어서 메시지의 전송방법 및 경로를 달리하고자 할 때 사용된다.

send의 경우

send(sock, "message", strlen("message"), MSG_OOB);

send의 경우는 간단하다. 플래그 옵션만 추가해주면 되기 때문이다. recv의 경우는 더 복잡하다.

recv의 경우

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;
    printf("Urgent message: %s \n", buf);
}
// sigaction 선언 생략
// . . . .
act.sa_handler = urg_handler;
fcntl(recv_sock, F_SETOWN, getpid());   // fd의 소유자를 현재 프로세스로 변경
state = sigaction(SIGURG, &act, 0);     // URG메세지 처리 시그널 등록
// 일반 메세지 처리는 아래에서 한다.

urg_handler를 통해 읽히는 데이터는 1바이트이다. MSG_OOB의 의의는 패킷에 URG 헤더옵션을 선언한다는데 있다. 오프셋 - 1 의 위치에 있는 바이트를 제외한 다른 데이터들은 평범하게 일반 메세지 처리 부분에서 처리된다.

MSG_PEEK, MSG_DONTWAIT: 입력버퍼 검사하기

MSG_PEEK옵션은 MSG_DONTWAIT옵션과 함께 설정되어 입력버퍼에 수신된 데이터가 존재하는지 확인하는 용도로 사용된다.

while(1)
{
  // MSG_PEEK 옵션은 데이터를 버퍼에서 제거하지 않고 확인만 하도록 합니다.
  // MSG_DONTWAIT 옵션은 블로킹되지 않고 즉시 반환되도록 합니다.
  int str_len = recv(recv_sock, buf, sizeof(buf)-1, MSG_PEEK | MSG_DONTWAIT);

  // 수신된 데이터의 길이가 양수인 경우, 데이터가 도착한 것이므로 루프를 종료합니다.
  if(str_len > 0) break;
}

readv, writev 입출력 함수

“데이터를 모아서 전송하고, 모아서 수신하는 기능의 함수”

#include <sys/uio.h>

ssize_t writev(int fileds, const struct iovec * iov, int iovcnt);
// 성공 시 전송된 바이트 수, 실패 시 -1 반환
  • filedes: 데이터 전송의 목적지를 나타내는 소켓의 파일 디스크립터 전달, 단 소켓에만 제한된 함수가 아니기 때문에, read 함수처럼 파일이나 콘솔 대상의 파일 디스크립터도 전달 가능하다.
  • iov: 구조체 iovec 배열의 주소 값 전달, 구조체 iovec의 변수에는 전송할 데이터의 위치 및 크기 정보가 담긴다.
  • iovcnt: 두 번째 인자로 전달된 주소 값이 가리키는 배열의 길이 정보 전달

그리고 위 함수의 두 번째 인자로 전달되는 배열의 구조체 iovec은 다음과 같이 정의되어 있다.

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

이렇듯 구조체 iovec은 전송할 데이터가 저장되어 있는 버퍼(char형 배열)의 주소 값과 실제 전송할 데이터의 크기 정보를 담기 위해 정의되었다.

예제 코드

#include <stdio.h>
#include <sys/uio.h>

int main(int argc, char *argv[])
{
    struct iovec vec[2];
    char buf1[] = "ABCDEFG";
    char buf2[] = "1234567";
    int str_len;
    
    vec[0].iov_base = buf1;
    vec[0].iov_len = 3;
    vec[1].iov_base = buf2;
    vec[1].iov_len = 4;
    
    str_len = writev(1, vec, 2);
    puts("");
    printf("Write bytes: %d \n", str_len);
    return 0;
}

실행 결과

ABC1234
#include <sys/uio.h>

ssize_t readv(int filedes, const struct iovec * iov, int iovcnt);
// 성공 시 수신된 바이트 수, 실패 시 -1 반환
  • filedes: 데이터를 수신할 파일(혹은 소켓)의 파일 디스크립터를 인자로 전달
  • iov: 데이터를 저장할 위치와 크기 정보를 담고 있는 iovec 구조체 배열의 주소 값 전달
  • iovcnt: 두 번째 인자로 전달된 주소 값이 가리키는 배열의 길이정보 전달

예제 코드

#include <stdio.h>
#include <sys/uio.h>
#define BUF_SIZE 100
int main(int argc, char *argv[])
{
    struct iovec vec[2];
    char buf1[BUF_SIZE] = {0, };
    char buf2[BUF_SIZE] = {0, };
    int str_len;
    
    vec[0].iov_base = buf1;
    vec[0].iov_len = 5;
    vec[1].iov_base = buf2;
    vec[1].iov_len = BUF_SIZE;
    
    str_len = readv(0, vec, 2);
    printf("Read bytes: %d \n", str_len);
    printf("First message: %s \n", buf1);
    printf("Second message: %s \n", buf2);
    return 0;
}

실행 결과

I like TCP/IP programming~
Read bytes: 34
First message: I lik
Second message: e TCP/IP programming~

⭐ readv, writev는 nagle알고리즘을 중지시킨 상황에서 더욱 효율적으로 작동한다.

윈도우 기반으로 구현하기

윈도우에는 시그널 핸들링이 존재하지 않기 때문에 select를 이용해서 MSG_OOB를 처리해야 한다. Out-of-band data의 경우는 예외상황에 해당되기 때문에, selectfd_set * exceptfds를 통해 URG메세지의 도착 여부를 확인할 수 있다.

// 생략
result = select(0, &readCopy, 0, &exceptCopy, &timeout); // exceptfds 지정
// . . . .
    if(FD_ISSET(hRecvSock, &exceptCopy))
    {
        strLen = recv(hRecvSock, buf, BUS_SIZE-1, MSG_OOB);
        buf[strLen] = 0;
        printf("Urgent message: %s \n", buf);
    }
// . . . .

writev & reacv에 직접 대응하는 함수가 윈도우에는 정의되어 있지 않다. 그러나 윈도우에서 제공하는 ‘중첩 입출력(Overlapped IO)‘을 이용하면 이와 동일한 효과를 얻을 수 있다. 이에 관한 설명은 추후에 한다.