Chapter 13 - 다양한 입출력 함수들
Chapter 13 - 다양한 입출력 함수들
13-1 : send & recv 입출력 함수
리눅스에도 send()
, recv()
함수가 존재함
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
// 성공시 전송된 바이트 수, 실패시 -1 반환
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
// 성공시 수신한 바이트 수(단, EOF 전송시 0), 실패시 -1 반환
윈도우의 send()
, recv()
함수와 자료형 이름 외에는 차이가 없음
send()
, recv()
함수 모두 마지막 매개변수로 데이터 송수신시 적용할 옵션정보가 전달됨
옵션정보는 비트로 사용되기 때문에 비트 OR 연산자를 이용해 둘 이상을 함께 전달할 수 있음
MSG_OOB
: 긴급 데이터(Out-of-bnad data)의 전송을 위한 옵션MSG_PEEK
: 입력버퍼에 수신된 데이터의 존재유무 확인을 위한 옵션MSG_DONTROUTE
: 데이터 전송과정에서 라우팅(Routing) 테이블을 참조하지 않을 것을 요구하는 옵션 - 따라서 로컬(Local) 네트워크상에서 목적지를 찾을 때 사용되는 옵션MSG_DONTWAIT
: 입출력 함수 호출과정에서 블로킹 되지 않을 것을 요구하기 위한 옵션 - 즉, 논블로킹(Non-blocking) IO 요구에 사용되는 옵션MSG_WAITALL
: 요청한 바이트 수에 해당하는 데이터가 전부 수신될 때까지, 호출된 함수가 반환되는 것을 막기 위한 옵션
MSG_OOB
옵션 사용한 데이터 송신 예시
// oob_send.cpp
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;
#define BUF_SIZE 30
void error_handling(char *buf);
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in recv_adr;
if(argc != 3)
{
cout << "Usage : " << argv[0] << " <IP> <port>\n";
exit(1);
}
sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&recv_adr, 0, sizeof(recv_adr));
recv_adr.sin_family = AF_INET;
recv_adr.sin_addr.s_addr = inet_addr(argv[1]);
recv_adr.sin_port = htons(atoi(argv[2]));
if (connect(sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr)) == -1)
error_handling("connect() error!");
write(sock, "123", strlen("123"));
send(sock, "4", strlen("4"), MSG_OOB);
write(sock, "567", strlen("567"));
send(sock, "890", strlen("890"), MSG_OOB);
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
MSG_OOB
옵션 사용한 데이터 수신 예시
// oob_recv.cpp
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
using namespace std;
#define BUF_SIZE 30
void error_handling(char *message);
void urg_handler(int signo);
int acpt_sock;
int recv_sock;
int main(int argc, char *argv[])
{
int str_len, state;
struct sockaddr_in recv_adr, serv_adr;
socklen_t serv_adr_sz;
struct sigaction act;
char buf[BUF_SIZE];
if(argc != 2)
{
cout << "Usage : " << argv[0] << " <port>\n";
exit(1);
}
act.sa_handler = urg_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
acpt_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&recv_adr, 0, sizeof(recv_adr));
recv_adr.sin_family = AF_INET;
recv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
recv_adr.sin_port = htons(atoi(argv[1]));
if (::bind(acpt_sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr)) == -1)
error_handling("bind() error");
::listen(acpt_sock, 5);
serv_adr_sz = sizeof(serv_adr);
recv_sock = accept(acpt_sock, (struct sockaddr*)&serv_adr, &serv_adr_sz);
fcntl(recv_sock, F_SETOWN, getpid());
state = sigaction(SIGURG, &act, 0);
while ((str_len = recv(recv_sock, buf, sizeof(buf), 0)) != 0)
{
if (str_len == -1)
continue;
buf[str_len] = 0;
cout << buf << endl;
}
close(recv_sock);
close(acpt_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
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;
cout << "Urgent message : " << buf << endl;
}
fcntl()
함수를 통해 파일 디스크립터 recv_sock
이 가리키는 소켓에 의해 발생하는 시그널을 처리하는 프로세스를 getpid()
함수가 반환하는 ID의 프로세스로 변경
이를 통해 현재 프로세스를 시그널의 처리 주체로 지정함
MSG_OOB
옵션으로 전송되는 것은 1바이트 뿐
이는 TCP에 Out-of-band
데이터, 즉 전혀 다른 통신 경로로 전송되는 데이터가 실질적으로 존재하지 않기 때문임
따라서 MSG_OOB
옵션은 수신 대상에게 데이터 처리를 독촉하는데에 의미가 있음
MSG_OOB
옵션 설정시 데이터의 출력버퍼에서 마지막 데이터 다음(오프셋 1 증가) 위치인 Urgent Pointer를 가리키면서 해당 정보를 상대 호스트에게 전달
결국 수신하는 상대방은 Urgent Pointer 앞 부분의 1바이트를 제외한 나머지는 일반적인 입력함수의 호출을 통해서 읽게됨
MSG_PEEK
옵션을 사용한 데이터 송신 예시
// peek_send.cpp
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in recv_adr;
if(argc != 3)
{
cout << "Usage : " << argv[0] << " <IP> <port>\n";
exit(1);
}
sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&recv_adr, 0, sizeof(recv_adr));
recv_adr.sin_family = AF_INET;
recv_adr.sin_addr.s_addr = inet_addr(argv[1]);
recv_adr.sin_port = htons(atoi(argv[2]));
if (connect(sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr)) == -1)
error_handling("connect() error!");
write(sock, "123", strlen("123"));
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
MSG_PEEK
옵션을 사용한 데이터 수신 예시
// peek_recv.cpp
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
int acpt_sock, recv_sock;
int str_len, state;
struct sockaddr_in acpt_adr, recv_adr;
socklen_t recv_adr_sz;
char buf[BUF_SIZE];
if(argc != 2)
{
cout << "Usage : " << argv[0] << " <port>\n";
exit(1);
}
acpt_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&acpt_adr, 0, sizeof(acpt_adr));
acpt_adr.sin_family = AF_INET;
acpt_adr.sin_addr.s_addr = htonl(INADDR_ANY);
acpt_adr.sin_port = htons(atoi(argv[1]));
if (::bind(acpt_sock, (struct sockaddr*)&acpt_adr, sizeof(acpt_adr)) == -1)
error_handling("bind() error");
::listen(acpt_sock, 5);
recv_adr_sz = sizeof(recv_adr);
recv_sock = accept(acpt_sock, (struct sockaddr*)&recv_adr, &recv_adr_sz);
while (1)
{
str_len = recv(recv_sock, buf, sizeof(buf) - 1, MSG_PEEK | MSG_DONTWAIT);
if (str_len > 0)
break;
}
buf[str_len] = 0;
cout << "Buffering " << str_len << " bytes : " << buf << endl;
str_len = recv(recv_sock, buf, sizeof(buf) - 1, 0);
buf[str_len] = 0;
cout << "Read again : " << buf << endl;
close(acpt_sock);
close(recv_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
MSG_PEEK
옵션 사용시 데이터가 읽혀지더라도 입력버퍼에서 지워지지 않음
따라서 MSG_DONTWAIT
옵션과 함께 사용하여 데이터의 존재유무를 확인하기 위한 블로킹 되지 않는 함수의 호출 구성에 사용
13-2 : readv & writev 입출력 함수
readv()
, writev()
함수는 데이터를 모아서 송수신하는 기능을 함
즉, 여러 버퍼에 나뉘어져 있는 데이터를 한번에 전송하거나 데이터를 여러 버퍼에 나눠서 수신할 수 있기 때문에 입출력 함수호출의 수를 줄일 수 있음
#include <sys/uio.h>
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
// 성공시 전송된 바이트 수, 실패시 -1 반환
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
struct iovec
{
void *iov_base; // 버퍼의 주소 정보
size_t iov_len; // 버퍼의 크기 정보
}
구조체 iovec
는 전송할 데이터가 저장되어있는 버퍼의 주소값 및 크기 정보를 정의
writev()
함수를 사용한 예시
// writev.cpp
#include <iostream>
#include <sys/uio.h>
using namespace std;
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);
cout << " " << endl;
cout << "Write bytes : " << str_len << endl;
return 0;
}
readv()
함수를 사용한 예시
// readv.cpp
#include <iostream>
#include <sys/uio.h>
using namespace std;
#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);
cout << "Read bytes : " << str_len << endl;
cout << "First message : " << buf1 << endl;
cout << "Second message : " << buf2 << endl;
return 0;
}
전송해야 할 데이터가 여러 개의 버퍼에 나뉘어져있거나, 수신된 데이터를 여러 저장소에 나눠서 읽어들이고 싶은 경우 경우 효율적
특히 전송되는 패킷의 수가 줄어들기 때문에 Nagle 알고리즘이 중지된 상황에서 활용하기가 더 좋음
13-3 : 윈도우 기반으로 구현하기
윈도우 기반에서 MSG_OOB
옵션을 사용한 예시
윈도우에는 시그널 핸들링이 존재하지 않기 때문에 select()
함수를 사용하여 구현
// oob_send_win.cpp
#include <iostream>
#include <winsock2.h>
using namespace std;
#define BUF_SIZE 30
void ErrorHandling(char* message);
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN sendAdr;
if (argc != 3)
{
cout << "Usage : " << argv[0] << " <IP> <port>\n";
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error");
hSocket = socket(PF_INET, SOCK_STREAM, 0);
memset(&sendAdr, 0, sizeof(sendAdr));
sendAdr.sin_family = AF_INET;
sendAdr.sin_addr.s_addr = inet_addr(argv[1]);
sendAdr.sin_port = htons(atoi(argv[2]));
if (connect(hSocket, (SOCKADDR*)&sendAdr, sizeof(sendAdr)) == SOCKET_ERROR)
ErrorHandling("connect() error!");
send(hSocket, "123", 3, 0);
send(hSocket, "4", 1, MSG_OOB);
send(hSocket, "567", 3, 0);
send(hSocket, "890", 3, MSG_OOB);
closesocket(hSocket);
WSACleanup();
return 0;
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
// oob_send_win.cpp
#include <iostream>
#include <winsock2.h>
using namespace std;
#define BUF_SIZE 30
void ErrorHandling(char* message);
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hAcptSock, hRecvSock;
SOCKADDR_IN sendAdr, recvAdr;
int sendAdrSize, strLen;
char buf[BUF_SIZE];
int result;
fd_set read, except, readCopy, exceptCopy;
struct timeval timeout;
if (argc != 2)
{
cout << "Usage : " << argv[0] << " <port>\n";
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error");
hAcptSock = socket(PF_INET, SOCK_STREAM, 0);
memset(&recvAdr, 0, sizeof(recvAdr));
recvAdr.sin_family = AF_INET;
recvAdr.sin_addr.s_addr = htonl(INADDR_ANY);
recvAdr.sin_port = htons(atoi(argv[1]));
if (::bind(hAcptSock, (SOCKADDR*)&recvAdr, sizeof(recvAdr)) == SOCKET_ERROR)
ErrorHandling("bind() error!");
if (::listen(hAcptSock, 5) == SOCKET_ERROR)
ErrorHandling("listen() error");
sendAdrSize = sizeof(sendAdr);
hRecvSock = accept(hAcptSock, (SOCKADDR*)&sendAdr, &sendAdrSize);
FD_ZERO(&read);
FD_ZERO(&except);
FD_SET(hRecvSock, &read);
FD_SET(hRecvSock, &except);
while (1)
{
readCopy = read;
exceptCopy = except;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
result = select(0, &readCopy, 0, &exceptCopy, &timeout);
if (result > 0)
{
if (FD_ISSET(hRecvSock, &exceptCopy))
{
strLen = recv(hRecvSock, buf, BUF_SIZE - 1, MSG_OOB);
buf[strLen] = 0;
cout << "Urgent message : " << buf << endl;
}
if (FD_ISSET(hRecvSock, &readCopy))
{
strLen = recv(hRecvSock, buf, BUF_SIZE - 1, 0);
if (strLen == 0)
{
break;
closesocket(hRecvSock);
}
else
{
buf[strLen] = 0;
cout << buf << endl;
}
}
}
}
closesocket(hSocket);
WSACleanup();
return 0;
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
writev()
와 readv()
함수는 윈도우에 정의되어있지 않으나, 중첩 입출력(Overlapped IO)를 이용하면 동일한 효과를 얻을 수 있음
내용 확인문제
- 다음 중, 데이터 전송옵션인
MSB_OOB
에 대해서 잘못 설명한 것을 모두 고르면?MSG_OOB
는 Out-of-band 데이터의 전송을 의미한다. 그리고 이는 다른 경로를 통한 고속의 데이터 전송이라는 의미를 갖는다. (O)MSG_OOB
는 다른 경로를 통한 고속의 데이터 전송이라는 의미를 갖기 때문에, TCP상에서도 이 옵션을 이용해서 전송된 데이터는 상대 호스트로 먼저 전송된다. (X)MSG_OOB
옵션이 주어진 상태에서 상대 호스트로 데이터가 먼저 전송된 이후에는 일반 데이터와 동일한 형태와 순서로 읽혀진다. 즉, 전송이 빠를 뿐, 수신 측에서는 이를 인지하지 못한다. (X)MSG_OOB
는 TCP의 기본 데이터 전송방식을 벗어나지 못한다. 즉, MSG_OOB 옵션이 지정되더라도 전송순서는 그대로 유지된다. 다만 이는 수신 측에 데이터 처리의 긴급을 요청하는 용도로 사용될 뿐이다. (O)
-
readv
&writev
함수를 이용해서 데이터를 송수신 할 경우 어떠한 이점이 있는지 함수 호출의 횟수와 입출력 버퍼의 관점에서 각각 설명해보자.입력 버퍼를 한번에 읽어들이고 출력 버퍼들에 한번에 쓰기 때문에 함수 호출의 횟수가 줄어듬
-
recv
함수호출을 통해서 입력버퍼의 데이터 존재유무를 확인하고자 할 때(확인 후 바로 반환하고자 할 때),recv
함수의 마지막 전달인자인 데이터 전송의 옵션을 어떻게 구성해야 하는가? 그리고 각각의 옵션이 의미하는 바는 무엇인지도 설명해 보자.MSG_PEEK
옵션으로 입력버퍼의 데이터를 읽어들이는데, 이 때MSG_DONTWAIT
옵션도 함께 사용하여 입력버퍼가 비어있더라도 블로킹이 되지 않도록 구성 -
리눅스에서는
MSG_OOB
데이터의 수신을 이벤트 핸들러의 등록을 통해서 확인이 가능하다. 그렇다면 윈도우에서는 어떻게 MSB_OOB 데이터의 수신을 확인할 수 있는지, 그 방법을 설명해보자.윈도우에서는
select()
함수에exceptset
을 등록하여 확인