Программирование сеть tcp ip

Программирование сетевых приложений (TCP/IP) на C/C++

Полный список зарегистрированных портов расположен по адресу: 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 (максимальное время жизни пакета) для того, чтобы

  1. заблудившийся пакет не попал в новое соединение с такими же параметрами.
  2. если потерялся ACK, подтверждающий закрытие соединения, с активной стороны, пассивная снова пощлёт FIN, активная, игнорируя TIME-WAIT уже закрыла соединение, поэтому пассивная сторона получит RST.

Отключение состояния TIME-WAIT крайне не рекомендуется, так как это нарушает безопасность TCP соединения, тем не менее существует возможность сделать это — опция сокета SO_LINGER.

Штатная ситуация — перезагрузка сервера может пострадать из-за наличия TIME-WAIT. Эта проблема решается заданием опции SO_REUSEADDR.

Отложенное подтверждение и алгоритм Нейгла.

Алгоритм Нейгла используется для предотвращения забивания сети мелкими пакетами и имеет очень простую формулировку — запрещено посылать второй маленький пакет до тех пор, пока не придет подтверждение на первый. Тем не менее данные отправляются при выполнении хотя бы одного из следующих условий:

  • можно послать полный сегмент размером MSS (максимальный размер сегмента)
  • соединение простаивает, и можно опустошить буфер передачи
  • алгоритм Нейгла отключен, и можно опустошить буфер передачи
  • есть срочные данные для отправки
  • есть маленьки сегмент, но его отправка уже задержана на достаточно длительное время (таймер терпения persist timer на тайм-аут ретрансмиссии RTO )
  • окно приема, объявленное хостом на другом конце, открыто не менее чем на половину
  • необходимо повторно передать сегмент
  • требуется послать ACK на принятые данные
  • нужно объявить об обновлении окна
Читайте также:  Какие основы программирования нужно знать

Кроме того, существует отложенное подтверждение. При этом хост, получивший сегмент, старается задержать отправку ACK, чтобы послать её вместе с данными.

Алгоритм Нейгла в купе с отложенным подтверждением в резонансе дают нежелательные задержки. Поэтому часто его отключают. Отключение алгоритма Нейгла производится заданием опции TCP_NODELAY

const int on = 1; setsockopt( s, IPPROTO_TCP, TCP_NODELAY, &on, sizeof( on ) ); 

Но более правильным было бы проектировать приложение таким образом, чтобы было как можно меньше маленьких блоков. Лучше писать большие. Для этого можно объединять данные самостоятельно, а можно пользоваться аналогом write, работающим с несколькими буферами:

#include  ssize_t writev( int fd, const struct iovec *iov, int cnt ); ssize_t readv( int fd, const struct iovec *iov, int cnt ); // Возвращают число переданных байт или -1 в случае ошибки struct iovec  char *iov_base; /* Адрес начала буфера*/ size_t iov_len; /* Длина буфера*/ > 
int WSAAPI WSAsend( SOCKET s, LPWSABUF, DWORD cnt, LPDWORD sent, DWORD flags, LPWSAOVERLAPPED ovl, LPSWSAOVERLAPPED_COMPLETION_ROUTINE func ); // Возвращает 0 в случае успеха, в противном случае SOCKET_ERROR typedef struct _WSABUF  u_long len; /* Длина буфера */ char FAR * buf; /* Указатель на начало буфера */ > WSABUF, FAR * LPWSABUF; 

Таймаут при вызове connect

alarm

void alarm_hndlr( int sig )  return; > int main( int argc, char **argv )  // . signal( SIGALRM, alarm_hndlr ); alarm( 5 ); int rc = connect( s, (struct sockaddr * )&peer, sizeof( peer ) ); alarm( 0 ); if( rc  0 )  if( errno == EINTR ) error( 1, 0, "Timeout\n" ); // . > 

Способ простой, но имеет ряд проблем.

  • Таймер, используемый в вызове alarm не должен больше нигде применяться.
  • Перезапустить connect сразу не получится. Необходимо будет подождать, закрыть и заново открыть сокет.
  • Некоторые системы могут возобновлять connect

Неблокирующие соединения

Суть в том, чтобы использовать неблокирующие сокеты и следить за ними с помощью системных вызовов. Жалко только, что переносимое решение «из коробки» можно реализовать только с тупым и медленным select.

select

int main( int argc, char **argv )  struct sockaddr_in peer; INIT() // set_address( argv[1], argv[2], &peer, "tcp" ); SOCKET s = socket( AAF_INET, SOCK_STREAM, 0 ); if( !isvalidsock( s ) ) error( 1, errno, "Socket colling error" ); /* Добавляет флаг "не блокирующий" к флагам сокета (как дескриптора файла)*/ int flags = fcntl( s, F_GETFL, 0 ) ); if( flags  0 ) error( 1, errno, "Error calling fcntl(F_GETFL)" ); if( fcntl( s, F_SETFL, flags | O_NONBLOCK )  0 ) error( 1, errno, "Error calling fcntl(F_SETFL)" ); int rc = connect( s, (struct sockaddr * )&peer, sizeof( peer ) ); if( rc  0 && errno != EINPROGRESS ) error( 1, errno, "Error calling connect" ); if( rc == 0 ) // вдруг уже не надо ждать  if( fcntl( s, F_SETFL, flags )  0 ) error( 1, errno, "Error calling fcntl (flags recovery)"); client( s, &peer ); return 0; > /* Если ждать надо, ждем с помощью select'а*/ fd_set rdevents; fd_set wrevents; fs_set exevents; FD_ZERO( &rdevents ); FD_SET( s, &rdevents ); wrevents = rdevents; exevents = rdevents; struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; rc = select( s + 1, &rdevents, &wrevents, &exevents, &tv ); if( rc  0 ) error( 1, errno, "Error calling select" ); else if( rc == 0 ) error( 1, 0, "Connection timeout" ); else if( isconnected( s, &rdevents, &wrevents, &exevents ) )  if( fcntl( s, F_SETFL, flags )  0 ) error( 1, errno, "Error calling fcntl (flags recovery)" ) client( s, &peer ); > else error( 1, errno, "Error calling connect" ); return 0; > /* В UNIX и WINDOWS разные методы уведомления об успешной попытке соединения, поэтому проверка вынесена в отдельную функцию.*/ // UNIX int isconnected( SOCKET s, fd_set *rd, fd_set *wr, fd_set *ex)  int err; int len = sizeof( err ); errno = 0; /*предполагаем, что ошибки нет*/ if( !FD_ISSET( s, rd ), !FD_ISSET( s, wr ) ) return 0; if( getsockopt( s, SOL_SOCKET, SO_ERROR, &err, &len )  0 ) return 0; errno = err; /* если мы не соединились */ return err == 0; > // Windows int isconnected( SOCKET s, fd_set *rd, fd_set *wr, fd_set *ex)  WSASetLastError( 0 ); if( !FD_ISSET( s, rd ) && !FD_ISSET( s, wr ) ) return 0; if( !FD_ISSET( s, ex ) ) return 0; return 1; > 

