В этой теме собранны несколько небольших отличий при программировании под 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. Тема остается открытой для тех, кому есть что добавить.

оригинал — http://bugtraq.ru/cgi-bin/forum.mcgi?type=sb&amp;b=2&amp;m=80610&amp;id=2329&amp;cp=5BQFsfAohTWhw
Автор: Gomes    Оценить