Post Lists

2018년 7월 13일 금요일

5. System Calls or Bust

http://beej.us/guide/bgnet/html/multi/syscalls.html

5. System Calls or Bust

이것은 우리가 너가 Unix box의 네트워크 기능에 접근하도록 해주는 시스템 호출(또는 다른 라이브러리 호출)에 들어가는 부분이다. 또는 그 문제에 대해 socket API를 지원하는 어떤 box든지. 너가 이러한 함수들 중 하나를 호출 할 때, kernel은 인계되고 너를 위해 자동으로 모든 일을 한다.

대부분 사람들이 여기에서 막히는 부분은 무슨 순서로 이러한 것들을 호출하는 가이다. 거기에서, 너도 이미 봤듯이, man pages는 쓸모가 없다. 음, 그 무서운 상황을 돕기위해, 나는 다음 섹션에서 정확히 너가 그것들을 프로그램에서 호출할 필요가 있는 같은 순서대로 system calls를 나열하려고 노력한다.

(대담함을 위해, 아래에 있는 많은 코드 조각들은 필수적인 에러 체크를 포함하지 않는다. 그리고 그것들은 흔히 getaddrinfo()에 대한 호출의 결과가 성공하고 링크드 리스트에서 유요한 entry를 반환한다고 가정한다. 이러한 상황 둘 다 적절히 stand-alone 프로그램에서 다뤄진다.)

5.1 getaddrinfo() - Prepare to launch!
이것은 많은 옵션을가진 한 함수의 real workhose이지만, 사용은 실제로 꽤 간단하다. 그것은 너가 나중에 필요한 구조체를 설정하는 것을 도와준다.

조금의 역사가 있다: 너는 DNS lookup을 하려고, gethostbyname()이라고 불리는 함수를 사용하곤 했었다. 그러고나서 너는 직접 struct sockaddr_in에 그 정보를 불러오고, 너의 호출에서 그것을 사용한다.

이것은 더이상 고맙게도 필수적이지 않다. (바람직하지 않을 뿐만 아니라, 만약 너가 IPv4 와 IPv6에 작동하는 코드를 쓰길 원한다면). 이러한 현대에는, 너는 이제 너를 위해 모든 종류의 일을 하는 getaddrinfo() 함수를 갖는다. 이것은 DNS와 service name lookup을 포함한다. 그리고 게다가 너가 필요한 구조체를 채워준다.

한 번 봐보자

#include <sys/type.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *node, // e.g. "www.example.com" or IP
                const char *service, // e.g. "http" or port number
                const struct addrinfo *hints,
                struct addrinfo** res);

너는 세 개의 input 인자를 이 함수에 주고, 그것은 너에게 결과로 링크드리스트에 대한 포인터를 너에게 준다.

node parameter는 연결한 host name 또는 IP 주소이다.

다음은 service parameter인데, "80"과 같은 포트 번호일 수 있고, 특별한 서비스의 이름일 수 있다. (IANA Port List에서 발견되거나, 너의 유닉스 머신에서 /etc/services 파일에서) "http" 또는 "ftp" 또는 "telnet" 또는 "smtp" 또는 뭐든지.

최종적으로, hints 파라미터는 너가 관련된 정보로 이미 채운 struct addrinfo를 가리킨다.

여기에서 sample call이 있다. 만약너가 너의 host의 IP address를 듣고싶은 서버라면, port 3490. 이것은 실제로 어떤 듣기 또는 네트워크 설정을 하지 않는다; 그것은단지 우리가 나중에 사용할 문제를:

int status;
struct addrinfo hints;
struct addrinfo *servinfo; // will point to the results

memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AR_UNSPEC; // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE; // fill in my IP for me

