К содержанию

Системные вызовы


getaddrinfo() — готовимся к запуску!

Эта функция — настоящая рабочая лошадка. У неё есть множество вариантов вызова, но в использовании она очень проста. Она помогает создавать структуры, необходимые в дальнейшем.

Немного истории: раньше для DNS-запросов вам необходимо было использовать функцию под названием gethostbyname(). Затем информацию нужно было вручную заносить в struct sockaddr_in, и только затем использовать её.
К счастью, в этом более нет необходимости (это даже нежелательно, если вы хотите использовать не только IPv4, но и IPv6). В наше время есть функция getaddrinfo(), которая выполняет для вас всю работу, влючая DNS & Service lookups, и заполняет структуры необходимыми данными.

Давайте посмотрим!

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>int getaddrinfo(const char *node,     // напр. «www.example.com» или IP
const char *service,  // напр. «http» или номер порта
const struct addrinfo *hints,
struct addrinfo **res);

 

Вы передаёте функции три входных параметра, а она отдаёт вам указатель на связанный список результатов.

Первый параметр — имя хоста или IP-адрес для соединения.

Следующий параметр — сервис, который может быть номером порта («80») или названием сервиса («http»). Если вы указываете сервис — он должен находиться в списке портов IANA или в файле /etc/services на unix-машине.

Наконец, третий параметр — указатель на struct addrinfo, которую вы уже заполнили минимальной информацией.

Вот пример вызова функции в случае, если вы — сервер, который хочет слушать сеть на порту 3490. Заметим, что этот код ничего не делает кроме создания структур, котрые будут использоваться в дальнейшем:

int status;
struct addrinfo hints;
struct addrinfo *servinfo;  // указатель на результатыmemset(&hints, 0, sizeof hints); // убедимся, что структура пуста
hints.ai_family = AF_UNSPEC;     // неважно, IPv4 или IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream-sockets
hints.ai_flags = AI_PASSIVE;     // заполните мой IP-адрес за меняif ((status = getaddrinfo(NULL, «3490», &hints, &servinfo)) != 0) {
fprintf(stderr, «getaddrinfo error: %sn», gai_strerror(status));
exit(1);
}// servinfo теперь указывает на связанный список на одну или больше структуру <i>addrinfo</i>// … Делаем что-то, где используем структуру <i>addrinfo</i> ….

freeaddrinfo(servinfo); // и освобождаем связанный список

 

Обратите внимание, как я назначил в ai_family AF_UNSPEC, тем самым указывая, что мне все равно, использовать ли ipv4 или ipv6. Вы можете устанавливать этот флаг в AF_INET или AF_INET6, если вам нужно указать конкретный протокол.

Кроме того, как видете, я указал флаг AI_PASSIVE. Он говорит getaddrinfo() назначить сокету адрес моего хоса. Это хорошо тем, что не нужно указывать свой адрес вручную. Иначе можно указать свой IP в качестве первого параметра getaddrinfo(), там, где у меня NULL.

Затем делаем вызов. Если есть ошибки (getaddrinfo() вернула не ноль), мы можем напечатать его, используя gai_strerror(). Если всё работает правильно, servinfo будет указывать на связанный список структур addrinfo, каждый из которых будет содержать struct sockaddr и многое другое, что мы будем использовать в дальнейшем.

Наконец, когда мы завершили все необходимые действия со связанными списками, которые getaddrinfo() так любезно нам предоставила, мы можем (и должны) освободить всё это вызовом freeaddrinfo().

Вот пример вызова, если вы клиент, который хочет подключиться к хосту «www.example.net» и порту 3490. Опять же, это на самом деле не инициирует соединение, а лишь создаёт необходимые структуры для использования в дальнейшем:

int status;
struct addrinfo hints;
struct addrinfo *servinfo;  // указатель на результаты вызоваmemset(&hints, 0, sizeof hints); // убедимся, что структура пуста
hints.ai_family = AF_UNSPEC;     // неважно, IPv4 или IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets// готовимся к соединению
status = getaddrinfo(«www.example.net», «3490», &hints, &servinfo);// servinfo теперь — указатель на связанный список с одной и более структурами addrinfo// и т.д.

 

