информационная безопасность
без паники и всерьез
 подробно о проектеRambler's Top100
Сетевые кракеры и правда о деле ЛевинаПортрет посетителя
BugTraq.Ru
Русский BugTraq
 Анализ криптографических сетевых... 
 Модель надежности двухузлового... 
 Специальные марковские модели надежности... 
 Бэкдор в xz/liblzma, предназначенный... 
 Три миллиона электронных замков... 
 Doom на газонокосилках 
главная обзор RSN блог библиотека закон бред форум dnet о проекте
bugtraq.ru / форум / programming
Имя Пароль
ФОРУМ
если вы видите этот текст, отключите в настройках форума использование JavaScript
регистрация





Легенда:
  новое сообщение
  закрытая нитка
  новое сообщение
  в закрытой нитке
  старое сообщение
  • Напоминаю, что масса вопросов по функционированию форума снимается после прочтения его описания.
  • Новичкам также крайне полезно ознакомиться с данным документом.
Подборка грабель, возникающих при кросс-платформенной разработке. 11.06.03 18:32   [Ktirf]
Автор: PS <PS> Статус: Elderman
<"чистая" ссылка>
В этой теме собранны несколько небольших отличий при программировании под Windows, Linux и FreeBSD.
Если вы внимательно читаете все темы форумов на www.bugtrack.ru, то можете дальше не читать. Все это было
в других темах, здесь лишь собранно в одном месте.
Так же не читайте, если Вы считаете себя крутым программером и, и так все знаете - только потеряете время.
Если же вам лениво читать форумы от корки до корки, и вы первый раз взялись писать кросс-платформенное приложение,
тогда, надеюсь, этот пост Вам поможет.

1. Select()
В ф-ии select есть сразу две проблемы.
Первая - это параметр nfds (первый параметр ф-ии select). Для Windows этот параметр не учитывается,
для Unix это важный параметр который должен быть равен: максимальный дескриптор (из трех возможных
fdset) плюс 1.
Вторая - второй, третий и четвертый параметры - fd_set.
Что представляет собой fd_set в Windows:

typedef struct fd_set {
  u_int    fd_count;                 // how many are SET? 
  SOCKET   fd_array[FD_SETSIZE];     // an array of SOCKETs 
} fd_set;

Это просто массив и счетчик элементов в нем. В этот массив, как и в любой другой можно положить
все что угодно. Даже то, что сокетом не является. В этом случае select просто вернет ошибку.

Что собой представляет fd_set в Linux и FreeBSD:
typedef struct fd_set {
        fd_mask fds_bits[howmany(FD_SETSIZE, NFDBITS)];
} fd_set;

Т.е. это всего лишь массив. Заполняется он так:
#define FD_SET(n, p)    ((p)->fds_bits[(n)/NFDBITS] |= _fdset_mask(n)) 

Чем грозит такое отличие ? Например, под Windows можно написать следующий код:

SOCKET myset[1024];
for( int i  = 0; i < 1024; i++ )
	myset[i] = INVALID_SOCKET; // А чем еще инициализировать неинициализированные переменные ?

Дальше можно делать так:

if( (ret = select( 0, &fdread, 0, 0, &tv )) > 0 )
{
	for( int i = 0; i < 1024; i++ )
		if( FD_ISSET( myset[i], &fdread ) )
			// do something
}
И это будет работать.
В Linux и FreeBSD такой код сразу даст segmentation fault.

#define FD_ISSET(n, p)  ((p)->fds_bits[(n)/NFDBITS] & _fdset_mask(n)) 

INVALID_SOCKET определен как -1, т.е. 0xFFFFFFFF

Например в FreeBSD NFDBITS отпределен как 32  ( 4*8 ). И мы пытаемся прочесть позицию 0x07FFFFFF
в fds_bits[] из 32'ух возможных ((1024 + 31)/32).

Поэтому, тот же код с минимальными исправлениями для Linux и FreeBSD должен выглядеть так:
if( (ret = select( 0, &fdread, 0, 0, &tv )) > 0 )
{
	for( int i = 0; i < 1024; i++ )
		if( myset[i] != INVALID_SOCKET )
			if( FD_ISSET( myset[i], &fdread ) )
				// do something
}

Еще в Windows FD_ISSET определен через ф-ию:
#define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))
И что-то подсказывает, что эта ф-ия защищена от сбоев по памяти и прочих неприятных вещей.

Кстати, INVALID_SOCKET для Linux и FreeBSD надо определять самому.

2. send()
Первая проблема - это SIGPIPE. Сигнал, который посылается Unix системой приложению, если то
пытается послать данные в сокет, соединение которого уже разорвано. В Windows в таком случае
будет возвращена одна из ошибок. Есть два метода борьбы с этим сигналом. Первый - пригодный
для Linux - установка флага (четвертый параметр ф-ии send() ) в MSG_NOSIGNAL. Второй, пригодный
для Linux и FreeBSD - установка обработчика сигнала, для SIGPIPE. Сам обработчик ничего не делает,
просто при выходе из него программа продолжается дальше, а по ошибке, возвращаемой send, можно судить
о разрыве соединения.
Вторая проблема - невозможность узнать, были ли реально отправлены данные. Теоретически это проблема
не кросс-платформенного кода. В MSDN сказано: The successful completion of a send does not indicate that the data was successfully delivered.
Но практически, я ни разу не сталкивался с таким под Windows, и сразу столкнулся делая порт под FreeBSD.
Поэтому опишу проблему и решение здесь.
Разрыв соединения о котором не знают обе(или одна) стороны. Например, ваша программа под FreeBSD послала данные,
ей вернулась ошибка 13 icmp - сокет будет принимать следующие данные для отправки, еще в течении некоторого времени.
Они будут буфферизированы в исходящей очереди, и в конце концов утеряны, при закрытии соединения системой.
Один из способов борьбы - это поставить сокет в select(), во второй параметр (readfds). При возврате положительного числа
попробовать прочесть 1 байт. Если recv вернет 0 - значит соединение было разорвано. (если вы не хотите читать из сокета,
вызовите recv с флагом MSG_PEEK).
Может возникнуть ситуация когда данный метод не сработает. Например, FIN не доставлен,
select с readfd вернет 0, а отправить нам пока нечего. О том что соединение разорвано мы так и не узнаем, пока не сделаем
send (а он может, в структуре приложения, не сделан никогда). Параметр SO_KEEPALIVE мало пригоден. Для Linux, по умолчанию,
детектирование соединения происходит раз в час, таймаут ожидания ответа - 15 минут. Т.е. 1 час 15 минут система будет считать
что соединение есть.
Единственный способ борьбы - это реализация своего протокола подтверждения доставки пакета.

