send & recv 입출력 함수
리눅스 기반에서 send & recv 함수를 소개하겠다.
이는 window에서의 함수와 똑같다.
send
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
// 성공 시 전송된 바이트 수, 실패 시 -1 반환
/*
sockfd : 데이터 전송 대상과의 연결을 의미하는 소켓의 파일 디스크립터 전달
buf : 전송할 데이터를 저장하고 있는 버퍼의 주소 값 전달
nbytes : 전송할 바이트 수 전달
flags : 데이터 전송 시 적용할 다양한 옵션 정보 전달
*/
recv
#include <sys/socket.h>
ssize_t recv(int sockfd, const void * buf, size_t nbytes, int flags);
//성공 시 수신한 바이트 수(단 EOF 전송 시 0), 실패 시 -1 반환
/*
sockfd : 데이터 수신 대상과의 연결을 의미하는 소켓의 파일 디스크립터 전달
buf : 수신된 데이터를 저장할 버퍼의 주소 값 전달
nbytes : 수신할 수 있는 최대 바이트 수 전달
flasg : 데이터 수신 시 적용할 다양한 옵션 정보 전달
*/
윈도우와 선언된 자료형의 이름만 다를 뿐 나머지는 완전히 동일하다.
send와 recv 함수의 마지막 매개변수에는 데이터 송수신시 적용할 옵션정보가 전달된다.
그런데 옵션정보는 비트 OR 연산자(| 연산자)를 이용해서 둘 이상을 함께 전달할 수 있다.
다음은 매개변수에 전달할 수 있는 옵션의 정보와 그 의미이다
옵션 | 의미 | 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'라 불리는 긴급 메시지의 전송에 사용된다.
간단하게 응급실을 생각해보자.
응급환자가 발생시에 기존에 대기하고 있는 환자들에게 양해를 구하고 먼저 진료를 봐야한다.
이러한 문제점 때문에 응급실이 별도로 존재한다.
readv & writev 입출력 함수
readv & writev 함수의 기능을 한 마디로 정리하자면 다음과 같다.
"데이터를 모아서 전송하고, 모아서 수신하는 기능의 함수"
즉, writev 함수를 사용하면 여러 버퍼에 나뉘어 있는 데이터를 한 번에 전송할 수 있고,
readv 함수를 사용하면 데이터를 여러 버퍼에 나눠서 수신할 수 있다.
이렇게 사용을 한다면 입출력 함수 호출의 수를 줄일 수 있다.
writev
#include <sys/uio.h>
ssize_t writev(int fildes, const struct iovec * iov, int iovcnt);
// 성공 시 전송된 바이트 수, 실패 시 -1 반환
/*
filedes : 데이터 전송의 목적지를 나타내는 소켓의 파일 디스크립터 전달.
단, 소켓에만 제한된 함수가 아니기 때문에, read함수처럼 파일이나 콘솔 대상의 파일 디스크립터도 전달
iov : 구조체 iovec 배열의 주소 값 전달, 구조체 iovec의 변수에는 전송할 데이터의 위치 및 크기 정보가 담긴다.
iovcnt : 두 번째 인자로 전달된 주소 값이 가리키는 배열의 길이 정보 전달
*/
다음과 같이 그림으로 쉽게 알아볼 수 있다.
위 그림에서 writev의 첫 번째 인자 1은 파일 디스크립터를 의미하므로 콘솔에 출력이 이뤄지고 ptr은 전송할 데이터 정보를 모아둔 iovec 배열을 가리키는 포인터이다. 또한 세 번째 인자가 2이기 때문에 ptr이 가리키는 주소를 시작으로 총 두 개의 iovec 변수를 참조하여 그 두 변수가 가리키는 버퍼에 저장된 데이터의 전송이 진행된다.
위그림의 iovec 구조체 배열을 자세히 관찰해보면 ptr[0]의 (배열 첫번 째 요소의) iov_base는 A로 시작하는 문자열을 가리키면서,
iov_len이 3이므로 ABC가 전송된다. 그리고 ptr[1]의 (배열 두번 째 요소의) iov_base는 숫자 1을 가리키며 iov_len이 4이므로 1234가 이어서 전송된다.
여기서 writev의 첫 번째 매개 변수 1이 콘솔에 출력이 이뤄진다는 것은
fd = 0 은 standard input이고
fd = 1 은 standard output 이기 때문이다.
writev 함수의 사용방법과 특성을 확인했으니 이제 예제를 통해 함수를 사용해 보겠다.
#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;
}
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);
writev 함수의 첫 번째 전달인자가 1이므로 콘솔로 출력이 이뤄진다.
readv
#include <sys/uio.h>
ssize_t readv(int fildes, const struct iovec * iov, int iovcnt);
// 성공 시 수신된 바이트 수, 실패 시 -1 반환
/*
filedes : 데이터를 수신할 파일(혹은 소켓)의 파일 디스크립터도 인자로 전달
iov : 데이터를 저장할 위치와 크기 정보를 담고 있는 iovec 구조체 배열의 주소 값 전달
iovcnt : 두 번째 인자로 전달된 주소 값이 가리키는 배열의 길이 정보 전달
*/
readv 함수는 writev 함수를 반대로 생각하면 되니 자세한 설명은 넘어가고 바로 코드를 작성해보자.
#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;
}
/*
swyoon@my_linux:~/tcpip$ gcc readv.c -o rv
swyoon@my_linux:~/tcpip$ ./rv
I like TCP/IP socket programming~
Read bytes: 34
First message: I lik
Second message: e TCP/IP socket programming~
*/
vec[0].iov_base=buf1;
vec[0].iov_len=5;
첫 번째 데이터 저장소의 위치와 저장할 데이터의 크기정보를 설정하고 있다. 특히 저장할 데이터의 크기를 5로 지정했기 때문에
buf1의 크기에 상관없이 최대 5바이트만이 영역에 저장된다.
vec[1].iov_base=buf2;
vec[1].iov_len=BUF_SIZE;
vec[0]에 등록되어있는 버퍼에 5바이트가 저장되고, 나머지 데이터는 vec[1]에 등록되어있는 버퍼에 저장된다.
특히 구조체 iovec의 멤버 iov_len에는 버퍼에 저장할 최대 바이트 크기 정보를 저장해야한다.
str_len=readv(0, vec, 2);
readv 함수의 첫 번째 전달인자가 0이기 때문에 콘솔로부터 데이터를 수신한다.
readv & writev 함수의 적절한 사용
그렇다면 어떤 상황이 readv와 writev 함수를 사용하기에 적절한 것일까 ?
사용할 수 있는 모든 경우가 적절한 상황이다.
예를 들어 전송해야 할 데이터가 여러 개의 버퍼(배열)에 나눠어 있는 경우, 모든 데이터의 전송을 위해서는 여러 번의 write 함수를 호출해야 한다. 하지만 이를 한 번의 write 함수 호출로 대신할 수 있다면 당연히 효율적이지 않은가 ?
마찬가지로 readv 함수 또한 여러 번 read 함수를 호출하는 것보다 한 번 호출하는 것이 더 효율적이다.
C언어 차원으로 생각해봐도 함수의 호출 횟수가 적으면, 그만큼 성능이 향상된다는 것을 알 수 있다.
그러나 일반적으로 전송되는 패킷의 수를 줄일 수 있다는 데에 더 큰 의미가 있다.
전송해야 할 데이터가 세 개의 공간으로 나뉘어 저장된 상황에서 데이터 전송을 예로 들고 있다.
이 상황에서 write 함수를 사용한다면 3번의 호출이 필요할 것이다.
반면 writev 함수를 사용한다면 한 번에 모든 데이터를 출력 버퍼로 밀어 넣고 하나의 패킷만 생성되어서 전송될 확률이 높다.
이는 전송속도의 향상으로 이어질 수 있다.
사진의 오른쪽 부분을 보면 알겠지만 하나의 큰 배열에 3개의 데이터를 모두 옮겨놓고 write 함수를 사용하는 것과 writev 함수를 호출하는 것과 결과는 같다.
하지만 어떤 것이 더 편할지는 누가봐도 알 것이라고 생각한다.
writev 함수와 readv 함수를 사용할 상황이 나오면 적극적으로 사용해야한다.
'스터디 > TCP 와 IP' 카테고리의 다른 글
11. 입출력 스트림의 분리에 대한 나머지 이야기 (0) | 2024.09.04 |
---|---|
10. 소켓과 표준 입출력 (0) | 2024.09.04 |
8. 멀티프로세스 기반의 서버구현 (0) | 2024.09.03 |
7. 도메인 이름과 인터넷 주소 (0) | 2024.09.03 |
6. 소켓의 우아한 연결 종료 (0) | 2024.09.03 |