Разница в работе с сокетами Windows и Linux
От: Ondron  
Дата: 05.10.05 21:22
Оценка:
Дорогие друзья, обращаюсь к вам с просьбой о помощи.

Мною был написан сервер, принимающий и передающий информацию напрямую через сокеты.
Для совместимости с Linux я добавил в код несколько переопределений.
Все они касались WSAStartup() и WSACleanup(), которые у меня определяются только при сборке приложения под Windows.
Плюс closesocket() для сборки в Windows и close() для Linux.
Других очевидных различий я не нашёл: имена функций, параметры и их типы для Windows и Linux одинаковы.
При тестировании в Windows проблем не возникает.
А под Linux дело доходит до первого вызова функции recv(remotesock, (char *) &data, sizeof (data), 0), после чего приложение вываливается по сигфолту.

Буду благодарен за помощь в моей проблеме.
Re: Разница в работе с сокетами Windows и Linux
От: butcher Россия http://bu7cher.blogspot.com
Дата: 06.10.05 05:26
Оценка:
Здравствуйте, Ondron, Вы писали:
O>Других очевидных различий я не нашёл: имена функций, параметры и их типы для Windows и Linux одинаковы.

Есть небольшое отличие в функции select(2) + ещё нужно позаботится об обработчиках сигналов, например SIGIO, SIGPIPE, SIGURG,.. Зависит от вашей программы.

Нет ничего невозможного..
Re: Разница в работе с сокетами Windows и Linux
От: vnp  
Дата: 06.10.05 05:53
Оценка:
Здравствуйте, Ondron, Вы писали:

O>Дорогие друзья, обращаюсь к вам с просьбой о помощи.


O>Мною был написан сервер, принимающий и передающий информацию напрямую через сокеты.

O>Для совместимости с Linux я добавил в код несколько переопределений.
O>Все они касались WSAStartup() и WSACleanup(), которые у меня определяются только при сборке приложения под Windows.
O>Плюс closesocket() для сборки в Windows и close() для Linux.
O>Других очевидных различий я не нашёл: имена функций, параметры и их типы для Windows и Linux одинаковы.
O>При тестировании в Windows проблем не возникает.
O>А под Linux дело доходит до первого вызова функции recv(remotesock, (char *) &data, sizeof (data), 0), после чего приложение вываливается по сигфолту.

O>Буду благодарен за помощь в моей проблеме.


Ну пока что помощь может быть только одна: покажите минимальный код, в котором воспроизводится ошибка.
Разница в работе с сокетами Windows и Linux
От: Gomes Россия http://irazin.ru
Дата: 06.10.05 06:19
Оценка: 51 (8)
#Имя: FAQ.network.socket.winlin
В этой теме собранны несколько небольших отличий при программировании под 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
Re[2]: Разница в работе с сокетами Windows и Linux
От: Pzz Россия https://github.com/alexpevzner
Дата: 06.10.05 07:27
Оценка:
Gomes wrote:
> 3. shutdown(), close() и closesocket()
> В Windows принято, что после вызова closesocket() соединение
> закрывается. Так же, соединения закрываются при закрытии программы.
> Система знает, какому процессу соответствуют сокеты, и закрывает их при
> смерти приложения. В Linix и FreeBSD это не так.
> Необходимо явно сказать shutdown (посылка FIN) и close (разъединение
> дескриптора и сокета). Если этого не сделать, то после закрытия
> приложения сокеты еще будут висеть в системе некоторое время, в течение
> которого bind на "занятые" порты будет возвращать ошибку.
> Такая же ситуация возникает если вы сделаете на серверной стороне
> приложения shutdown и close, закроете приложение, а на клиентской
> стороне эти ф-ии вызваны не будут. После этого Вы не сможете запустить
> серверное приложение в течении некоторого времени.
> Методы борьбы — не известны. Только ждать.