if((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0)
{
  fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
  exit(1);
}

// servinfo now points to a linked list of 1 or more struct addrinfos

// ... do everything until you don't need servinfo anymore ....

freeaddrinfo(servinfo); // free the linked-list

내가 ai_family를 AF_UNSPEC으로 설정한 것을 주목해라. 그리고 거기에서, 나는 우리가 IPv4 또는 IPv6를 쓰는 것을 신경쓰지 않는다고 말한다. 너는 그것을 AF_INET 또는 AF_INET6라고 설정할 수 있다. 만약 너가 하나 또는 다른 것을 특정하게 원한다면.

또한, 너는 거기에서 AI_PASSIVE 플래그를 볼 것이다; 이것은 getaddrinfo()에게 나의 local host의 주소를 socket structures에 할당하라고 말한다. 이것은 너가 그것을 하드코딩 할 필요가 없기 때문에 좋다. (또는 너는 getaddrinfo()의 첫 번째 파라미터로서, 특정한 주소를 넣을 수 있다. 거기에서 나는 현재 NULL을 가진다.)


그리고나서 우리는 호출을 한다. 만약 에러가 있다면 (getaddrinfo()가 0이 아닌것을 반환한다면), 우리는 gai_strerror() 함수를사용하여 그것을 출력한다. 만약 모든 것이 적절히 작동한다면, servinfo는 struct addrinfo의 한 링크드 리스트를 가리킨다. 그리고 그것 각각은 우리가 나중에 사용할 수 있는 struct sockaddr을 포함한다.

마지막으로, 우리는 결국에 getaddrinfo()가 매우 우아하게 우리를 위해 할당한 연결리스트로 작업하는 것이 끝났을 때, 우리는 그것을 freeaddrinfo()에 대한 호출로 해제해야만 한다.

여기에서 sample call이 있다. 만약 너가 특정한 서버에 연결하고 싶은 클라이언트라면, 가령 "www.example.net" port 3490. 또 다시 이것은 실제로 연결하지 않지만, 우리가 나중에 사용할 구조를 설정한다:

int status;
struct addrinfo hints;
struct addrinfo *servinfo; // will point to the results

memset(&hints,0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets

// get ready to connecct
status = getaddrinfo("www.example.net", "3490", &hints, &servinfo);

// servinfo now points to a linked list of 1 or more struct addrinfos

// etc.

나는 계속해서 servinfo가 주소 정보를 가진 링크드리스트라고 말한다. 이 정보를 보여줄 빠른 데모 프로그램을 써보자. 이 짧은 프로그램은 너가 커맨드 라인에 명시한 어떤 호스트인지에 대한 IP 주소를 프린트할 것이다:


너도 보듯이, 그 코드는 너가 커맨드 라인에 무엇을 넘기든 getaddrinfo()를호출한다. 그리고 res에 의해 가리켜지는 링크드리스트를 채운다. 그러고나서 우리는 그 리스트에 대해서 반복하고, 거기에 있는 것을 프린트한다.

(약간 더러운 것이 있다. 거기에서 우리는 struct sockaddr들이 IP version에 따라 다른 유형인지 알아야 한다. 이것에 대해서 미안하다! 나는 그것을 하는 더 좋은 방법을 모른다.)

Sample run! 모든 사람들은 스크린샷을 사랑한다:

이제 어느정도 통제할수 있으니, 우리는 다른 소켓 함수에 넘기기위해 getaddrinfo()로부터 얻은 그 결과를 사용할 것ㅅ이고, 마지막으로 우리의 네트워크 연결이 된다. 계속해서 읽어라!

5.2 socket() - Get the File Descriptor!
나는 더 이상 늦출 수 없다 - 나는 socket() 시스템 호출에 대해 말해야만 한다. 여기에 breakdown이 있다:

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, intprotocol);

그러나 이러한 인자들은 무엇인가? 그것들은 너가 무슨 종류의 소켓을 원하는지 말하도록 해준다. (IPv4 또는 IPv6, stream or datagram, 그리고 TCP 또는 UDP).

사람들이 이러한 값들을 하드코딩하는 것은 익숙하고, 너는 전적으로 여전히 그것을 할 수 있다. (domain은 PF_INET또는 PF_INET6, type은 SOCK_STREAM또는 SOCK_DGRAM, 그리고 protocol은 주어진 type에 대해 적절한 프로토콜을 선택하기 위해 0으로 설정되어질 수 있다. 또는 너는 너가 원하는 프로토콜을 look up하기위해 getprotobyname()을 호출 할 수 있다, "tcp"인지 "udp"인지에 대해)

(이 PF_INET은 struct sockaddr_in에서 sin_Family field를 초기화할 때, 너가 사용할 수 있는 AF_INET과 가까운 친척이다. 사실, 그것들은 매우 밀접하게 관련되어 있어서, 그것들은 실제로 같은 값을 갖고, 많은 프로그래머들은 socket()를 호출하고 AF_INET을 PF_INET 대신에 첫 번째 인자로 보낼 것이다. 이제, 우유랑 쿠키좀 먹어보자. 일화를 들을 시간이니까. 옛날 옛적에, 오랜 시간 전에, 아마도 한 address family가 ("AF_INET"에서 "AF"가 상징하는 것) 그것들의 프로토콜 패밀리에의해 참조되는 몇 가지 프로토콜들을 지원할지도 모른다고 생각되어졌다. ("PF_INET"에서 "PF"가 상징하는 것). 그것은 발생하지 않았다. 그리고 그것들은 그 이후에도 행복하게 살아왔다. 끝. 그래서 해야할 가장 정확한 것은 struct sockaddr_in에서 AF_INET을 쓰는 것이고, socket() 호출에서는 PF_INET을 쓰는 것이다.)

어쨋든, 그걸로 충분하다. 너가 정말 하기로 원하는 것은 getaddrinfo()에 대한 호출의 결과 값을 사용하는 것이고, 그것들을 이렇게 직접적으로 socket() 넘기는 것이다:

int s;
struct addrinfo hints, *res;

// do the look up
// [pretend we already filled out the "hints" struct]
getaddrinfo("www.example.com", "http", &hints, &res);

// [again, you should do error-checking on getaddrinfo(), and walk
// the "res" linked list looking for valid entries instead of just
// assuming the first one is good (like many of these examples do.)
// See the section on client/server for real examples.]

s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

socket()은 간단히 너에게 너가 나중 system call에 사용할 수 있는 socket descriptor를 반환하거나 에러 시에는 -1를 반환한다. 전역 변수 errno는 error 값으로 설정된다. (좀 더 세부사항을 위해서 errno man page와, 멀티쓰레드 프로그램에서 errno를 사용한 quick note를보아라)

좋아 그러나 이 소켓이 무엇에 좋을까? 답은, 그것은 그 자체로는 쓸모 없고, 너는 좀 더 많은 시스템 호출을 읽고 만들 필요가 있다. 그것이 작동하게 하기 위해서

5.3 bind() - What port am I on?
일단 너가 socket를 가졌다면, 너는 그 소켓을 너의 local machine에 있는 한 포트와 사상시킬지도 모른다. (이것은 흔히 너가 특정 포트에서 들어오는 연결을 listen()할거라면 된다. - 멀티플레이어 네트워크 게임은, 그들이 너에게 "connect to 192.168.5.10 port 3490" 하라고 말할 때 이것을 한다.) 그 포트 번호는 커널에 의해서  들어오는 패킷을 어떤 프로세스의 소켓 descriptor와 매치시키는데 사용된다. 만약 너가 connect()만 할 거라면 (너는 서버가 아닌 클라이언트이기 때문에), 이것은 아마도 불필요 할지도 모른다. 어쨋든 그것을 읽어라.

여기에 bind() system call에 대한 시놉시스가 있다:

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr* my_addr, int addrlen);