3. shutdown(), close() и closesocket()
В Windows принято, что после вызова closesocket() соединение закрывается. Так же, соединения закрываются при закрытии программы.
Система знает, какому процессу соответствуют сокеты, и закрывает их при смерти приложения. В Linix и FreeBSD это не так.
Необходимо явно сказать shutdown (посылка FIN) и close (разъединение дескриптора и сокета). Если этого не сделать, то после закрытия
приложения сокеты еще будут висеть в системе некоторое время, в течение которого bind на "занятые" порты будет возвращать ошибку.
Такая же ситуация возникает если вы сделаете на серверной стороне приложения shutdown и close, закроете приложение, а на клиентской
стороне эти ф-ии вызваны не будут. После этого Вы не сможете запустить серверное приложение в течении некоторого времени.
Методы борьбы - не известны. Только ждать.

4. Потоки.
В Windows есть только один механизм потоков, и он реализван в ядре. В Unix реализации потоков могут быть различны. Некоторые реализованны
в пространстве ядра (posix thread для Linux и FreeBSD), другие могут быть в пространстве пользователя (у Рихтера они названы fibers).
Необходимо четко представлять чем Вы пользуетесь.
Потоки, реализованные в пространстве пользователя, имеют одну неприятную особенность - система о них не знает. Значит планировщик системы
никогда не передаст управление другому потоку. Только процессу. Если же такой поток в процессе завис, то зависло и все приложение.
Простейший тест для определения в каком пространстве реализованы потоки (использован pthread, используйте другие ф-ии из выбранной вами библиотеки,
аналогичные приведенным):

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>   
#include <signal.h>              
#include <stdio.h>
             
pthread_t th1;
pthread_t th2; 

void* TH1(void* )
{
	printf( "TH1 %X\n", pthread_self() );
	while( true );
}

void* TH2(void* )
{
	int i;
	printf( "TH2 %X\n", pthread_self() );
	for( i  = 0; i < 10; i++ )
		printf( "TH2 %X\n", pthread_self() );
	sleep(5);
	for( i  = 0; i < 10; i++ )
		printf( "TH2 %X\n", pthread_self() );
}

int main( int argc, char** argv )
{
	printf( "Main thread: %X\n", pthread_self() );

	pthread_create( &th1, 0, TH1, 0 );
	pthread_create( &th2, 0, TH2, 0 );

	sleep( 600 );
}

Если Вы видите надпись TH2... менее чем 20 раз - значит потоки реализованы в пространстве пользователя.
Методы борьбы: переходить на pthread или не допускать зависания потоков.

5. Mutex
В Windows, mutex созданый по умолчанию ведет себя рекурсивно. Т.е. сколько раз один и тот же поток вызвал для него lock, столько же раз
должен быть вызван unlock.
pthread_mitex, по умолчанию, ведет себя иначе. Для того что бы его поведение соответствовало Windows mutex (по умолчанию) необходимо
создать его следующим образом:

pthread_mutexattr_t	attrs;
pthread_mutexattr_init(&attrs);
pthread_mutexattr_settype(&attrs, PTHREAD_MUTEX_RECURSIVE);		
pthread_mutex_init( &m_Mutex, &attrs );
	
6. Исключения C++
В Windows системные исключения и исключения C++ смешаны (это точно, если вы пользуетесь MS VC). Например, сбой по памяти, попытка деления на 0, и пр.
ловятся с помощью catch(...). В Unix это не так (точно, если использовать gcc). В таких случаях приложению посылаются сигналы.
В большинстве случаев, после такого сигнала приложение можно только закрыть.
Windows код:
try
{
  char buffer[1024];
  strcpy( buffer, str );
}
catch(...)
{
  // Внутреняя ошибка. Ну и черт с ней. Работаем дальше.
}

Unix код:
try
{
  char buffer[1024];
  strcpy( buffer, str );
}
catch(...)
{
  // Здесь мы никогда не будем. И скорей всего, приложение уже закрыто. Премии мне не видать.
}

Методы борьбы: писать приложение так, что бы не допустить подобных ситуаций.


P.S. Все что касается сокетов - относится к TCP соединениям. Причем считается, что все используется по умолчанию. Т.е. без SO_LINGER и т.д.
P.P.S. Тема остается открытой для тех, кому есть что добавить.

---
Круто! В faqmakers! 11.06.03 18:44  
Автор: Ktirf <Æ Rusakov> Статус: Elderman
<"чистая" ссылка>
Только не надо все в pre делать, читать неудобно :)
1




Rambler's Top100
Рейтинг@Mail.ru


  Copyright © 2001-2024 Dmitry Leonov   Page build time: 0 s   Design: Vadim Derkach