fork() 썼을때 소켓 discriptor를 어떻게 구분할까요?

jagalchee의 이미지

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>

#define SERVER_PORT 5125


int main()
{
    struct sockaddr_in serverAddr;
    int listenSocket, connectSocket, status, count=0;
    socklen_t serverAddrSize = (socklen_t)sizeof(struct sockaddr_in);
    pid_t pid;

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bzero( &(serverAddr.sin_zero), 8 );

    listenSocket = socket(AF_INET, SOCK_STREAM, 0);
    bind( listenSocket, (struct sockaddr *)&serverAddr, (socklen_t)sizeof(struct sockaddr_in) );
    listen(listenSocket, 5);

    while(1)
    {
        connectSocket = accept( listenSocket, (struct sockaddr *)&serverAddr, &serverAddrSize );
        count = count + 1;

        pid = fork();

        // 부모
        if( pid > 0 )
        {
            printf("부모 %d\n", connectSocket);
#         close(connectSocket);
        }

        // 자식
        else if( pid == 0 )
        {
            printf("자식 %d %d\n", count, connectSocket);
            return 0;
        }
    }

    return 0;
}

위의 코드는 다중연결을 위한 서버를 만들기 위해 fork()를 사용하는 경우, 자식 프로세스에서 소켓 디스크립터를 어떻게 구분할까..에 관해 궁금증을 가지고 간단히 짜본 것입니다.

첫번째로, # 부분을 주석처리 하고 서버를 실행한 후, 동시에 2개의 클라이언트가 접속을 하게 되면, 다음과 같은 결과가 나오게 됩니다.

자식 1 4
부모 4
자식 2 5
부모 5

즉, 당연한 결과라고 생각을 하지만 2개의 자식 프로세스에선 각각 다른 소켓 디스크립터 번호를 가지고 있다는 걸 알 수 있습니다.

헌데, # 부분을 주석처리 안 하고 서버를 실행한 후, 동시에 2개의 클라이언트가 접속을 하게 되면, 다음과 같은 결과가 나오게 됩니다.

자식 1 4
부모 4
자식 2 4
부모 4

즉, 2개의 자식 프로세스에선 같은 소켓 디스크립터 번호를 가지고 있다는 걸 알 수 있습니다. 그런데, 이와 같이 자식 프로세스가 가지고 있는 소켓 디스크립터 번호가 같음에도 불구하고, 각각 자식 프로세스는 각각에 접속된 클라이언와만 통신을 하더군요. 분명 read() 혹은 write() 할 때, 첫번 째 인자로 소켓 드스크립트 번호를 넘겨주는 걸로 알고 있는데요. 이 번호가 같으면 같은 소켓이라고 이해하고 있었습니다만, 위의 결과를 보니 아닌거 같습니다. 제가 잘 못 이해하고 있었던거 같네요.

어떻게 소켓 디스크립터 번호는 동일한데, 어떻게 소켓을 구분하는 건지요? 이에 대해서 속 시원하게 설명해 주시면 감사하겠습니다.

mithrandir의 이미지

같은 소켓(파일) 디스크립트 번호가 같은 소켓을 가리키는 것은 같은 프로세스 내부에 한해서 입니다.

그러므로 부모 프로세스가 accept에 의해 반환된 fd를 닫고 나서 다시 accept 해서 할당받은 fd는 방금 close 한 fd와는 관계가 없지요 (사실 같은 listenfd 에 의해 나온 녀석이므로 아예 관계가 없는 것은 아니겠지요) 따라서 자식 1과 자식 2가 가진 fd는 결국 다른것입니다.

(0, 1, 2 번은 fork에 의해 복사(dup) 가 이루어진 것이므로 같은 대상을 가리키는 것이지요)

또다른 예로 0, 1, 2 번 fd를 close 할 경우 socket() 에 의해 0이 반환 될 수 있는데, 이것 역시 0번 (stdout) 과는 관계가 없는 fd이지요.