sockfd는 socket()에 의해 반환된 socket file descriptor이다. my_addr은 너의 주소, port 그리고 IP 주소에 대한 정보를 포함하는 struct sockaddr에 대한 포인터이다. addrlen은 그 주소의 바이트 길이이다.

한 덩어리에 흡수해야할게 조금 있다. 그 소켓을 프로그램이 동작하는 port 3490에 호스트에 바인드 시키는 예제를 가져보자.

struct addrinfo hints, *res;
int sockfd;

// first load up address structs with getaddrinfo():

memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;  // fill in my IP for me

getaddrinfo(NULL, "3490", &hints, &res);

// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// bind it to the port we passed in  to getaddrinfo():
bind(sockfd, res->ai_addr, res->ai_addrlen);

AI_PASSIVE 플래그를 사용하여, 나는 프로그램에게 그것이 작동하고있는 호스트의 IP에 바인드하라고 말하는것이다. 만약 너가 특정 로컬 IP 주소에 바인드하고 싶다면 AI_PASSIVE를 빼고, getaddrinfo()의 첫 번째 인자에 IP 주소를 넣어라.

bind()는 또한 에러시에 -1을 반환하고, errno를 에러값으로 설정한다.

많은 오래된 코드는 수동으로 struct sockaddr_in을 bind()호출하기전에 채운다. 명백히 이것은 IPv4-specific 이지만, 너가 IPv6로 같은 것을 하지 못하게 할 것은 없다. getaddrinfo()를 사용하는 것이 더 쉬운것을 빼고는 일반적으로. 어쨋든, 오래된 코드는 이것처럼 보인다:

