Skip to content

Latest commit

 

History

History
197 lines (128 loc) · 8.79 KB

File metadata and controls

197 lines (128 loc) · 8.79 KB

socket() - получение файлового дескриптора

Системный вызов socket() имеет следующую сигнатуру:

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

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

Аргументы позволяют указать какой тип сокета нужен - IPv4 / IPv6 , UPD или TCP.

Эти значения аргументов могут передаваться напрямую из результатов функции getaddrinfo():

struct addrinfo hints;
struct addrinfo* res;

// заполняем hints...
// ...

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

// проверяем ошибки и итерируем linked-list res...
// ...

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

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

В случае ошибки выставляется errno, и можно вывести ее с помощью вызова perror().


bind() - привязка к порту

Как только у вас появился сокет (дескриптор файла), вы можете связать его с конкретным портом на вашем локальном устройстве.

Обычно это делается, чтобы прослушивать входящие подключения, через функцию listen().

Номер порта используется ядром ОС, чтобы найти соответвствующую программу, которой нужно переправить сетевой пакет. То есть, номер порта - это идентификатор программы, которая получает сетевой пакет.

Для клиентских приложений, которые только соеденяются (через функцию connect()), привязка через bind() не требуется.

Сигнатура функции bind():

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

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

Пример программы, которая привязывает сокет к хосту, на котором запущенна программа,в порт 3490:

struct addrinfo hints;
struct addrinfo* res;

memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // IPv4 или IPv6, любой.
hints.ai_socktype = SOCK_STREAM; // TCP
hints.ai_flags = AI_PASSIVE; //заполнить IP автоматически

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

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

int bind_res = bind(sockfd, res->ai_addr, res->ai_addrlen);

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

В случае ошибки bind() выставляется errno, и можно вывести ее с помощью вызова perror().

connect() - подключение

Сигнатура вызова connect():

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

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

Пример соединениния, с использованием getaddrinfo() :

struct addrinfo hints;
struct addrinfo* res;

memset(&hints, 0, sizeof(hints));

hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

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

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

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

Обратите внимание, что не используется системный вызов bind(), потому что это клиентская программа, и ей нет дела до локального порта, она только подключается, а не прослушивает подключения.

В случае ошибки connect() выставляется errno, и можно вывести ее с помощью вызова perror().

listen() - прослушивание подключений

Если вам нужно просто ожидать внешний подключений и обрабатывать их, то обработка идет следующими системными вызовами:

  • listen()
  • accept()

listen() имеет следующую сигнатуру:

int listen(int sockfd, int backlog);
  • int sockfd - файловый дескриптор вашего сокета из вызова socket()

  • int backlog - количество соединений, доступных для входящей очереди. На практике, это означает, что входящие подключения будут попадать в эту очередь, пока вы не вызовете accept(). А очередь сама ограничена данным количеством. Условно, если лимит стоит 20, то только 20 подключений могут находится в этой очереди, не более.

В случае ошибки listen() выставляется errno, и можно вывести ее с помощью вызова perror().

Итак, если вы собираетесь прослушивать входящие подключения, то порядок системных вызовов будет такой:

  1. getaddrinfo()
  2. socket()
  3. bind()
  4. listen()
  5. accept()

accept() - принятие подключений

Системный вызов accept() возвращает новый сокет, с которым уже можно взаимодействовать через чтение и запись (recv() и send()). Можно сказать, что это сокет для конкретного клиента, который к вам подключился.

Сигнатура:

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

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

Аргументы:

  1. int sockfd - дескриптор прослушивающего сокета, созданного ранее.

  2. struct sockaddr* addr - обычно указатель на локальный struct sockaddr_storage. В нем будет информация о подключившемся пире - его адрес, порт.

  3. socklen_t* addrlen - размер объекта адреса, обычно через sizeof(struct sockaddr_storage).

В случае ошибки accept() выставляется errno, и можно вывести ее с помощью вызова perror().

Пример прослушивания и обработки подключений:

#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#define MYPORT "3490" // порт, которому будут подключаться пользователи
#define BACKLOG 10 // сколько ожидающих подключения может быть в очереди

int main(void)
{
    struct addrinfo hints;
    struct addrinfo* res;
    
    /* !!! Не забываем обрабатывать ошибки системных вызовов! Здесь это будет пропущено */

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC; // IPv4 или IPv6 без разницы
    hints.ai_socktype = SOCK_STREAM; // TCP
    hints.ai_flags = AI_PASSIVE; // заполнить мой IP за меня

    getaddrinfo(NULL, MYPORT, &hints, &res);

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

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

    // принимаем входящие подключения
    struct sockaddr_storage their_adr;
    socklen_t addrsize = sizeof(their_addr);
    int client_fd = accept(sockfd, (struct sockaddr*)&their_addr, &addr_size);

    // Готовы взаимодействовать с сокетом client_fd, писать и читать из него:
    // ....
}