Я всё время повторяю, что servinfo — это связанный список со всеми типами информации об адресах. Давайте набросаем быстрое демо, показывающее эту информацию. Эта короткая программа будет печатать IP-адреса для любого хоста, который вы укажете в командной строке:

/*
** showip.c — показывает IP-адрес хоста, указанного в командной строке.
*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>int main(int argc, char *argv[])
{
struct addrinfo hints, *res, *p;
int status;
char ipstr[INET6_ADDRSTRLEN];if (argc != 2) {
fprintf(stderr,«usage: showip hostnamen»);
return 1;
}memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // AF_INET или AF_INET6 для указания версии протокола
hints.ai_socktype = SOCK_STREAM;

if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
fprintf(stderr, «getaddrinfo: %sn», gai_strerror(status));
return 2;
}

printf(«IP addresses for %s:nn», argv[1]);

for(p = res;p != NULL; p = p->ai_next) {
void *addr;
char *ipver;

// получаем указатель на адрес, по разному в разных протоколах
if (p->ai_family == AF_INET) { // IPv4
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
ipver = «IPv4»;
} else { // IPv6
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = «IPv6»;
}

// преобразуем IP в строку и выводим его:
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
printf(»  %s: %sn», ipver, ipstr);
}

freeaddrinfo(res); // free the linked list

return 0;
}

 

Как видите, код вызывает getaddrinfo() на то, что вы передаёте в командной строке, и она заполняет связанные списки, указывающие на нужные ресурсы, которые мы затем можем перебирать и выводить, или делать с ними что захотим.

(немного криво то, как нужно по-разному работать в зависимости от версии IP. Извините за это, я не совсем уверен, есть ли лучший путь это организовать.)

Запускаем! Все любят скриншоты:

$ showip www.example.net
IP addresses for www.example.net:IPv4: 192.0.2.88$ showip ipv6.example.com
IP addresses for ipv6.example.com:IPv4: 192.0.2.101
IPv6: 2001:db8:8c00:22::171

 

Теперь, когда у нас всё под контролем, мы можем использовать полученные от getaddrinfo() результаты в других функциях, чтобы наконец установить наше сетевое соединение! Читайте дальше!

socket() — создаём дескриптор файла!

Думаю, хватит откладывать. Пора поговорить о системном вызове socket(). Вот разбор:

#include <sys/types.h>
#include <sys/socket.h>int socket(int domain, int type, int protocol);

 

Но что из себя представляют все эти аргументы? Они позволяют вам указать, какой тип сокета вы хотите создать (IPv4 или IPv6, TCP или UDP, потоковый или дейтаграммный).
domain — это PF_INET или PF_INET6 (PF = protocol family). type — это SOCK_STREAM или SOCK_DGRAM, а protocol может быть установлен в 0 для автоматического выбора подходящего этому семейству протокола. Ну или можно использовать getprotobyname() для получения номера нужного протокола, «tcp» или «udp».

PF_INET — это близкий родственник AF_INET, который вы использовали при инициализации поля sin_family в структуре sockaddr_in. Фактически они настолько близки, что на самом деле имеют одинаковое значение, и многие программисты взаимозаменяют их, указывая при вызове socket AF_INET вместо PF_INET. Теперь возьмите молоко и печенье, потому что настало время для экскурса в историю.
Давным-давно, много лет назад, считалось, что семейство адресов (то самое «AF» в «AF_INET») сможет поддерживать несколько протоколов, которые обьединены в одно семейство протоколов («PF» в «PF_INET»). Этого не произошло. И все они жили долго и счастливо, конец. Итак, правильнее всего использовать AF_INET в struct sockaddr_in и PF_INET в вызове socket().

В общем, хватит об этом. Всего-то и нужно — использовать значения, полученные от getaddrinfo(), скормив их вызову socket(), вот так:

int s;
struct addrinfo hints, *res;// Делаем dns-lookup
// [подразумевается, что мы уже заполнили структуру «hints»]
getaddrinfo(«www.example.com», «http», &hints, &res);// [ещё раз: вы должны проверять на ошибки результат работы getaddrinfo()
// и использовать полученный связанный список только после этого, а не тупо
// надеяться, что с ним всё в порядке и данные валидны (как делается во многих примерах).
// см. секцию о примерах клиент/серверных приложений для реальных примеров.]s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

 

socket() просто возвращает нам дескриптор сокета, который вы можете использовать в дальнейших системных вызовах. Ну или -1 при ошибке. Глобальная переменная errno в этом случае будет содержать значение ошибки (см. man errno).

Отличненько, отличненько, но что хорошего в этом сокете? Ответ прост — в самом по себе сокете ничего особенного нет, и для того, чтобы он имел смысл, нужно сделать несколько больше системных вызовов.

bind() — на каком я порту?

Создав сокет, вы можете ассициировать его с портом на вашей машине (это обычно делается, когда вы собираетесь «слушать» входящие соединения на определённом порту. Например, серверы сетевых игр именно «слушают» на порту 3490, когда вы говорите игре «connect 192.168.5.10 port 3490».) Номер порта используется ядром ОС, чтобы сопоставлять входящий пакет определённому дескриптору сокета и определённому процессу. Если вы хотите использовать только connect() (если вы клиент, а не сервер), это будет не обязательным.

Вот краткий обзор системного вызова bind():

#include <sys/types.h>
#include <sys/socket.h>int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

 

sockfd — это файловый дескриптор сокета, возвращаемый вызовом socket(). my_addr — указатель на struct soctaddr, содержащую информацию о вашем адресе, а именно — IP и порте. addrlen — это длинна адреса в байтах.

Уфф. Вот небольшой пример, обьединяющий всё это в одно целое. Пример биндит сокет на порт 3490:

struct addrinfo hints, *res;
int sockfd;// сначала заполним адресные структуры ф-ей getaddrinfo():memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;  // использовать IPv4 или IPv6, нам неважно
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;     // Заполните для меня мой IPgetaddrinfo(NULL, «3490», &hints, &res);// создаем сокет:

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

// вешаем его на порт, который мы передали в getaddrinfo():

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

 

Используя флаг AI_PASSIVE, я говорю программе биндиться на IP-адреса хоста, на котором она запущена. Если вы хотите забиндить программу на конкретный адрес, выкиньте AI_PASSIVE и передайте первым аргументом getaddrinfo() IP-адрес.

bind() точно так же возвращает -1 при ошибке и передаёт в переменную errno значение ошибки.

Во множестве старого кода struct sockaddr_in заполнялась вручную перед вызовом bind(). Очевидно, что приведённый код написан для IPv4, но ничто не мешает вам делать то же самое и для IPv6, кроме того, использование getaddrinfo() будет ещё легче. В общем, старый код выглядел примерно так:

// !!! ЭТО СТАРЫЙ СПОСОБ !!!

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, сетевой порядок байт
my_addr.sin_addr.s_addr = inet_addr(«10.12.110.57»);
memset(my_addr.sin_zero, , sizeof my_addr.sin_zero);

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

 

В этом коде вы тоже можете передать INADDR_ANY в s_add_field, это будет аналог AI_PASSIVE. Версия INADDR_ANY для IPv6 — глобальная переменная in6addr_any, назначенная полю sin6_addr структуры sockaddr_in6.
Существует также макрос IN6ADD_ANY_INIT, которую можно использовать для инициализации.

Что ещё нужно помнить при вызове bind(): не выходите за рамки, назначая номер порта. Все порты ниже 1024 — ЗАРЕЗЕРВИРОВАНЫ, и использовать их может только суперпользователь (root)! Вы можете использовать любой порт выше этого значения, вплоть до 65535 (конечно, если порт не используется другой программой).

Иногда, перезапуская сервер и, соответственно, вызов bind(), вы можете получить ошибку «Адрес уже используется» / «Address allready in use». Что это знаит? Сокеты, установившие соединение, всё ещё висят в ядре ОС, и оно держит порт занятым. Вы можете либо подождать (минуту или около того), пока ядро не освободит порт, либо добавить в программу ког, позволяющий повторно использовать порт, например так:

int yes=1;
//char yes=’1′; // вариант для Solaris// избавляемся от надоедливой ошибки «Address already in use»
if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == 1) {
perror(«setsockopt»);
exit(1);
}

 

Последнее маленькое, но важное дополнение о bind(): иногда вам абсолютно не нужно его вызывать. Если вы connect()`итесь к удалённой машине и вам не важно, каким будет ваш локальный порт, вам нужнно всего лишь вызвать connect(), оно само найдёт незанятый порт и забиндится на него, если это будет необходимо.

connect() — Эй, ты!

Давайте просто представим, что у вы — telnet-приложение. Ваш пользователь приказывает вам (совсем как в фильме TRON) получить файловый дескриптор сокета. Вы согласны и вызываете socket(). Далее, юзер говорит вам соединиться с «10.12.110.57» и портом «23» (стандартный telnet-порт.) И что вам нужно сделать?

К счастью для вас, программы, вы сейас читаете раздел, посвященный функции connect() — соединяющейся с удалённым хостом. Так что читайте! Не теряйте времени!

Вызов connect() выглядит следующим образом:

#include <sys/types.h>
#include <sys/socket.h>int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

 

sockfd — наш файловый дескриптор, который любезно вернул нам socket(). serv_addr — структура SOCKADDR, содержащая порт назначения и IP-адрес; addrlen — длинна этой структуры в байтах.
Вся эта информация может быть почерпнута из результатов вызова getaddrinfo().

Вы начинаете вникать в смысл всего этого? Я вас не слышу отсюда, поэтому просто надеюсь, что да. Давайте рассмотрим пример, в котором мы создаём подключение к «www.example.com», порту 3490:

struct addrinfo hints, *res;
int sockfd;// Сначала заполним адресные структуры с помощью getaddrinfo():memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

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

// создаём сокет:

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

// connect!

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

 

Опять же, программисты «старой школы» заполняли структуры struct sockaddr_in вручную прежде, чем передать их в connect(). Вы тоже можете это делать, если хотите. см. аналогичный пункт в разделе bind() выше.

Не забывайте проверять возвращаемое connect()`ом значение. При ошибке это будет -1, а значение ошибки, как обычно, в переменной errno.

Кроме того, обратите внимание, что мы не вызывали bind(). В данном случае нам нафиг не нужен наш локальный номер порта, нам нужен только удалённый порт, с которым мы устанавливаем соединение. Ядро само выберет для нас локальный порт и хост, с которым мы соединяемся, автоматически получит от нас эту информацию.

listen() — Кто-нибуть может мне позвонить?

Ок, теперь давайте поменяемся местами. Что если вы не хотите ни с кем соединяться. Скажем, например, вы хотите ожидать входящие соединения и как-то их обрабатывать. Этот процесс состоит из двух щагов: сначала вы слушаете (listen()), затем принимаете (accept()) соединение.

Вызов listen() довольно прост, но требует описания:

int listen(int sockfd, int backlog);

 

sockfd — это обычный дескриптор файла, полученный вызовом socket(), а backlog — число возможных соединений во входящей очереди. Что это значит? Входящиее соединения должны ожидать в очереди, пока их не примут (accept()), и это как раз лимит размера очереди ожидающих соединений. Вам скорее всего хватит в большинстве случаев хватит значения от 5 до 10.

Опять же, как обычно, listen() возвращает -1 при ошибке и присваивает errno её значение.

Как вы, возможно, уже догадались, перед вызовом listen() мы должны вызвать bind(), чтобы сервер запустился на определённом порту. (А программа-клиент должна знать этот порт, чтобы к нему подцепиться). Итак, если вы хотите прослушивать входящие соединения, последовательность вызовов будет такова:

getaddrinfo();
socket();
bind();
listen();
/* дальше идёт accept()  */

 