libevent

Следует обнулять структуру sockaddr_in

Для дополнения структуры до 16 байт, в ней имеется блок sin_zero. Он должен быть нулевым.

Преобразование порядка байт

0x12345678 => 12 34 56 78 - прямой порядок (big endian) 0x12345678 => 78 56 34 12 - братный порядок (little endian)

Сетевой порядок байт всегда прямой. Для преобразования числа из порядка платформы в сетевой порядок используют hton (host to network) группу функций ( htonl , htons ). Обратно, соответственно ntoh .

Разрешение имен и служб

см. gethostbyaddr , gethostbyname , gethostbyname2 (IPv6), getservbyname , getservbyport

boost::asio

Данная библиотека берет на себя ассинхронную передачу и получение сообщений по сети. Реализуется шаблоном проектирования Proactor. На каждое событие получения ответа или отправки запроса вешается обработчик. Операции вводавывода осуществляются асинхронно, но события формируются в очередь и выполняются последовательно. Таким образом, вы получаете возможность писать код не заморачиваясь на аспектах многопоточного программирования, собирая сливки с асинхронной передачи данных по сети.

Лучше всего о boost::asio написано в документации. В качестве простого примера выступает проект «Курсор».

Количество открытых соединений

Итак, допустим мы хотим удержать максимально возможное число соединений. Сколько же это? Первым параметром, в который мы уткнёмся — число одновременно открытых файлов на процесс операционной системы.

Следующая команда меняет это число в пределах сессии:

Откуда берется константа — не знаю. В моей системе было так. Следует отметить, что для исполнения нужны специальные права.

Далее следует обратить внимание на количество оперативной памяти и иметь ввиду, что каждое открытое соединение откушает около 64Кб unswappable памяти. Таким образом, в моём случае:

python -c"print(`free | awk '/-\/+.*/ '`/64)"

эта цифра была более 243422.0625. Значит будем пробовать открыть 200000 соединений. Не совсем всё так просто. Существует целый ряд настроек в net.ipv4.tcp (/proc/sys/net/ipv4).

    tcp_mem — векторная величина (минимум, нагрузка, максимум), характеризующая общие настройки потребления памяти для протокола TCP. Измеряется в страницах (обычно страница — 4Кб). До минимума операционная система ничего не делает, при среднем — старается ограничить использование памяти. Максимум — максимальное число страниц, разрешённое для всех TCP сокетов. Так как мы замахиваемся на 200000 соединений, нам надо бы минимум (200000*64)/4 = 3200000. Зачем заставлять нервничать операционную систему.

sudo sysctl -w net.ipv4.tcp_mem="3200000 3300000 3400000"

Итоговый скрипт подстройки ОС Linux

#!/bin/bash ulimit -n 1048576 MAX_SOCKS=`python -c"print(\`free | awk '/-\/+.*/ '\`//64"` let MAX_SOCKS_MIDLE=$MAX_SOCKS+1000 let MAX_SOCKS_UP=$MAX_SOCKS+2000 sysctl -w net.ipv4.tcp_mem="$MAX_SOCKS $MAX_SOCKS_MIDLE $MAX_SOCKS_UP" sysctl -w net.ipv4.tcp_syncookies=0 sysctl -w net.ipv4.netfilter.ip_conntrack_max=1048576 sysctl -w net.ipv4.tcp_no_metrics_save=1 sysctl -w net.ipv4.somaxconn=$MAX_SOCKS sysctl -w net.ipv4.core.netdev_max_backlog=1000 sysctl -w net.ipv4.tcp_tw_recycle=0 sysctl -w net.ipv4.tcp_tw_reuse=0

Отслеживание соединений

Выше для отслеживания состояния соединения мы использовали select. К сожалению он имеет жесткое ограничение по количеству отслеживаемых элементов. Существуют другие подходы, но к сожалению они не кросс-платформенные. Можно использовать библиотеку libevent. Заявляется, что она использует максимально эффективную реализацию для данной системы, но писать придётся в событийной модели.

Ещё один интересный момент. Стек TCP/IP Linux (3.3.8) в рамках одного потока идентифицирует соединение не только по локальному адресу, но и по удаленному. Это позволяет а одном потоке использовать два сокета с одним локальным адресом и портом, но разными удаленными адресами. К сожалению, при использовании большого количества соединений возникают различные сайд-эффекты. В связи с этим придется вручную контроллировать раздачу портов для reusable сокетов.

Источник

Оцените статью