• 카테고리

    질문 & 답변
  • 세부 분야

    시스템

  • 해결 여부

    해결됨

recvfrom 질문

20.08.27 03:37 작성 조회수 364

1

코드를 아래와 같이 작성한 후 printf("%s", rec.sun_path); 를 했는데 그냥 empty 합니다. 주소를 받아오고 싶은데 어떻게 해야하나요?

답변 6

·

답변을 작성해보세요.

1

rjsdud582님의 프로필

rjsdud582

질문자

2020.08.29

아하 유닉스 도메인 소켓이어서 출력이 안된거군요. 그리고 그것은 하나의 주소로도 가능하기 때문에 그런 것이고.

그래서 기존 유닉스 도메인 소켓에서는 client부에서 bind() 할 필요가 없다. 하지만 인터넷 도메인 소켓은 하나의 호스트 내에서 이루어지는게 아니기 때문에 클라이언트의 주소도 반드시 설정 되어야만 한다!! 이거군요

이해 갔습니다. 정말 감사합니다!! 

1

안녕하세요. 좋은 질문 주셨네요. 

recvfrom()의 5번째 파라미터와 6번째 파라미터는 상대 소켓의 주소를 알아오기 위해 사용됩니다.  recvfrom의 man page를 보시면 아래와 같이 설명되어 있습니다.

잘 읽어보시면 실수하신 부분이 보이실 것입니다. 바로 6번째 파라미터인 len 파라미터를 반드시 sizeof(struct sockaddr_un) 값으로 초기화해서 넘기셔야 합니다. 첨부해주신 코드에서는 addrlen 변수를 sizeof(struct sockaddr_un)으로 설정한 부분이 보이지 않네요. 이 부분은 빠뜨리셨어요 :)

제가 수업에 사용했던 코드를 기반으로 테스트를 하고 계신 것이라 예상됩니다. 만약 그렇다라고 한다면.. addrlen의 초기화 만으로는 client socket의 address를 출력하지 않을 것입니다. 그 이유는 client 측에서 socket에 bind를 하지 않았기 때문입니다.  왜 그런지는 아래 내용을 찬찬히 읽어봐주세요.

일반적으로 datagram 서버 소켓은 recvfrom()을 호출하기 전에 bind()를 하게 됩니다. 이 소켓이 어떤 주소로 들어오는 데이터를 받을지 결정하는 것이 바로 bind() 입니다. 서버 소켓인데 bind를 하지 않았다는 말은 곧 어떤 주소로 들어오는 데이터를 받을지 정하지 않고 커널에 수신 명령을 내리는 것과 같습니다. 그래서 서버 소켓은 반드시 bind를 하게 됩니다. 

클리이언트 소켓은 다릅니다. sendto()를 호출하기 전에 bind를 하지 않아도 서버 소켓의 주소만 잘 입력해주면 데이터 전송에 성공합니다. 전송 대상의 주소는 반드시 코드에서 결정해줘야 합니다. 사람이 정해주지도 않았는데 커널이 독심술로 대상을 결정할 수는 없겠죠 :) 그런데 소켓의 소스 주소는 코드에서 설정하지 않아도 됩니다. 왜냐하면 커널이 적절한 값을 찾아 자동으로 넣어주기 떄문입니다. 특히 인터넷 도메인 소켓의 경우에는 클라이언트의 소켓에도 반드시 주소와 포트번호가 설정되어 있어야 통신이 가능합니다. 그래서 인터넷 도메인 소켓은 코드에서 클라이언트 소켓에 bind를 해주지 않으면 커널이 적절한 주소와 포트번호를 할당해 bind 처리를 해 줍니다. 그래서 코드에서 bind()를 하지 않아도 통신이 가능하죠. 만약 사용하고 싶은 주소와 포트번호가 있다면 bind()를 해주면 됩니다.  그런데 유닉스 도메인 소켓은 인터넷 도메인 소켓과 또 다릅니다. 코드에서 클라이언트 소켓에 bind()를 하지 않았다면 자동으로 뭔가를 설정하지 않고 그냥 연결을 합니다. 서버가 열어놓은 소켓 하나로 양방향 통신이 가능하기 때문이죠.

이야기가 좀 길어졌네요. 결론적으로 unix domain socket의 recvfrom()에서 source address를 가져올 수 있습니다. 단, 클라이언트 소켓에 bind()를 해줘야 bind한 값을 얻어올 수 있습니다. 아래 코드는 강의에서 사용하던 코드에 약간의 수정을 해서 source address를 받아오도록 했습니다. 참고하시길 바랍니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCK_PATH	"sock_stream_un"

static void print_usage(const char *progname)
{
	printf("%s (server|client)\n", progname);
}