Я не буду писать пример полного кода, так как он довольно очевиден (в разделе accept() ниже есть более подробный код). Действительно сложная часть всего этого шамаства — это accept().

accept() — благодарим за звонок на порт 3490.

Приготовьтесь — вызов accept() довольно любопытный и странный! Что будет происходить далее: кто-то далеко-далеко будет пытаться connect() к вашей машине на порт, который вы listen(). Их соединения попадут в очередь ожидающий принятия. Вы вызываете accept() и говорите ему принять ожидающее соединение. А вызов возвращает вам совершенно новый файловый дескриптор сокета для этого конкретного соединения! Верно, теперь у вас есть два сокета по цене одного! Первый сокет всё ещё слушает новые входящие соединения, а только что созданный — наконец готов к обмену данными с помощью send() и recv()!

Вызов выглядит так:

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

 

sockfd — это слушающий сокет. Пока просто. addr — это обычно указатель на локальную структуру sockaddr_storage. В ней будет информация о входящем соединении (с её помощью вы сможете узнать, кто именно и с каким исходящим портом соединялся с вами). addrlen — локальная переменная, целое число, которое должно быть устанволено в sizeof(struct sockaddr_storage) перед передачей в accept(). accept() не поместит в структуру обьём данных больше, чем вы укажете в этой переменной. Если поместит меньше — то отразит это в значении addrlen.

