Сетевое программирование си
Программирование сокетов на C / C ++
Что такое сокет программирования?
Сокетное программирование — это способ соединения двух узлов в сети для связи друг с другом. Один сокет (узел) прослушивает определенный порт с IP-адреса, а другой сокет обращается к другому, чтобы сформировать соединение. Сервер формирует сокет слушателя, в то время как клиент обращается к серверу.
Диаграмма состояний для модели сервера и клиента
Этапы для сервера
- Создание сокета:
sockfd: дескриптор сокета, целое число (как дескриптор файла)
домен: целое число, домен связи, например, AF_INET (протокол IPv4), AF_INET6 (протокол IPv6)
тип: тип связи
SOCK_STREAM: TCP (надежный, ориентированный на соединение)
SOCK_DGRAM: UDP (ненадежный, без установления соединения)
protocol: значение протокола для интернет-протокола (IP), равное 0. Это то же число, которое указывается в поле протокола в заголовке IP пакета.
Setsockopt:
Это помогает в манипулировании опциями для сокета, на который ссылается дескриптор файла sockfd. Это совершенно необязательно, но помогает в повторном использовании адреса и порта. Предотвращает ошибку, такую как: «адрес уже используется».
Bind:
После создания сокета функция bind связывает сокет с адресом и номером порта, указанными в addr (пользовательская структура данных). В примере кода мы привязываем сервер к локальному узлу, поэтому мы используем INADDR_ANY для указания IP-адреса.
Слушать:
Он переводит сокет сервера в пассивный режим, в котором он ждет, пока клиент подойдет к серверу, чтобы установить соединение. Журнал ожидания определяет максимальную длину, до которой может увеличиваться очередь ожидающих соединений для sockfd. Если запрос на подключение приходит, когда очередь заполнена, клиент может получить ошибку с указанием ECONNREFUSED.
Accept:
Он извлекает первый запрос на соединение в очереди ожидающих соединений для прослушивающего сокета, sockfd, создает новый подключенный сокет и возвращает новый дескриптор файла, ссылающийся на этот сокет. В этот момент устанавливается соединение между клиентом и сервером, и они готовы к передаче данных.
Этапы для клиента
- Сокетное соединение: точно так же, как и при создании сокета сервера
- Connect:
Системный вызов connect () соединяет сокет, указанный дескриптором файла sockfd, с адресом, указанным в addr. Адрес и порт сервера указаны в addr.
Реализация
Здесь мы обмениваемся одним приветственным сообщением между сервером и клиентом, чтобы продемонстрировать модель клиент / сервер.
// Серверная программа на C / C ++ для демонстрации программирования Socket
#include
#include
#include
#include
#include
#include
#define PORT 8080
int main( int argc, char const *argv[])
int server_fd, new_socket, valread;
struct sockaddr_in address;
int addrlen = sizeof (address);
char *hello = «Hello from server» ;
// Создание дескриптора файла сокета
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
perror ( «socket failed» );
// Принудительное подключение сокета к порту 8080
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT,
address.sin_port = htons( PORT );
// Принудительное подключение сокета к порту 8080
if (bind(server_fd, ( struct sockaddr *)&address,
perror ( «bind failed» );
if (listen(server_fd, 3)
if ((new_socket = accept(server_fd, ( struct sockaddr *)&address,
valread = read( new_socket , buffer, 1024);
printf ( «%sn» ,buffer );
send(new_socket , hello , strlen (hello) , 0 );
printf ( «Hello message sentn» );
// Клиентская программа C / C ++ для демонстрации программирования Socket
#include
#include
#include
#include
#include
#define PORT 8080
int main( int argc, char const *argv[])
int sock = 0, valread;
struct sockaddr_in serv_addr;
char *hello = «Hello from client» ;
if ((sock = socket(AF_INET, SOCK_STREAM, 0))
printf ( «n Socket creation error n» );
// Преобразование адресов IPv4 и IPv6 из текста в двоичную форму
if (inet_pton(AF_INET, «127.0.0.1» , &serv_addr.sin_addr)
printf ( «nInvalid address/ Address not supported n» );
if (connect(sock, ( struct sockaddr *)&serv_addr, sizeof (serv_addr))
printf ( «nConnection Failed n» );
send(sock , hello , strlen (hello) , 0 );
printf ( «Hello message sentn» );
valread = read( sock , buffer, 1024);
printf ( «%sn» ,buffer );
Компиляция:
gcc client.c -o клиент
gcc server.c -o сервер
Выход:
Next: Программирование на языке сокетов на C / C ++: обработка нескольких клиентов на сервере без многопоточности
Эта статья предоставлена Акшат Синха . Если вы как GeeksforGeeks и хотели бы внести свой вклад, вы также можете написать статью с помощью contribute.geeksforgeeks.org или по почте статьи contribute@geeksforgeeks.org. Смотрите свою статью, появляющуюся на главной странице GeeksforGeeks, и помогите другим вундеркиндам.
Пожалуйста, пишите комментарии, если вы обнаружите что-то неправильное или вы хотите поделиться дополнительной информацией по обсуждаемой выше теме.
Программирование сетевых приложений (TCP/IP) на C/C++
Простейшие примеры
TCP/IP
Что следует иметь ввиду при разработке с TCP
- TCP не выполняет опрос соединения (не поможет даже keep alive — он нужен для уборки мусора, а не контроля за состоянием соединения). Исправляется на прикладном уровне, например, реализацией пульсации. Причем на дополнительном соединении.
- Задержки при падении хостов, разрыве связи.
- Необходимо следить за порядком получения сообщений.
- Заранее неизвестно сколько данных будет прочитано из сокета. Может быть прочитано несколько пакетов сразу!
- Надо быть готовым ко всем внештатным ситуациям:
- постоянный или временный сбой сети
- отказ принимающего приложения
- аварийный сбой самого хоста на принимающей стороне
- неверное поведение хоста на принимающей стороне
- учитывать особенности сети функционирования приложения (глобальная или локальная)
OSI и TCP/IP
Порты
Полный список зарегистрированных портов расположен по адресу: http://www.isi.edu/in-notes/iana/assignment/port-numbers. Подать заявку на получение хорошо известного или зарегистрированного номера порта можно по адресу http://www.isi.edu/cgi-bin/iana/port-numbers.pl.
Состояние TIME-WAIT
После активного закрытия для данного конкретного соединения стек входит в состояние TIME-WAIT на время 2MSL (максимальное время жизни пакета) для того, чтобы
- заблудившийся пакет не попал в новое соединение с такими же параметрами.
- если потерялся ACK, подтверждающий закрытие соединения, с активной стороны, пассивная снова пощлёт FIN, активная, игнорируя TIME-WAIT уже закрыла соединение, поэтому пассивная сторона получит RST.
Отключение состояния TIME-WAIT крайне не рекомендуется, так как это нарушает безопасность TCP соединения, тем не менее существует возможность сделать это — опция сокета SO_LINGER.
Штатная ситуация — перезагрузка сервера может пострадать из-за наличия TIME-WAIT. Эта проблема решается заданием опции SO_REUSEADDR.
Отложенное подтверждение и алгоритм Нейгла.
Алгоритм Нейгла используется для предотвращения забивания сети мелкими пакетами и имеет очень простую формулировку — запрещено посылать второй маленький пакет до тех пор, пока не придет подтверждение на первый. Тем не менее данные отправляются при выполнении хотя бы одного из следующих условий:
- можно послать полный сегмент размером MSS (максимальный размер сегмента)
- соединение простаивает, и можно опустошить буфер передачи
- алгоритм Нейгла отключен, и можно опустошить буфер передачи
- есть срочные данные для отправки
- есть маленьки сегмент, но его отправка уже задержана на достаточно длительное время (таймер терпения persist timer на тайм-аут ретрансмиссии RTO )
- окно приема, объявленное хостом на другом конце, открыто не менее чем на половину
- необходимо повторно передать сегмент
- требуется послать ACK на принятые данные
- нужно объявить об обновлении окна
Кроме того, существует отложенное подтверждение. При этом хост, получивший сегмент, старается задержать отправку ACK, чтобы послать её вместе с данными.
Алгоритм Нейгла в купе с отложенным подтверждением в резонансе дают нежелательные задержки. Поэтому часто его отключают. Отключение алгоритма Нейгла производится заданием опции TCP_NODELAY
Но более правильным было бы проектировать приложение таким образом, чтобы было как можно меньше маленьких блоков. Лучше писать большие. Для этого можно объединять данные самостоятельно, а можно пользоваться аналогом write, работающим с несколькими буферами: