Categories: C/C++

network-application-mistakes/

Очень многие программисты при написании сетевых приложений на C/C++ по неопытности допускают одни и те же ошибки. Даже несмотря на то, что эти ошибки, казалось бы, довольно известны. Поэтому сегодня мне хотелось бы в очередной раз поднять этот несколько баянистый топик, в надежде, что заметка поможет уменьшить hit ratio соответствующих граблей.

Итак, первая проблема заключается в обработке сигнала SIGPIPE. Этот сигнал приходит приложению, например, в случае, когда оно пытается сделать send в сокет, уже закрытый на стороне клиента. По умолчанию этот сигнал убивает приложение, что часто не является тем, чего хочет программист (если он пишет свое приложение на трэдах , а не процессах). Решить проблему можно так:

/*
* If client will close a connection send() will just return -1
* instead of killing a process with SIGPIPE.
*/

void HttpServer :: _ignoreSigpipe ( ) {
sigset_t msk ;
sigemptyset ( & msk ) ;
sigaddset ( & msk, SIGPIPE ) ;
if ( pthread_sigmask ( SIG_BLOCK, & msk, nullptr ) ! = 0 )
throw std :: runtime_error ( «pthread_sigmask() call failed» ) ;
}

Две другие проблемы связаны с обработкой возвращаемых значений от recv и send (или read и write , смотря что вы используете).

Во-первых, эти вызовы могут возвращать ошибку и устанавливать errno в значение EINTR в случае, если вызов был прерван пришедшим программе сигналом до того, как были посланы или приняты какие-либо данные. Это совершенно штатная ситуация, и в этом случае обычно нужно просто повторить вызов.

Во-вторых, ни один из вызовов не гарантирует, что пошлет или примет ровно столько байт, сколько вы указали третьим аргументом. То есть, за один вызов может быть принято или послано как ровно столько байт, сколько вы хотели, так и меньше. Часто эта особенность приводит к неприятной ситуации, когда код отлично работает у программиста на ноутбуке, но неожиданно ломается на сервере.

Правильный код будет выглядеть как-то так:

void Socket :: read ( char * buff, size_t buffsize ) {
while ( buffsize > 0 ) {
ssize_t res = :: read ( _fd, buff, buffsize ) ;
if ( res == 0 ) {
throw std :: runtime_error ( «client closed connection» ) ;
} else if ( res < 0 ) {
if ( errno == EINTR )
continue ;
throw std :: runtime_error ( «read() failed» ) ;
}
buff + = res ;
buffsize = res ;
}
}

void Socket :: write ( const char * buff, size_t buffsize ) {
while ( buffsize > 0 ) {
ssize_t res = :: write ( _fd, buff, buffsize ) ;
if ( res <= 0 ) {
if ( errno == EINTR )
continue ;
throw std :: runtime_error ( «write() failed» ) ;
}

buff + = res ;
buffsize = res ;
}
}

Это, собственно, и есть те ошибки, о которых я хотел рассказать. Само собой разумеется, существуют и куда более глубоководные грабли. Например, при реализации собственных протоколов обычно не лишено смысла предусмотреть посылку пингов клиенту. Нужно это для своевременного закрытия повисших соединений, которые образуются как из-за «умного» прокси между клиентом и сервером, кэширующего соединения (часто встречается в случае HTTP), так и тупо из-за бага в клиентском коде. Еще неплохой идеей будет предусмотреть собственные контрольные суммы. Было больше одной истории с багами в маршрутизаторах — например, в редких случаях они портили принятые данные, а затем вычисляли контрольные суммы в IP-пакетах от испорченных данных (см также тынц и тынц ). Однако это все уже темы для других постов.

А какие типичные ошибки вы бы добавили к приведенному списку?

admin

Share
Published by
admin
Tags: C/C++

Recent Posts

Консоль удаленного рабочего стола(rdp console)

Клиент удаленного рабочего стола (rdp) предоставляет нам возможность войти на сервер терминалов через консоль. Что…

2 месяца ago

Настройка сети в VMware Workstation

В VMware Workstation есть несколько способов настройки сети гостевой машины: 1) Bridged networking 2) Network…

2 месяца ago

Логи брандмауэра Windows

Встроенный брандмауэр Windows может не только остановить нежелательный трафик на вашем пороге, но и может…

2 месяца ago

Правильный способ отключения IPv6

Вопреки распространенному мнению, отключить IPv6 в Windows Vista и Server 2008 это не просто снять…

2 месяца ago

Ключи реестра Windows, отвечающие за параметры экранной заставки

Параметры экранной заставки для текущего пользователя можно править из системного реестра, для чего: Запустите редактор…

2 месяца ago

Как управлять журналами событий из командной строки

В этой статье расскажу про возможность просмотра журналов событий из командной строки. Эти возможности можно…

2 месяца ago