Угадаете, что дальше? accept возвращает -1 и устанавливает errno в значение ошибки, если что-то пойдёт не так.

Как и ранее, обьединим всё это в один кусок, вот пример кода, описывающий всё это:

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>#define MYPORT «3490»  // порт, к которому клиенты будут подключаться
#define BACKLOG 10     // как много удерживать ожидающих соединенийint main(void)
{
struct sockaddr_storage their_addr;
socklen_t addr_size;
struct addrinfo hints, *res;
int sockfd, new_fd;

// !! не забывайте проверять все вызовы на ошибки !!

// сначала заполним адресные структуры с пом. 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, MYPORT, &hints, &res);

// создадим сокет, биндим его, и слушаем:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
bind(sockfd, res->ai_addr, res->ai_addrlen);
listen(sockfd, BACKLOG);

// теперь принимаем входящее соединение:

addr_size = sizeof their_addr;
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);

// мы готовы общаться через сокет new_fd!
.
.
.

 

Обратите внимание, что для всех вызовов send() и recv() мы будем использовать именно new_fd. Если вам нужно только одно входящее соединение, вы можете закрыть слушающий сокет после accept().

send() и recv() — поговори со мной, детка!

Эти две функции используются для коммуникации через потоковые TCP и UDP сокеты. Если вам нужно использовать обычные UDP-сокеты, не устанавливающие соединения — смотрите секцию sendto() и recvfrom().