// !!! THIS IS THE OLD WAY !!!

int sockfd;
struct sockaddr_in my_addr;
sockfd = socket(PF_INET, SOCK_STREAM, 0);

my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
memset(my_addr.sin_Zero, '\0', sizeof(my_addr.sin_zero));

bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr);

위의 코드에서, 너는 또한 INADDR_ANY를 s_addr 필드에 할당할 수 있다. 만약 너가 너의 로컬 IP 주소에 바인드 하ㅗ 싶다면 (위에서 AI_PASSIVE 플래그처럼) INADDR_ANY의 IPv6 버전은 너의 strut sockaddr_in6의 sin6_addr필드에 할당되는 전역 변수 in6addr_any이다. (또한 너가 변수 intializer로서 사용할 수 있는 매크로 IN6ADDR_ANY_INIT이 있다.)

bind()를 호출할 때 봐야할 다른 것이 있다: 너의 포트 번호 아래로 가려고 하지말아라. 1024 아래에 있는 모든 포트번호는 예약되어 있다 (만ㅇ갸 너가 superuser가 아니라면)! 너는 그 위에 있는 포트 번호 어떤 것이든 가질 수 있다. 65535까지 (다른 프로그램에 의해서 이미 사용되고 있지 않다면)

가끔씩 너는 눈치챌지도 모르는데, 너는 서버를 다시 실행시키려하고, bind()가 실패한다. "이미 사용중인 주소"라고 말하면서. 그것이 무엇을 의미하는가? 음, 연결되었던 한 소켓이 여전히 커널에서 돌아다니고 있고, 그것은 그 포트를 독차지하고 있는 것이다. 너는 그것이 clear 되기를 기다릴 수 있고 (1분정도), 또는 그것이 그 포트를 재사용 하도록 너의 프로그램이 코드를 추가할 수 있다, 이것 처럼:

int yes = 1;
// char yes = '1'; // Solaris people use this

