Windows에서 되는지 실행 안 해봤고, 단순히 UNIX Network Programming(이하 UNP)에 나온 걸 정리해보겠다. MSDN에는 WSASendMsg라는 녀석을 준비하였는데 대충 비슷하게 보인다. 다만 Overlapped I/O를 Windows용으로 써야하기때문에 API가 다른 것 같다.
socket에는 sendmsg/recvmsg라는 녀석이 있다. 이 녀석 형태를 보면 아래와 같다.
send/recv와 달리 버퍼가 안 보이고, msghdr 라는 구조체를 쓰는데 - 물론 이 녀석이 버퍼겠지 - 이걸 우선 까보자.
sendmsg/recvmsg는 접속/비접속형 소켓을 모두 지원한다. msg_name과 msg_namelen은 비접속형 소켓(예:UDP)에서 받을 주소 구조체(예:struct sockaddr_in)을 알맞게 만들어서 넣어주는 것이다.
또한 I/O vector를 지원하여, msg_iov에는 struct iovec 배열의 주소, msg_iovlen은 배열 원소 개수를 예쁘게 넣는다. 이때 msg_iov는 NULL이 아니며, msg_iovlen은 0이 될 수 없다. 무조건 뭔가라도 하나 보내야한다. 만약 FD외에도 다른 데이터를 함께 넘기고 싶다면 이것을 애용(?)하면 좋겠다.
자, 드디어 FD에 대한 중요한 내용. 제어 정보 - 즉, FD 같은 걸 넘기는 방법이다. 이것을 위해 준비된 구조체가 있다. 바로 cmsghdr라는 구조체인데, 역시나 한 번 까보자.
참고로 FD를 넘기려면 Protocol은 UNIX domain, level은 SOL_SOCKET, type은 SCM_RIGHTS로 설정하라고 위대하신 UNP님께서 말씀하신다. (지랄)
자, 이제 예제 소스이다. _-_
결과
좀 길긴 하지만... -_- 대충 컴파일하고 실행하면, 부모의 표준 출력을 자식에게 잘 넘겨서 "hello, world!"만을 잘 찍는 것을 확인할 수 있다.
참고로 sendmsg로 fd를 전송하고, recvmsg로 fd를 받기 전에 fd를 close하더라도 sendmsg 시점에 fd에 대한 reference count를 증가하기 때문에, 받기도 전에 닫히는 불상사는 없을 것이다.
뱀발: 간만에 기술 관련 장문을 올리려니 삭신이 다 쑤신다. 쿨럭쿨럭.
뱀발2: 저~ 위에선 sendFD의 buf에 원래 fd를 넣었는데, NULL을 넣으면 데이터가 없거나 끊긴 것을 판단하지 못해서인지 recvFD에서 블럭된다. 귀찮더라도 1바이트라도 예쁘게 넣어서 보내주자.
socket에는 sendmsg/recvmsg라는 녀석이 있다. 이 녀석 형태를 보면 아래와 같다.
ssize_t sendmsg(int s, const struct msghdr *msg, int flags);
ssize_t recvmsg(int s, struct msghdr *msg, int flags);
send/recv와 달리 버퍼가 안 보이고, msghdr 라는 구조체를 쓰는데 - 물론 이 녀석이 버퍼겠지 - 이걸 우선 까보자.
struct msghdr {
void * msg_name; // 접속할 주소
socklen_t msg_namelen; // 접속할 주소 크기
struct iovec * msg_iov; // IO 버퍼
size_t msg_iovlen; // IO 버퍼 개수
void * msg_control; // 제어 정보 버퍼
socklen_t msg_controllen; // 제어 정보 버퍼 크기
int msg_flags; // 플래그
};
읔, 뭔가 많다. ㅡ_-) 난 이런게 젤 싫더라...sendmsg/recvmsg는 접속/비접속형 소켓을 모두 지원한다. msg_name과 msg_namelen은 비접속형 소켓(예:UDP)에서 받을 주소 구조체(예:struct sockaddr_in)을 알맞게 만들어서 넣어주는 것이다.
또한 I/O vector를 지원하여, msg_iov에는 struct iovec 배열의 주소, msg_iovlen은 배열 원소 개수를 예쁘게 넣는다. 이때 msg_iov는 NULL이 아니며, msg_iovlen은 0이 될 수 없다. 무조건 뭔가라도 하나 보내야한다. 만약 FD외에도 다른 데이터를 함께 넘기고 싶다면 이것을 애용(?)하면 좋겠다.
자, 드디어 FD에 대한 중요한 내용. 제어 정보 - 즉, FD 같은 걸 넘기는 방법이다. 이것을 위해 준비된 구조체가 있다. 바로 cmsghdr라는 구조체인데, 역시나 한 번 까보자.
struct cmsghdr {
socklen_t cmsg_len; // 제어 정보 전체 길이 (헤더크기 포함)
int cmsg_level; // 제어 정보 레벨
int cmsg_type; // 제어 정보 타입
/*
unsigned char cmsg_data[???]; // 제어 정보
*/
};
요놈은 제어 정보 헤더이다. 넘기고 싶은게 있다면, 이 헤더를 이용해서 새로운 구조체를 만들어야한다. 각 인자를 설명하는 것보다 대충 먼저 예제를 만들어보자. 우리가 넘기고자 하는 것은 file-descriptor. int형이고, 32비트 운영체제에서 4바이트를 차지하는 어여쁜 녀석이다. 이 녀석을 넘길 데이터 구조체를 만들어보자. UNP에선 union을 썼는데, 귀찮으니 대충 보고 따라해보자.참고로 FD를 넘기려면 Protocol은 UNIX domain, level은 SOL_SOCKET, type은 SCM_RIGHTS로 설정하라고 위대하신 UNP님께서 말씀하신다. (지랄)
typedef union _cmsg_fd
{
struct cmsghdr cmsg;
char cmsg[CMSG_SPACE(sizeof(int))];
} cmsg_fd;
CMSG_SPACE라는 매크로는 (제어 정보 + sizeof(cmsghdr))이다. 결국 cmsg_fd = [ cmsghdr ][ int ] 로 이뤄진 것이다. 우리가 넘길 건 int 하나이니 대충 이렇게 잡고 넘어가자. 나중에 데이터부분을 접근하려면 CMSG_DATA(cmsg_fd)하면 데이터부분만 포인터로 접근할 수 있다.자, 이제 예제 소스이다. _-_
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <iostream>
#include <cstdlib>
#include <errno.h>
using namespace std;
// 헉! 헤더만 해도 어마어마하게 많구나!!
//! @brief Control Message for sending/receiving FD.
typedefunion _cmsg_fd
{
struct cmsghdr cmsg;
char data[CMSG_SPACE(sizeof(int))];
} cmsg_fd;
bool
recvFD(int sockfd, int& fd, char* buf, size_t buflen)
{struct msghdr msg;
struct iovec iov;
cmsg_fd cmsg;
fd = -1;
// 뭔가 받아올 데이터가 있나부지?
iov.iov_base = buf;
iov.iov_len = buflen;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = &cmsg;
msg.msg_controllen = sizeof(cmsg);
msg.msg_flags = 0;
if ( -1 == recvmsg(sockfd, &msg, 0) )
{cerr << __func__ << '(' << getpid() << "): "
<< strerror(errno) << '(' << errno << ')' << endl;
return -1;}
const struct cmsghdr* cptr(CMSG_FIRSTHDR(&msg));
if ( !cptr )
{
cerr << __func__ << '(' << getpid() << "): "
<< "no cmsg 1st header" << endl;
return false;}
if ( SOL_SOCKET != cptr->cmsg_level || SCM_RIGHTS != cptr->cmsg_type )
{cerr << __func__ << '
(' << getpid() << "): "
<< "invalid control message" << endl;
return false;}
memcpy(&fd, CMSG_DATA(cptr), sizeof(fd));
return true;}
bool
sendFD(int sockfd, int fd, const char* buf, size_t buflen)
{
struct msghdr msg;
struct iovec iov;
cmsg_fd cmsg;
// 뭐 따로 보낼 데이터가 있다면 함께 알콩달콩 보내보자.
iov.iov_base = (void*)buf;
iov.iov_len = buflen;
msg.msg_name = NULL; // TCP가 아니라면 sockaddr_in 같은 걸 이용하자. -_-;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
// 컨트롤 메시지를 지정해준다.
msg.msg_control = &cmsg;
msg.msg_controllen = sizeof(cmsg);msg.msg_flags = 0;
// CMSG를 세팅하자!!
struct cmsghdr* cptr(CMSG_FIRSTHDR(&msg));
// 이럴 리 없겠지?
// if ( NULL == cptr ) { 에러처리; }
cptr->cmsg_len = CMSG_LEN(sizeof(int));
cptr->cmsg_level = SOL_SOCKET;
cptr->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(cptr), &fd, sizeof(int));
// 전송이닷!
if ( -1 == sendmsg (sockfd, &msg, 0) )
{
cerr << __func__ << '(' << getpid() << "): "
<< strerror(errno) << '(' << errno << ')' << endl;
return false;
}
return true;
}
int
main(int argc, char* argv[])
{
int sockfd[2];
if ( -1 == socketpair(AF_UNIX, SOCK_STREAM, AF_LOCAL, sockfd) )
{
cerr << __func__ << '(' << getpid() << "): "
<< strerror(errno) << '(' << errno << ')' << endl;
return EXIT_FAILURE;
}
if ( fork() )
{
// 부모라면...
// 표준 출력을 자식에게 보낸다.
int fd(1);
sendFD(sockfd[0], fd, (char*)&fd, sizeof(fd));
close(1);
cout << "standard output is closed" << endl; // 출력 안 될 것이다.
int res;
wait(&res);
}
else
{
// 자식이라면...
// 표준 출력 FD를 받는다. 1이 아닐 것이다.
close(1);
int fd, ofd;
recvFD(sockfd[1], fd, (char*)&ofd, sizeof(ofd));
cerr << "received fd: " << fd << ", extra data: " << ofd << endl;
if ( fd > -1 )
{
cout << "ah, ah standard output is testing..." << endl; // 출력 안 될 것이다.
dup2(fd, 1); // 받은 fd를 자식의 표준출력에 복사한다.
cout << "hello, world!" << endl; // 이것만 출력될 것이다.
}
}
return EXIT_SUCCESS;
}
결과
received fd: 6, extra data: 1
hello, world!
좀 길긴 하지만... -_- 대충 컴파일하고 실행하면, 부모의 표준 출력을 자식에게 잘 넘겨서 "hello, world!"만을 잘 찍는 것을 확인할 수 있다.
참고로 sendmsg로 fd를 전송하고, recvmsg로 fd를 받기 전에 fd를 close하더라도 sendmsg 시점에 fd에 대한 reference count를 증가하기 때문에, 받기도 전에 닫히는 불상사는 없을 것이다.
뱀발: 간만에 기술 관련 장문을 올리려니 삭신이 다 쑤신다. 쿨럭쿨럭.
뱀발2: 저~ 위에선 sendFD의 buf에 원래 fd를 넣었는데, NULL을 넣으면 데이터가 없거나 끊긴 것을 판단하지 못해서인지 recvFD에서 블럭된다. 귀찮더라도 1바이트라도 예쁘게 넣어서 보내주자.
댓글
댓글 쓰기