В этой теме собранны несколько небольших отличий при программировании под Windows, Linux и FreeBSD. Если вы внимательно читаете все темы форумов на www.bugtrack.ru, то можете дальше не читать. Все это было в других темах, здесь лишь собранно в одном месте. Так же не читайте, если Вы считаете себя крутым программером и, и так все знаете — только потеряете время. Если же вам лениво читать форумы от корки до корки, и вы первый раз взялись писать кросс-платформенное приложение, тогда, надеюсь, этот пост Вам поможет. 1. Select() В ф-ии select есть сразу две проблемы. Первая — это параметр nfds (первый параметр ф-ии select). Для Windows этот параметр не учитывается, для Unix это важный параметр который должен быть равен: максимальный дескриптор (из трех возможных fdset) плюс 1. Вторая — второй, третий и четвертый параметры — fd_set. Что представляет собой fd_set в Windows:
Это просто массив и счетчик элементов в нем. В этот массив, как и в любой другой можно положить все что угодно. Даже то, что сокетом не является. В этом случае select просто вернет ошибку. Что собой представляет fd_set в Linux и FreeBSD:
Т.е. это всего лишь массив. Заполняется он так:
Чем грозит такое отличие ? Например, под Windows можно написать следующий код:
Дальше можно делать так:
И это будет работать. В Linux и FreeBSD такой код сразу даст segmentation fault.
INVALID_SOCKET определен как -1, т.е. 0xFFFFFFFF Например в FreeBSD NFDBITS отпределен как 32 ( 4*8 ). И мы пытаемся прочесть позицию 0x07FFFFFF в fds_bits[] из 32'ух возможных ((1024 + 31)/32). Поэтому, тот же код с минимальными исправлениями для Linux и FreeBSD должен выглядеть так:
Еще в Windows FD_ISSET определен через ф-ию:
И что-то подсказывает, что эта ф-ия защищена от сбоев по памяти и прочих неприятных вещей. Кстати, 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, используйте другие ф-ии из выбранной вами библиотеки, аналогичные приведенным):
Если Вы видите надпись TH2... менее чем 20 раз — значит потоки реализованы в пространстве пользователя. Методы борьбы: переходить на pthread или не допускать зависания потоков. 5. Mutex В Windows, mutex созданый по умолчанию ведет себя рекурсивно. Т.е. сколько раз один и тот же поток вызвал для него lock, столько же раз должен быть вызван unlock. pthread_mitex, по умолчанию, ведет себя иначе. Для того что бы его поведение соответствовало Windows mutex (по умолчанию) необходимо создать его следующим образом:
6. Исключения C++ В Windows системные исключения и исключения C++ смешаны (это точно, если вы пользуетесь MS VC). Например, сбой по памяти, попытка деления на 0, и пр. ловятся с помощью catch(...). В Unix это не так (точно, если использовать gcc). В таких случаях приложению посылаются сигналы. В большинстве случаев, после такого сигнала приложение можно только закрыть. Windows код:
Unix код:
Методы борьбы: писать приложение так, что бы не допустить подобных ситуаций. P.S. Все что касается сокетов — относится к TCP соединениям. Причем считается, что все используется по умолчанию. Т.е. без SO_LINGER и т.д. P.P.S. Тема остается открытой для тех, кому есть что добавить. оригинал — http://bugtraq.ru/cgi-bin/forum.mcgi?type=sb&b=2&m=80610&id=2329&cp=5BQFsfAohTWhw |