// lose the pesky "Address already in use" error message
if (setsockopt (listener, SOL_SOCKET, SO_REUSEADDR, &yes,sizeof(yes) == -1)
{
   perror("setsockopt");
   exit(1);
}

bind()에대한 한 작은 추가, 마지막 note: 너가 전적으로 그것을 호출하지 않을 때가 있다. 만약 너가 한 원격 기계가 connect()중이고, 너가 너의 로컬 포트가 무엇인지신경쓰지 않는다면 (너가 오직 원격 포트만을 신경쓴느 telent의 경우처럼), 너는 간단히 connect()를 호출할 수 있고, 그것은 그 소켓이 bind되지 않았는지를 볼 것이고, 필요하다면 사용되지 않은 local port에 그것을 바인드 할 것이다.

5.4. connect() - Hey, you!
잠시동안 너가 telnet 프로그램인 척 해보자. 너의 사용자는 너에게 명령을 내린다 (영화 TRON에서 처럼) socket file descriptor를 얻어오라고. 너는 순응하고 socket()을 호출한다. 다음으로, 사용자는 너에게 포트번호 "23"에 있는 "10.12.110.57" 연결하라고 말한다. (표준 telnet port이다.) 너는 이제 무엇을 할까?

운좋게도, 너는 이제 coonect() section을 읽고 있다. - 원격 호스트에 어떻게 연결할지에 대해서이다. 그래서 성급하게 읽어보자! 지체할 시간이 없다!

connect() call은 다음과 같다:

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

sockfd는우리의 친근한 이웃 소켓 파일 디스크립터이다. socket() call에 의해 반환되는. 그리고 serv_addr은 목적지 포트와 IP주소를 담고있는 struct sockaddr이고, addrlen은 서버 주소 structure의 바이트 길이이다.

모든 이 정보는 getaddrinfo() 호출의 결과로 얻어질 수 있다.

이제 점점 더 이해되기 시작하는가? 나는 여기에서 너를 들을 수 없다, 그래서 나는 그렇기를 희망해야한다. "www.example.com"의 포트 3490에 socket connection을 해보는 예쩨를 가져보자:

struct addrinfo hints, *res;
int sockfd;

// first, load up address structs with getaddrinfo() :

memset(&hints, 0, sizeof(hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

getaddrinfo("www.example.com", "3490", &hints, &res);

// make a socket:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// connect!

connect(sockfd, res->ai_addr, res->ai_addrlen);

또 다시, 옜날 시절 프로그램들은 그들 자신의 struct sockaddr_in들을 채운다. coonet()에 넘기기 위해서. 너는 만약 원한다면 그것을 할 수 있다. bind() section에서 유사한 note를 보아라 위에서.

connect()로부터 반환 값을 체크해보아라- 그것은 에러시에 -1을 반환하고, errno 변수를 설정한다.

또한, 우리는 bind()를 호출하지 않은 것을 주목해라. 기본적으로 우리는 우리의 로컬 포트 번호를 신경쓰지 않는다; 우리는 오직 어디로 갈지만을 신경쓴다. (원격 포트) 커널은 우리를 위해 로컬 포트를 고를 것이고, 우리가 연결한 사이트는 자동적으로 우리로부터 이 정보를 얻는다. 걱정하지 말아라.

5.5. listen() - Will somebody please call me?
좋아, 속도를 바꿀 시간이다. 너가 원격 호스트에 연결하고 싶지 않다면 어떨까? 재미삼아, 너가 들어오는 연결을 기다리고 그것들을 어떤 방식으로 다루고 싶다고 가정해보자. 그 프로세스는 두 단계이다: 처음에 listen()하고 그러고나서 accept()한다.

listen call은 꽤 간단하다, 그러나 꽤 설명을 요구한다:

  int listen(int sockfd, int backlog);

sockfd는 socket() 시스템 호출로 나온 보통의 socket file descriptor이다. backlog는 들어오는 큐에 허용되는 연결의 개수이다. 그것이 무슨 말인가? 음, 들어오는 연결들은 너가 그것들을 accept()할 때까지 이 큐에서 기다릴 것이다. (아래를 보아라) 그리고 이것은 얼마나 많은 것들이 큐에 있을지에 대한 제한이다. 대부분의 시스템들은 조용히 이 숫자를 약 20개로 제한한다; 너는 아마도 그것을 5 또는 10으로 설정할 수 있다.

또 다시, 보통 하듯이, listen() -1을 반환하고, error시에 errno를 설정한다.

또한, 너가 이미 상상했듯이, 우리는 listen()을 호출하기 전에 bind()를 호출할 필요가 있다. 그래서 그 서버는 특정 포트에서 작동하게 하기 위해서이다. 너는 너의 친구에게 어떤 포트에 연결할지를 말할 수 있어야만 한다.


getaddrinfo();
socket();
bind();
listen();
/* accept() goes here */

나는 sample 코드에 그것을 남겨둘 것이다. 왜냐하면 그것은 꽤 자기 스스로 설명하고 있는 것이기 때문이다. (아래에서, accept() section에서의 코드는 좀 더 완전하다.) 이 전체 sha-bang의 정말 까다로운 부분을 accept()에 대한 호출이다.

5.6. accept() - "Thank you forcalling port 3490."
준비해라 - accept() call은 조금 이상하다! 여기에서 일어날 것은 이것이다: 꽤 멀리있는 누군가가 너의 machine에 너가 listen()하고 있는 포트에 connect() 하려고 노력할것이다. 그들의 connection은 accept()ed되기 위해 큐에 기다리고 있을 것이다. 너는 accept()를 호출하고, 너는 그것에게 pending connection을 얻으라고 말한다. 그것은 너에게 brand new socket file descriptor를 줄 것인데, 이것은 단일의 연결을 위해 사용되는 것이다! 맞아, 갑자기 너는 하나의 값으로 두 개의 socket file descriptors를 가지게된다. 원래의 것은 여전히 새로운 연결에 대해 듣고있는 중이고, 그 새롭게 만들어지는 것은 마침내 send()와 recv()를 할 준비가 되어있다. 우리는 거의 다 왔다!

그 호출은 다음과 같다:


#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd는 listen() 하고있는 socket descriptor이다. 충분히 쉽다. addr은 보통 local struct sockaddr_storage에 대한 포인터일 것이다. 이것은 들어오는 연결에 대한 정보가 가는 곳이다. (그리고 이것과 함께 너는 어떤 호스트가 너에게 어떤 포트로 전화걸고 있는지를 결정할 수 있다). addrlen은 local integer variable인데, sizeof(struct sockaddr_storage) 로 설정되어야 한다. 그것의 주소가 accept()로 넘겨지기 전에. accept()는 addr에 그렇게 많은 바이트를 넣지 않을 것이다. 만약 그것이 더 적게 넣는다면, 그것은 반영시키기 위해, addrlen의 값을 바꿀 것이다.

맞춰보아라. accept()는 에러가 발생한다면 -1을 반환하고, errno를 설정한다.

이전 처럼, 이것은 한 덩어리를 흡수할 묶음이고, 그래서 여기에 너의 속독을 위한 sample code fragment가 있다.

(#include <netdb.h> 를 추가해야 AI_PASSIVE 사용가능)

또 다시, 우리가 모든 send()와 recv() calls를 위해 소켓 descriptor new_fd를 사용할 것이라는 것에 주목해라. 만약 너가 한 개의 connection을 받았다면, 너는 같은 포트에 들어오려는 커넥션을 막기위해서, listening하고 있는 sockfd는 close()할 수있다. 만약 너가그렇게 원한다면.

5.7. send() and recv() - Talk to me, baby!
이러한 두 함수들은 stream socket 또는 연결된 datagram sockets 위에서 의사소통을 위한 것이다. 만약 너가 일반적인 연결되지 않은 regular datagram sockets을 사용하길 원한다면, 너는 sendto()와 recvfrom()의 섹션에서 볼 필요가 있을 것이다. send() call:

  int send(int sockfd, const void *msg, intlen, int flags);

sockfd는 너가 데이터를 보내길 원하는 sockdescriptor이다. (그것은 socket()으로 반하ㅗㄴ된 것이거나또는 너가 accept()로 얻은 거이다.) msg는 너가 보내길 원하는 데이터에 대한 포인터이고, len은 그 데이터의 바이트 길이이다. flags는 그냥 0으로 설정해라. (send() man page를 보아라 좀 더 정보와 관련된 flags들을 위해서)

몇 가지 sample code는 다음과 같을 것이다:


char *msg = "Beej was here!";
int len, bytes_sent;
.
.
.
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
.
.
.

send()는 실제로 보내진 바이트의 수를 반환한다 - 이것은 너가 그것에게 보내라고한 수보다 더작을지도 모른다! 보아라, 가끔식너는 그것에게 전체 데이터를 보내라고 하지만, 그것을 할 수 없다. 그것은 그것이 가능한 데이터의 데부분을 보내고, 너가 나중에 나머지를 보내도록 너를 신뢰한다. 기억해라, 만약 send()에 의해 보내진 값이 len의 값과 맞지 않다면, 그 문자열의 나머지를 보내는 것은 너에게 달려 있다. 좋은 소식은 이것이다: 만약 그 패킷이 작다면 (1K 이하 또는 그 정도) 그것은 아마도 한 번에 전체의 것을 간신히 보낼 것이다. 또 다시, -1은 에러시에 반환되고, errno는 error number로 설정된다.

recv() call은 여러 면에서 유사하다:

  int recv(int sockfd, void* buf, int len, int flags);

sockfd는 읽혀질 socket descriptor이고, buf는 정보가 읽혀질 버퍼이고, len은 그 버퍼의 최대길이이다. flags는 또 다시 0으로 설정될수있다. (flag 정보에 대해 recv() manpage를 보아라.)

recv()는 실제로 버퍼에 읽혀진 바이트의 수를 반환하고, 에러시에 -1을 반환한다. (errno set와 함께)

기다려! recv()는 0을 반환할 수 있다. 이것은 오직 한 가지 만을 의미할 수 있다: 그 원격 side가 너에 대한 연결을 끊었다는 것이다! 0의 반환 값은 너가 이것이 발생했다는 것을 알려주는 recv()의 방법이다.

쉬웠었다 그렇지 않냐? 너는 이제 데이터를 stream sockets에서 왔다갔다 보낼 수 있다. Whee! 너는 이제 Unix Network Programmer! 이다.

5.8. sendto() and recvfrom() - Talk to me, DGRAM-style
"이것은 모두 괜찮고 멋져." 나는 너가 말하는 것을 듣고있다, "그러나 어디에서 이거는 연결되지 않은 datagram socket을 남겨두었나?" 문제없다 친구야. 우리는 할 게 있다.

datagram sockets은 원격 호스트에 연결되지 않기때문에, 우리가 한 패킷을 보내기전에, 우리는 어떤 정보를 줄 필요가 있는지 맞춰보아라. 맞아! 목적지 주소! 여기에 한 스쿱이 있다:

  int sendto(int sockfd, const void* msg, int len, unsigned int flags, conststruct sockaddr* to, socklen_t tolen);

너도 보듯이 이 호출은 기본적으로 두 가지 다른 정보를 추가하여 send()의 호출과 같다. to는 struct sockaddr에 대한 포인터이다. (이것은 아마도 또 다른 struct sockaddr_in 또는 struct sockaddr_in6 또는 struct sockaddr_storage일 수있다. 너가 마지막에 캐스팅 할 수  있는. 그리고 이것은 목적지 IP 주소와 포트를 포함한다. tolen은 간단히 sizeof *to 또는 sizeof(struct sockaddr_Storage)로 설정되어질 수 있다.

destination address structure를 얻기 위해, 너는 아마도 getaddrinfo()로 그것을 얻거나 recvfrom() 으로 얻어진것을 얻을것이다. 또는 너는그것을 직접 채울 것이다.

send( 처럼, sendto()는 실제로 보내진 바이트 수를 반환한다. (그리고 다시, 너가 그것을 보내라고 말한 바이트의 수 보다 작을지도 모른다!), 또는 에러시에 -1을 반환한다.

동등하게, recv()와 recvfrom()은 유사하다. recvfrom()의 개요는:

  int recvfrom(int sockfd, void* buf, int len, unsigned int flags, struct sockaddr* from, int* fromlen);

또 다시, 두 개 필드가 추가된 recv() 와 같다. from은 시작하는 기계의 IP 주소와 포트로 채워질 local struct sockaddr_storage에 대한 포인터이다. fromlen은 sizeof *from 또는 sizeof(struct sockaddr_storage)로 초기화될 local int에 대한 포인터이다. 그 함수가 반환할 때, fromlen은 from에 실제로 저장되는 주소의 길이를 포함한다.

recvfrom()는 받아진 바이트의 수를 반환하고, 에러시에 -1을 반환한다. (errno도 그에 따라 설정된다.)

그래서 여기에 질문이 있따: 왜 우리는 socket type으로 struct sockaddr_storage를 사용하는가? struct sockaddr_in이 아니라? 왜냐하면, 너도 보듯이, 우리는 우리 자신을 IPv4 또는 IPv6에 묶이지 않게 하고 싶어하기 때문이다. 그래서 우리는 generic한 struct sockaddr_storage를 사용하고, 우리가 알기에 이것은 그 둘 중 하나에 대해 충분히 크다.

(그래서... 여기에 또 다른 이유가 있다: 왜 struct sockaddr은 그 자체로 어떤 주소든지에 대해 충분히 크지 않는가? 우리는 심지어 일반 목적의 struct sockaddr_storage를 일반 목적의 struct sockaddr로 캐스팅 한다. 이것은 관련 없어보이고 쓸모 없어 보인다. 답은 그것은 충분히 크지 않다는 것이고, 내가 추측하기에, 그것을 이 시점에서 바꾸는 것은 문제가 될 것이다. 그래서 그들은 새로운 것을 하나 만들었따.)

기억해라, 만약 너가 datagram socket을 connect() 한다면, 너는 그러고나서 모든 너의 트랜잭션에 대해 send()와 recv()를 간단히 사용할 수 있다. 그 소켓은 그 자체로 여전히 datagram socket이고, 그 패킷은 여전히 UDP를 사용한다, 그러나 소켓 인터페이스는 자동적으로 목적지와 소스 정보를 너를위해 추가할 것이다.

5.9. close() and shutdown() - Get outta my face
Whew! 너는 하루종일 데이터를 send()하고 recv()하고 있었고, 너는 그것을 가졌따. 너는 이제 너의 socket descriptor에 대한 connetion을 닫을 준비가 되었다. 이것은 쉽다. 너는 그냥 보통의 Unix file descriptor close() 함수를 사용할 수 있다:

  close(sockfd);

이것은 그소켓에 대해 어떤 read and write를 방지할 것이다. 원격 종단에 대해 소켓을 읽거나 쓰려고 하는 누군가는 에러를 받게 될 것이다.

너가 좀 더 socket이 어떻게 close되는지에대해 좀 더 통제력을 원하는 경우에, 너는 shutdown() 함수를 사용할 수 있다. 그것은 너가 어떤 방향에서의 communication을 끈을 수 있게 한다. 또는 두 방향 간에. (close()가 하는 것 처럼. 개요는:

  int shutdown(int sockfd, int how);

sockfd는 너가 끄기를 원하는 소켓 file descriptor이고, how는 다음중의 하나이다.

0 나중의 receives가 허용되지 않는다.
1 나중의 sends가 허용되지 않는다.
2 나중의 sends와 receives가 허용되지 않는다. (close() 처럼)

shutdown() 성공시에 0을 반환하고, 에러시에 -1을 반환한다. (errno가 그에 따라서 설정되고)

만약 unconnected datagram sockets에 대해 shutdown()을 사용하는 것을 설계한다면, 그것은 간단히 socket을 나중의 send()와 recv() calls에 대해 그 소켓이 이용가능 하지 않게 할 것이다. (만약 너의 datagram socket에 connect()한다면, 너는 이러한 것들을 사용할 수 있다는 것을 기억해라.)

shutdown()는 실제로 file descriptor를 close()하는것을 주목하는 것이 중요하다 - 그것은 그것의 사용성을 바꾼다. socket descriptor를 해제하기위해서, 너는 close()를 사용할 필요가 있다.

그것에 대해 더 이상 할 게 없다.

(Windows와 Winsock을 사용한다면, 너는 close() 대신에 closesocket()을 호출해야만 하는 것을 기억하는 것을 제외하고)

5.10. getpeername() - Who are you?
이 함수는 매우 쉽다.

그것은 너무쉽고, 나는 거의 그것에게 그것 자신의 section을 주지 않았다. 그러나 여기에 어쨋든 있다.

그 함수 getpeername()은 너에게 연결된 stream socket의 다른 쪽 끝에 누가 있는지를 말해줄 것이다. 그 개요는:

  #include <sys/socket.h>
  int getpeername(int sockfd, struct sockaddr *addr, int* addrlen);

sockfd는 연결된 stream socket의 descriptor이고, addr는 연결된 다른 쪽에 대한 정보를 담을 struct sockaddr에 대한 포인터이다. (또는 struct sockaddr_in), 그리고 addrlen은 int에 대한포인터이고, 그것은 sizeof(*addr) 또는 sizeof(struct sockaddr)로 초기화되어야 한다.

그 함수는 에러시에 -1을 반환하고 errno를 그에 따라서 설정한다.

일단 너가 그것들의 주소를 가진다면, 너는 inet_ntop(), getnameinfo() 또는 gethostbyaddr()를 더 많은 정보를 얻거나 print하기위해 사용할 수 있다. 아니, 너는 그들의 login name을 얻을 수 없다. (그래, 만약 다른 컴퓨터가 ident daemon을 돌린다면, 이것은 가능하다. 그러나 이것은 이 문서의 범위를 넘는다. 좀 더 정보를 얻으려면 RFC 1413를 체크해라.)

5.11 gethostname() - Who am I
gethostname() 함수는 getpeername()보다 심지어 더 쉽다. 그것은 너의 프로그램이 작동하고 있는 컴퓨터의 이름을 반환한다. 그 이름은 그리고 gethostbyname()에 의해 사용되어질 수 있다.  아래에서, 너의 local machine의 IP 주소를 결정하기 위해서.

무엇이 좀 더 재미있어 질 수 있을까? 나는 몇 가지 것을 생각해볼 수 있지만, 그것들은 socket programming에 관련되지 않는다. 어쨋든, 여기에 breakdown이 있다:

  #include <unistd.h>
  int gethostname(char* hostname, size_t size);

인자는 간단하다: hostname은 함수의 return에서 hostname을 포함할 char의 배열에 대한포인터이고, size는 그 hostname array의 바이트 길이이다.

그 함수는 성공적인 완료에 대해 0을 반환하고, 에러시에 -1을 반환하고, 보통 errno를 설정한다.

댓글 없음:

댓글 쓰기