모든 내용은 [윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어] 를 기반으로 제 나름대로 이해하여 정리한 것입니다. 다소 부정확한 내용이 있을수도 있으니 이를 유념하고 봐주세요!
# 리눅스에서의 send / recv
- 윈도우 기반에서 소개한 함수이나, 사실상 차이가 나지 않음
- 선언된 자료형의 이름에서만 조금 차이가 나지만 매개변수의 순서, 의미, 그리고 사용방법까지 완전히 동일
#include <sys/socket.h>
ssize_t send(int sockfd, const void* buf,size_t nbytes, inf flags);
// -> 성공시 전송된 바이트수, 실패시 -1 반환
- sockfd : 데이터 전송 대상과의 연결을 의미하는 소켓의 파일 디스크립터 전달
- buf : 전송할 데이터를 저장하고 있는 버퍼의 주소값 전달
- nbytes : 전송할 바이트 수 전달
- flags : 데이터 전송시 적용할 다양한 옵션정보 전달
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
// -> 성공시 수신한 바이트수(단 EOF 전송시 0), 실패시 -1 반환
- sockfd : 데이터 수신 대상과의 연결을 의미하는 소켓의 파일 디스크립터 전달
- buf : 수신된 데이터를 저장할 버퍼의 주소값 전달
- nbytes : 수신할 수 있는 최대 바이트수 전달
- flags : 데이터 수신시 적용할 다양한 옵션 정보 전달
# 데이터 송수신시 적용할 옵션정보(flags) : 비트 OR 연산자(| 연산자)를 이용해서 둘 이상을 함께 전달 가능
옵션(Opiton) | 의 미 | send | recv |
MSG_OOB | 긴급 데이터(Out-ofband data)의 전송을 위한 옵션 | O | O |
MSG_PEEK | 입력버퍼에 수신된 데이터의 존재유무 확인을 위한 옵션 | O | |
MSG_DONTROUTE | 데이터 전송과정에서 라우팅(Routing) 테이블을 참조하지 않을 것을 요구하는 옵션, 따라서 로컬(Local) 네트워크상에서 목적지를 찾을 때 사용되는 옵션 | O | |
MSG_DONTWAIT | 입출력 함수 호출과정에서 블로킹 되지 않을 것을 요구하기 위한 옵션. 즉, 넌-블로킹(Non-blocking) IO의 요구에 사용되는 옵션 | O | O |
MSG_WAITALL | 요청한 바이트 수에 해당하는 데이터가 전부 수신될 때까지, 호출된 함수가 반환되는 것을 막기위한 옵션 | O |
- 옵션의 지원여부는 운영체제마다 조금식 차이가 날 수 있음
1) MSG_OOB : 긴급 데이터(Out-of-band data)의 전송을 위한 옵션
- 긴급으로 전송해야 할 메시지가 있어 전송방법 및 경로를 달리하고자 할 때 사용됨
- TCP는 별도의 통신경로를 제공하지 않으므로, MSG_OOB 옵션을 추가해도
단지 TCP에 존재하는 urgent mode라는 것을 이용해서 데이터를 전송해줄뿐,
더 빨리 데이터가 전송되는 것도 아니며, 시그널 핸들러를 통해서 읽히는 데이터도 1바이트 밖에 되지 않음
- MSG_OOB는 데이터를 수신하는 대상에게 데이터의 처리를 독촉하는 것에 의미가 있는 것임
- TCP의 긴급메시지는 빠른 이동을 보장하지는 않지만, 빠른 응급조치를 요구한다
- MSG_OOB 옵션이 지정되면 패킷 자체가 긴급패킷이 됨
(TCP 헤더 중 일부를 살펴보면 URG = 1(긴급메시지가 존재하는 패킷임을 의미),
UGR Pointer(이것이 가리키는 오프셋의 바로 앞에 존재하는 것이 긴급메시지임을 의미))
# 오프셋(offset) : 기본이 되는 위치를 바탕으로 상대적 위치를 표현하는 것
2) MSG_PEEK : 입력버퍼에 수신된 데이터의 존재 유무 확인을 위한 옵션
- 아무런 옵션을 주지 않고 recv 함수 호출시, 입력버퍼에서 데이터는 지워짐
- MSG_PEEK 옵션을 주고 recv 함수 호출시, 입력버퍼에 데이터가 읽히더라도 입력버퍼에 데이터가 지워지지 않음
- MSG_DONTWAIT 옵션과 함께 설정되어 입력버퍼에 수신된 데이터가 존재하는지 확인하는 용도로 사용됨
- 따라서 MSG_DONTWAIT 옵션(입력버퍼에 없더라도 데이터가 없어도 블로킹에 빠지지 않음)과 묶여서
블로킹 되지 않는, 데이터의 존재유무를 확인하기 위한 함수 호출 구성에 사용됨
- MSG_PEEK | MSG_DONTWAIT => 데이터가 존재하지 않아도 블로킹 상태에 빠지지 않음
# readv & writev 입출력함수
- 데이터 송수신의 효율성을 향상시키는데 도움이 되는 함수
- 데이터를 모아서 전송하고, 모아서 수신하는 기능의 함수 => 입출력 함수호출의 수를 줄일 수 있음
#include <sys/uio.h>
ssize_t writev(int filedes, const struct iovec * iov, int iovcnt);
// -> 성공시 전송된 바이트수, 실패시 -1 반환
- filedes : 데이터 전송의 목적지를 나타내는 소켓의 파일 디스크립터 전달
단, 소켓에 제한된 것이 아니기 때문에, 파일이나 콘솔 대상의 파일 디스크립터도 전달 가능
- iov : 구조체 iovec 배열의 주소값, 구조체 iovec에는 전송할 데이터의 위치 및 크기 정보가 담김
- iovcnt 두번째 인자로 전달된 주소값이 가리키는 배열의 길이 정보
struct iovec
{
void * iov_base; // 버퍼의 주소 정보
size_t iov_len; // 버퍼의 크기 정보
}
# writev.c
#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; // 길이는 3만큼
vec[1].iov_base=buf2;
vec[1].iov_len=4; // 길이는 4만큼
str_len=writev(1, vec, 2); // 표준출력(콘솔)에 writev로 데이터 전송
puts("");
printf("Write bytes: %d \n",str_len);
return 0;
}
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec * iov, int iovcnt);
// -> 성공시 수신된 바이트수, 실패시 -1 반환
- filedes : 데이터를 수신할 파일(혹은 소켓)의 파일 디스크립터
- iov : 데이터를 저장할 위치와 크기 정보를 담고 있는 iovec 구조체 배열의 주소값
- iovcnt : 두번째 인자로 전달된 주소값이 가리키는 배열의 길이 정보
# readv.c
#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; // 길이 5만큼
vec[1].iov_base=buf2;
vec[1].iov_len=BUF_SIZE; // 길이 BUF_SIZE만큼
str_len=readv(0, vec, 2); // 표준입력으로부터 readv로 데이터 수신
printf("Read bytes: %d \n",str_len);
printf("First message: %s \n",buf1);
printf("First message: %s \n",buf2);
return 0;
}
# readv & writev 함수의 적절한 사용
- 전송해야 할 데이터가 여러개의 버퍼(배열)에 나뉘어 있는 경우,
모든 데이터의 전송을 위해서는 여러 번의 write 함수호출이 요구되는데,
이를 딱 한번의 writev 함수호출도 대신할 수 있으니 효율적
- 입력버퍼에 수신된 데이터를 여러 저장소에 나눠서 읽어 들이고 싶은 경우에도
여러번의 read 함수가 아니라 한번의 readv 함수를 호출하는 것이 보다 효율적
- C언어 차원에서 함수의 호출횟수가 적으면 그만큼 성능 향상
+ 전송되는 패킷의 수를 줄일 수 있다는데 더 큰 의미가 있음
- writev 함수는 (성능 향상을 위해 명시적으로) Nagle 알고리즘이 중지된 상황에서 더 활용의 가치가 높음
- 여러 배열에 나눠져 있는 데이터를 전송순서에 맞춰 큰 하나의 배열에 옮겨놓고(복사해놓고)
한 번의 write함수를 호출하는 것과 writev는 동일한 결과를 얻을 수 있으나,
보다 편리하므로 사용할 수 있을 때 writev, readv를 적극 사용하자.
# 윈도우에는 시그널 핸들링이 존재하지 않음.
따라서, 이러한 옵션(예외상황)과 관련하여 send, recv 함수로 데이터 송수신을 하고자 한다면,
select 함수를 활용하여 구현할 수 있다. - select 함수의 이벤트 3("예외상황이 발생한 소켓")
윈도우의 select 함수의 형태 및 매개변수, 반환형은 리눅스와 완전 동일하나
첫번째 매개변수(관심있는 파일 디스크립터의 수)는 리눅스와의 호환성을 위한 것일뿐 의미가 없으므로 0을 전달
# 윈도우에는 writev & readv 함수에 직접 대응하는 함수가 없으나,
윈도우에서 제공하는 중첩 입출력(overlapped IO)를 이용하면 동일한 효과를 얻을 수 있음.
[출처] : 윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어
'Programming > 열혈 TCP, IP 소켓 프로그래밍(저자 윤성우)' 카테고리의 다른 글
Ch 14. 멀티캐스트 & 브로드캐스트 (3) | 2020.08.09 |
---|---|
Ch 13. 내용 확인문제 (0) | 2020.08.09 |
Ch 12. 내용 확인문제 (0) | 2020.08.09 |
Ch 12. IO 멀티플렉싱 (0) | 2020.08.09 |
Ch 11. 내용 확인문제 (0) | 2020.08.09 |