Вызов send():

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

 

sockfd — как обычно, дескриптор сокета, через который вы хотите переслать данные. msg — указатель на данные, которые вы хотите передать; len — длинна этих данных в байтах. Флаги просто устанавливаем в 0 (см. man send для более подробной информации о флагах).

Пример кода:

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

 

send возвращает число байт, посланных фактически — это может быть число в 2 раза меньше, чем переданные вами функции данные. Например, вы можете сказать send’у отправить целую тонну данных — он просто не справится с ними за один присест. Он «выстрелит» ровно столько данных, сколько сможет, и скажет вам об этом, чтобы вы отправили остальные данные позже. Помните: если значение, возвращаемое send(), меньше значения len, это сигнал вам, что нужно отправить оставшиеся данные. Хорошая новость заключается в следующем: если пакет небольшой (менее 1К или около того) — он, вероятно, отправится за один раз. Опять же, при ошибке возвращается -1.

Вызов recv() во многом схож:

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

 

sockfd — это дескриптор сокета, из которого мы читаем данные. buf — буфер, в который мы читаем информацию, а len — максимальный размер буфера приёма. Флаги вновь могут быть установлены в 0.

recv() возвращает число бафтов, прочитанных и записанных в буфер, или -1 при ошибке.

Подождите! recv() может вернуть и 0. Это может означать только одно: удалённая сторона закрыла канал связи с вами. Значение 0, возвращённое recv(), даёт вам знать об этом.

Это было не так уж и сложно, верно? Теперь вы можете передавать данные удалённому компьютеру и получать их от него! Ураа! Ты — сетевой программист!

sendto() и recvfrom() Поговрии со мной дейтаграммами!

Как вы можете видеть, этот вызов в основе своей такой же, как и send(), с добавлением двух фрагментов информации. Это указатель на структуру SOCKADDR (которая, вероятно, будет другой структурой, sockaddr_in, sockaddr_in6 или sockaddr_storage), которая содержит адрес назначения и порт. tolen где-то глубоко в своей сути — простой int, и может быть установлен в sizeof *to или sizeof(struct sockaddr_storage).

Чтобы получить в руки структуру адреса назначения, вам возможно понадобится запустить getaddrinfo() или заполнить её руками.

Точно так же, как и send(), recv() возвращает число фактически отправленных байт (которое, опять же, может быть меньше общего числа байт, которые вы пытались передать!), или -1 при ошибке.

Столь же похожи и recv() / recvfrom(). Синтаксис recvfrom():

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

 