언제나 삽질 - http://tisphie.net/typo/
프로그래밍 언어 개발 - http://langdev.net

lovemyin의 이미지

fork()를 하게되면 프로세스가 생성되고 스택과 힙 영역이 복사가 됩니다. 각 프로세스 마다 서로 상관없이 쓸 수 있다는 얘기죠
하지만 소켓을 생성한 후 fork()를 하게 되면 각 프로세스의 소켓은 하나의 소켓이 됩니다. 즉 참조카운터가 1증가된 것이지요
때문에 부모프로세스에서 소켓이 필요없다면 fork() 후에 당연히 소켓을 닫아야 할 것입니다. 그렇게 되면 소켓이 완전 닫히는 것이 아니라 참조 카운터가 1 감소됨에따라 결국 참조카운터는 2-1=1이 되는것입니다.

/***************************************************
* 가장 심플한 것이 가장 아름다운 것이다.
***************************************************/

김충길의 이미지

per process file descriptor 테이블이랑 kernel file descriptor 테이블의 차이를 찾아보면 됩니다.

screen + vim + ctags 좋아요~

ulra의 이미지

안녕하세요.

질문이 있는데요.

제가 짠 프로그램의 순서가 다음과 같습니다.

1) fork를 해서 부모가 자식이 생겼습니다.

2) 공유메모리공간을 설정해서 자식과 부모가 서로 데이터를 주고 받을수
있게 되었습니다.

3) 부모에서 accept를해서 소켓을 획득한다음에 이것을 공유메모리로 복사
했습니다.

4) 자식이 공유메모리를 통해서 소켓을 얻은다음 사용하였습니다.

이런식으로 만들었습니다.

자식이 공유메모리를 통해서 얻은 소켓을 이용하여 쓸수 있나요?

해보니까 저장된 값은 같은데 실제로 write함수를 쓰면

잘못된 파일 디스크립터라고 메세지가 나옵니다.

이런 문제는 어떻게 해결해야 하나요?

OTL 즐!!!! (좌절 금지!!!)

indie의 이미지

recvmsg와 sendmsg를 이용하면 됩니다.
자세한 내용은 스티븐 아저씨의 unp이나 apue에 passing descriptors로
나와 있을 겁니다.

집에나 갈까?

ulra의 이미지

sendmsg 함수를 사용한 예제같은것을 구할수 없나요?

지금 unp나 apue 책이 없습니다.

OTL 즐!!!! (좌절 금지!!!)

indie의 이미지

제대로 작동하는지 모르겠습니다.
예전에 스티븐 아저씨 책에서 배껴 온거 같은데...
크게 문제될게 없어 보이는데 한번 확인해보세요.
그리고 한번에 여러개의 디스크립터도 전송할수 있습니다.

int pass_fd(int local_fd, int *pass_fd)
{
	struct msghdr msg;
	struct cmsghdr *cmsgptr;
	struct iovec iov[1];
	char c = ' ';
	int rv;

	union {
		struct cmsghdr cm;
		char control[CMSG_SPACE(sizeof(int))];
	}control_un;

	iov[0].iov_base = (void *)&c;
	iov[0].iov_len = sizeof(char);

	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;
	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof(control_un.control);

	cmsgptr = CMSG_FIRSTHDR(&msg);
	cmsgptr->cmsg_len = CMSG_LEN(sizeof(int));
	cmsgptr->cmsg_level = SOL_SOCKET;
	cmsgptr->cmsg_type = SCM_RIGHTS;

	*((int *)CMSG_DATA(cmsgptr)) = *pass_fd;

	
	rv = sendmsg(local_fd, &msg, 0);
	if( rv == -1) {
		perror("sendmsg\n");
		exit(0);
	} else {
		close(*pass_fd);
	}

	return 0;
}

