информационная безопасность
без паники и всерьез
 подробно о проектеRambler's Top100
Портрет посетителяСтрашный баг в Windows
BugTraq.Ru
Русский BugTraq
 Анализ криптографических сетевых... 
 Модель надежности двухузлового... 
 Специальные марковские модели надежности... 
 Бэкдор в xz/liblzma, предназначенный... 
 Три миллиона электронных замков... 
 Doom на газонокосилках 
главная обзор RSN блог библиотека закон бред форум dnet о проекте
bugtraq.ru / форум / programming
Имя Пароль
ФОРУМ
все доски
FAQ
IRC
новые сообщения
site updates
guestbook
beginners
sysadmin
programming
operating systems
theory
web building
software
hardware
networking
law
hacking
gadgets
job
dnet
humor
miscellaneous
scrap
регистрация





Легенда:
  новое сообщение
  закрытая нитка
  новое сообщение
  в закрытой нитке
  старое сообщение
  • Напоминаю, что масса вопросов по функционированию форума снимается после прочтения его описания.
  • Новичкам также крайне полезно ознакомиться с данным документом.
Подборка грабель, возникающих при кросс-платформенной разработке. 11.06.03 18:32  Число просмотров: 4696 [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. Тема остается открытой для тех, кому есть что добавить.

---
<programming> Поиск 
  • Подборка грабель, возникающих при кросс-платформен... - PS 11.06.03 18:32 [4696]






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


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