Метод борьбы — SO_REUSEADDR при открытии соцкета. Причем в виндах и в
унихе эта опция делает разные штуки. В унихе она позволяет занать
TCP-порт, который уже занят другим сокетом в состоянии TIME_WAIT, i не
более того. Т.е. все же "нормальных" сокетов у Вас все равно будет не
более одной штуки на порт. В виндах же Вы, с помощью этой опции, сможете
повесить на один порт сколько угодно нормальных сокетов. Как уж они
будут потом делить этот порт между собой, науке (т.е., мне) неизвестно.
Posted via RSDN NNTP Server 1.9
Re[3]: Разница в работе с сокетами Windows и Linux
От: wellwell Австралия https://www.softperfect.com
Дата: 06.10.05 07:42
Оценка:
"Pzz" <43064@users.rsdn.ru> wrote in message news:1421249@news.rsdn.ru...
> повесить на один порт сколько угодно нормальных сокетов. Как уж они
> будут потом делить этот порт между собой, науке (т.е., мне) неизвестно.

Просто все приходящие данные будут продублированы на оба сокета с независимыми буферами.
Posted via RSDN NNTP Server 1.9
Re[2]: Разница в работе с сокетами Windows и Linux
От: butcher Россия http://bu7cher.blogspot.com
Дата: 06.10.05 07:51
Оценка:
G>Еще в Windows FD_ISSET определен через ф-ию:
G>#define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))
G>И что-то подсказывает, что эта ф-ия защищена от сбоев по памяти и прочих неприятных вещей.

int FAR PASCAL
__WSAFDIsSet(
    SOCKET fd,
    fd_set FAR *set)
/*++
Routine Description:

    Determines if a specific socket is a contained in an FD_SET.

Arguments:

    s - A descriptor identifying the socket.

    set - A pointer to an FD_SET.
Returns:

    Returns TRUE if socket s is a member of set, otherwise FALSE.

--*/
{
    int i = set->fd_count; // index into FD_SET
    int rc=FALSE; // user return code

    while (i--){
        if (set->fd_array[i] == fd) {
            rc = TRUE;
        } //if
    } //while
    return (rc);
} // __WSAFDIsSet


(c) Microsoft Co

Нет ничего невозможного..
Re[3]: Разница в работе с сокетами Windows и Linux
От: butcher Россия http://bu7cher.blogspot.com
Дата: 06.10.05 08:05
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Метод борьбы — SO_REUSEADDR при открытии соцкета. Причем в виндах и в

Pzz>унихе эта опция делает разные штуки. В унихе она позволяет занать
Pzz>TCP-порт, который уже занят другим сокетом в состоянии TIME_WAIT, i не
Pzz>более того. Т.е. все же "нормальных" сокетов у Вас все равно будет не
Pzz>более одной штуки на порт. В виндах же Вы, с помощью этой опции, сможете
Pzz>повесить на один порт сколько угодно нормальных сокетов. Как уж они
Pzz>будут потом делить этот порт между собой, науке (т.е., мне) неизвестно.

SO_REUSEPORT в большей степени похожа на Windows'овскую SO_REUSEADDR.
% man setsockopt
...
     SO_REUSEADDR indicates that the rules used in validating addresses sup-
     plied in a bind(2) system call should allow reuse of local addresses.
     SO_REUSEPORT allows completely duplicate bindings by multiple processes
     if they all set SO_REUSEPORT before binding the port.  This option per-
     mits multiple instances of a program to each receive UDP/IP multicast or
     broadcast datagrams destined for the bound port.
...

MSDN:

SO_REUSEADDR: Allows socket to bind to an address and port already in use. The SO_EXCLUSIVEADDRUSE option can prevent this. Also, if two sockets are bound to the same port the behavior is undefined as to which port will receive packets.


Нет ничего невозможного..
Re[3]: Разница в работе с сокетами Windows и Linux
От: Gomes Россия http://irazin.ru
Дата: 06.10.05 08:44
Оценка: :)
Здравствуйте, butcher, Вы писали:

B>(c) Microsoft Co


Ну, значит "что-то" ошибается
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.