static int do_server(void)
{
	int ret;
	int sock;
	struct sockaddr_un addr;
	struct sockaddr_un client_addr;
	socklen_t len;
	char buf[128];

	sock = socket(AF_UNIX, SOCK_DGRAM, 0);
	if (sock == -1) {
		perror("socket()");
		return -1;
	}

	memset(&addr, 0, sizeof(addr));
	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
	if (bind(sock, (struct sockaddr *)&addr, 
				sizeof(struct sockaddr_un)) == -1) {
		perror("bind()");
		close(sock);
		return -1;
	}

	memset(buf, 0, sizeof(buf));

	/* len SHOULD be set to sizeof(struct sockaddr_un) */
	memset(&client_addr, 0, sizeof(struct sockaddr_un));
	len = sizeof(struct sockaddr_un);
	ret = recvfrom(sock, buf, sizeof(buf), 0, 
			(struct sockaddr *)&client_addr, &len);
	if (ret == -1) {
		perror("recv()");
		close(sock);
		return -1;
	}
	printf("client addr [%s]\n", client_addr.sun_path);
	printf("client said [%s]\n", buf);
	close(sock);

	return 0;
}

static int do_client(void)
{
	int sock;
	struct sockaddr_un addr;
	struct sockaddr_un srcaddr;
	char buf[128];
	int ret;

	sock = socket(AF_UNIX, SOCK_DGRAM, 0);
	if (sock < 0) {
		perror("socket()");
		return -1;
	}

	/* bind address to client socket */
	memset(&srcaddr, 0, sizeof(srcaddr));
	srcaddr.sun_family = AF_UNIX;
	strncpy(srcaddr.sun_path, "ThisIsSrcAddr", sizeof(srcaddr.sun_path) - 1);
	if (bind(sock, (struct sockaddr *)&srcaddr, 
				sizeof(struct sockaddr_un)) == -1) {
		perror("bind()");
		close(sock);
		return -1;
	}

	memset(&addr, 0, sizeof(addr));
	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);

	memset(buf, 0, sizeof(buf));
	snprintf(buf, sizeof(buf), "this is msg from sock_dgram");
	ret = sendto(sock, buf, sizeof(buf), 0,
			(struct sockaddr *)&addr, sizeof(struct sockaddr_un));
	if (ret < 0) {
		perror("send()");
		close(sock);
		return -1;
	}

	close(sock);
	return 0;
}

int main(int argc, char **argv)
{
	int ret;

	if (argc < 2) {
		print_usage(argv[0]);
		return -1;
	}

	if (!strcmp(argv[1], "server")) {
		ret = do_server();
	} else if (!strcmp(argv[1], "client")) {
		ret = do_client();
	} else {
		print_usage(argv[0]);
		return -1;
	}

	return ret;
}

0

:)

0

source address가 출력되지 않은 이유는 말씀하신 1, 2번 항목으로 인한 것이 맞습니다.

커널이 자동으로 할당해준 주소도 볼 수 있습니다. 인터넷 도메인 소켓의 경우 커널이 자동으로 할당해준 주소를 볼 수 있죠. 다만, unix domain socket은 자동으로 할당해주지 않기 때문에 출력되는 것이 없는 것입니다. 먼저 답변에서도 말씀드렸지만, unix domain socket은 하나의 주소(즉, 서버가 설정한 파일명)로도 양방향 통신이 되기 때문에 클라이언트의 주소를 할당할 필요가 없는 것입니다.

인터넷 도메인 소켓의 경우 클라이언트의 주소도 반드시 설정되어야 통신이 가능합니다. 대부분의 경우 클라리언트의 주소는 커널이 자동설정하게 하여 통신을 해도 별 문제가 없죠. 하지만 DHCP와 같은 특수한 프로토콜은 특수한 IP 주소와 소스 포트번호를 사용해야 하기 때문에 이런 프로토콜을 구현하시려면 클라이언트 소켓에도 반드시 bind를 해줘야 합니다.

이제 좀 더 확실하게 이해가 되시나요? :)

0

rjsdud582님의 프로필

rjsdud582

질문자

2020.08.28

음 그러니까 제 코드가 잘못된 이유는

1. sizeof(struct sockaddr_un)으로 초기화 해주지 않았고

2. client에서 bind를 해주지 않았기 때문이라는 게 맞나요? 

그리고

처음에 사실 저렇게 초기화를 해봤었는데 아무것도 뜨지 않고 그냥 empty 하더라고요. 그럼 이 말인 즉슨 커널이 자동적으로 할당해준 주소는 단순히 printf 문으로 볼 수 없다는 게 맞나요?

그리고 어렴풋이 강의중에는 인터넷 도메인 소켓 방식에서는 이런 주소를 받아오는게 중요하다고(?) 하셨던 것 같은데 그 이유는 인터넷 도메인 소켓에서는 어떤 포트번호를 사용하고 있는지에 대한 정보가 중요하기 때문인가요?

아직 네트워크에 대한 개념이 없어서(이번 학기에 수강할 예정).. 확인차 제가 잘 이해했는지 여쭙고 싶어 추가 질문 드립니다! 감사합니다!

0

rjsdud582님의 프로필

rjsdud582

질문자

2020.08.28

와~ 진짜 감사드립니다!