void recv_fd(int local_fd, int *peer_fd)
{
	struct msghdr msg, *ptr;
	struct cmsghdr *cmsgptr, *prevptr;
	struct iovec iov[1];
	int fd, i, rv, rcvsz;
	char c;

	union {
		struct cmsghdr cm;
		char control[CMSG_SPACE(sizeof(int))];
	}control_un;

	iov[0].iov_base = (void *)&c;
	iov[0].iov_len = sizeof(char);

	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;
	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof(control_un.control);

	rv = recvmsg(local_fd, &msg, 0);

	if( rv < 0 ) {
		printf("recvmsg errno: %d\n", errno);
		*peer_fd = -1;

		return;
	}

	cmsgptr = CMSG_FIRSTHDR(&msg);
	if( cmsgptr == NULL ) {
		printf("cmstgptr is NULL\n");
		goto find_next;
	}

	if( cmsgptr->cmsg_len != CMSG_LEN(sizeof(int))) {
		printf("cmsg_len error\n");
		goto find_next;
	}
	if( cmsgptr->cmsg_level != SOL_SOCKET) {
		printf("cmsg_level error\n");
		goto find_next;
	}
	if( cmsgptr->cmsg_type != SCM_RIGHTS) {
		printf("cmsg_type error\n");
		goto find_next;
	}

	*peer_fd = *((int*)CMSG_DATA(cmsgptr));
	return ;


find_next:
	{
		*peer_fd = -1;
		return ;
	}
}

집에나 갈까?

moonzoo의 이미지

Quote:
1) fork를 해서 부모가 자식이 생겼습니다.

2) 공유메모리공간을 설정해서 자식과 부모가 서로 데이터를 주고 받을수
있게 되었습니다.

3) 부모에서 accept를해서 소켓을 획득한다음에 이것을 공유메모리로 복사
했습니다.

4) 자식이 공유메모리를 통해서 소켓을 얻은다음 사용하였습니다.

이런식으로 만들었습니다.

자식이 공유메모리를 통해서 얻은 소켓을 이용하여 쓸수 있나요?

해보니까 저장된 값은 같은데 실제로 write함수를 쓰면

잘못된 파일 디스크립터라고 메세지가 나옵니다.

이런 문제는 어떻게 해결해야 하나요?

fork를 하는 순간부터 자식과 부모는 서로 다른

File Discriptor Table을 갖습니다.

이 table은 (fd : 해당 파일 link ) 의 쌍으로 되어있습니다.
(물론 또 다른 정보도 있을수 있겠지만 여기선 상관없으니...)

부모가 accept했을 경우에 부모는 이 table에

(해당 소켓 fd : 해당 소켓 link ) 를 기술합니다.

그러므로 부모는 이 fd값만 있으면 언제라도 해당 소켓에 link할

수 있습니다.

그러나 이 시점에서 자식의 table에는 이러한 내용이 없겠죠!

그러니 자식에게 단순히 fd값 만을 넘겨준다고 해서

해당 소켓에 link할 수는 없습니다.

그래서 일반적으로 fork후에 accept를 하는 것이 아니라

accept를 한 후에 fork를 합니다.

왜냐면 fork 할때 자식은 완전히 초기화된 테이블을 갖는게 아니라

부모의 복사본을 갖기때문에. fork할때 accept된 fd값이 기술된

table을 갖기 때문입니다. 그래서 해당 fd로 소켓에 link할 수 있습니다.

좀 두서없긴 했는데.. 암튼 그렇습니다 ㅋ

ulra의 이미지

궁금한게 있는데요.

local_fd는 어떻게 생긴 건가요?

pipe로 만들어야 하나요?

저는 이미 shmget,shmat 로 부모와 자식간에 공유메모리를 지정하였거든요.

이걸 이용할 수는 없나요?

OTL 즐!!!! (좌절 금지!!!)

indie의 이미지

저는 그냥 유닉스 도메인 소켓을 이용했습니다.
일반 소켓이나 파이프를 쓰셔도 되구요...

고양이 귀엽네요 ^-^

집에나 갈까?

ulra의 이미지

감사드립니다. ^^ 좋은 하루 되세요.

OTL 즐!!!! (좌절 금지!!!)

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.