Опять же, так же, как и recv(), добавляется пара полей. from — указатель на локальную структуру sockaddr_storage, которая будет заполнена IP-адресом и портом удалённой машины. fromlen — указатель на локальное int, которое должно быть инициализировано как sizeof *from или как sizeof(struct sockaddr_storage). После вызова функции fromlen будет содержать размер сохранённой информации об удалённом адресе.

recvfrom() возвращает число полученных байт или -1 при ошибке.

Возникает вопрос: почему мы используем структуру sockaddr_storage как тип сокета? Почему не sockaddr_in?
Потому что мы не хотим ограничивать себя только IPv4 или только IPv6. Поэтому используем generic-структуру sockaddr_storage, о которой точно знаем, что её хватит для хранения обоих типов протоколов.

Помните, если вы используете connect() с дейтаграммным сокетом, вы можете просто использовать send() и recv() для коммуникации. Сокет сам по себе остаётся дейтаграммным и пакеты передаются по UDP, но интерфейс сокета будет автоматически добавлять информацию об адресах.

close() и shutdown() — Уйди с глаз моих!

Ура! Вы отправляете и получаете данные в течение дня, и наконец вам это надоело. Вы готовы к тому, чтобы закрыть соединение на вашем сокете. Это легко. Вы можете просто использовать сокет как обычный файловый дескриптор Unix, передав его в функцию close():

close(sockfd);

 

Это предотвратит дальнейшее чтение и передачу в сокет. Если кто-то на другом конце попытается что-то прочитать или передать в него, получит ошибку.

Просто на случай, если вы хотите получить чуть больше контроля над закрытием сокета, вы можете использовать функцию shutdown(). Она позволяет вам обрубить связь в выбранном направлении, или в обоих направлениях (так же, как это делает close()). Синтаксис:

int shutdown(int sockfd, int how);

 

sockfd — дескриптор сокета, how — одно из следующего:

0 Получение данных запрещено
1 Отправка данных запрещено
2 Любой обмен данными запрещен (аналог close())

shutdown() возвращает 0 при успехе и -1 при ошибке.

Если вы используете shutdown() на дейтаграммный сокет без установления соединений, сокет просто станет недоступен для вызовов send() / recv() (если помните, вы можете их использовать, если вызовете connect() на дейтаграммном сокете).

важно отметить, что shutdown на самом деле не закрывает дескриптор — он только отключает возможность его использования. Чтобы освободить десткриптор, по прежнему нужно вызывать close().

Добавить к этому нечего.

(Ну, кроме того, что если вы используете Windows — вместо close() вы должны использовать closesocket()).

getpeername() — ты кто такой?

Эта функция очень проста.

Она настолько проста, что я почти не писал под неё отдельный раздел. Но он всё равно тут появился 0_о

Функция getpeername() поведает тебе о том, кто находится на другом конце сокета.
Синтаксис:

#include <sys/socket.h>

int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

 

sockfd — это дескриптор потокового сокета (уже соединенного connect()`ом); addr — указатель на struct sockaddr (или на struct sockaddr_in), который и будет содержать информацию о другом конце соединения, а addrlen — указатель на int, который должен быть инициализирован ка sizeof *addr или sizeof(struct sockaddr).

Функция возвращает -1 при ошибке.

После получения адреса вы можете использовать inet_ntop(), getnameinfo() или gethostbyaddr(), чтобы вывести информацию в читаемом виде. Нет, вы не получите их логин. (О, ок. Если компьютер на другой стороне работает под управлением демона ident, возможно и это. Это, однако, выходит за рамки данного документа — за подробностями обратитесь к RFC 1413.

gethostname() — а я кто такой?

Функция ещё проще, чем gethostbyname(). Она возвращает имя компьютера, на котором запущена ваша программа. Название может быть затем использовано, например, в gethostbyname(), чтобы определить адрес вашего компьютера.

Что может быть забавнее? Много чего, но это всё не будет относиться к программированию сокетов. Так или иначе, вот синтаксис:

#include <unistd.h>

int gethostname(char *hostname, size_t size);

 

Аргументы просты: hostname — указатель на массив char, который после обработки функцией будет содержать имя хоста, и размер длинны этого массива в байтах.

Функция возвращает 0 при успехе и -1 в случае ошибки.

К содержанию