Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Сети > Ошибка accept()


Автор: VitaminCPP 16.1.2008, 23:16
Есть сервер, есть клиент. Дескриптор сокета хранится в глобальной переменной sock. Вообще, куча глобальных данных, так надо. Если видите переменную и она не обьявлена в коде, значит глобальная. Сервер и клиент запускаются в новом потоке. 

Сервер:

Код

// Creates socket and sets it to be a listening socket
// Если пользователь в диалоге указал интерфейс, его адресс хранится в Address 
// и соответсвенно этот инферсейс используется, иначе INADDR_ANY
void SetUpListener()
{
    u_long nInterfaceAddr = INADDR_ANY;

    if (Address!=0 && 
        (nInterfaceAddr = inet_addr(Address)) == INADDR_NONE) 
    { sock = INVALID_SOCKET; return; }

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock != INVALID_SOCKET)
    {
        sockaddr_in sinInterface;
        sinInterface.sin_family = AF_INET;
        sinInterface.sin_addr.s_addr = nInterfaceAddr;
        sinInterface.sin_port = htons(Port);
            
        if (bind(sock, (sockaddr*)&sinInterface, 
            sizeof(sockaddr_in)) !=    SOCKET_ERROR) 
            listen(sock, 1);
    }
}

// The main Server function, calls other functions written above
DWORD WINAPI DoServer (LPVOID lParam)
{
    StatusText(0, "Establishing the listener...");
    SetUpListener();
    if (sock == INVALID_SOCKET) 
    {
        StatusText(0, "Failed to establish the listener");
        return 1;
    }

    StatusText(0, "Waiting for a connection...");
    sockaddr_in sinRemote;
    int nAddrSize = sizeof(sinRemote);
    sock = accept(sock, (sockaddr*)&sinRemote, &nAddrSize);
    if (sock != INVALID_SOCKET)
    {
        StatusText(0, "Connection accepted!");
        std::stringstream buf; 
        buf << "Playing with " << inet_ntoa(sinRemote.sin_addr) 
            << ":" << ntohs(sinRemote.sin_port);
        StatusText(1, buf.str().c_str());
    } else {
        StatusText(0, "Failed accept connection");
        return 1;
    }

    return 1;
}
// этот код вызывается из другого места, не буду вдаваться в подробности проекта.
// важно, что я его вызываю перед повторным созданием сервера.
void ShutDownConnection()
{
    if (shutdown(sock, SD_SEND) == SOCKET_ERROR) return;

    if (closesocket(sock) == SOCKET_ERROR) return;
    return;
}


Клиент:

Код

void EstablishConnection(u_long RemoteAddr)
{
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock != INVALID_SOCKET) 
    {
        sockaddr_in sinRemote;
        sinRemote.sin_family = AF_INET;
        sinRemote.sin_addr.s_addr = RemoteAddr;
        sinRemote.sin_port = htons(Port);

        if (connect(sock, (sockaddr*)&sinRemote, sizeof(sockaddr_in)) ==
            SOCKET_ERROR) sock = INVALID_SOCKET;
    }
}

DWORD WINAPI DoClient (LPVOID lParam)
{
    // Getting host address
    u_long RemoteAddr = inet_addr(Address);
    if (RemoteAddr == INADDR_NONE)
    {
        StatusText(0, "Wrong IP-address or port!");
        return 1;
    }

    // Establish connection to the host
    in_addr Address;
    memcpy(&Address, &RemoteAddr, sizeof(u_long));
    std::stringstream buf;
    buf << "Connecting to " << inet_ntoa(Address) << ":" << Port;
    StatusText(0, buf.str().c_str());

    EstablishConnection(RemoteAddr);
    if (sock == INVALID_SOCKET)
    {
        StatusText(0, "Connection to server failed!");
        return 1;
    }
    buf.str("");
    buf << "Playing with " << inet_ntoa(Address) << ":" << Port;
    StatusText(1, buf.str().c_str());
    StatusText(0, "Connected!");

    return 1;
}

//Код закрытия сокета см. у сервера, для клиента вызыается тот же код.


Вобщем, проблема в следующем:
Я запускаю сервер, подключаюсь к нему клиентом через localhost. В глобальной сети программа не тестировалась. Потом, если я закрываю сокеты у клиента и сервера и пытаюсь подключить сокет сервера снова к тому же порту (тоесть запускаю сервер заново), получаю в статус баре строку Failed accept connection(см. вызов accept() у сервера). Код ошибки WSAEINVAL (10022). Другие порты работают. Если я не подключаю клиент, а просто создаю подключение сервера к порту, удаляю его и снова создаю, всё работает.

Windows XP, SP2
Visual Studio 2008

Как мне освобождать порт?

Автор: onsql 16.1.2008, 23:38
Ну, вообще говоря, в функции void SetUpListener() никак не сигнализируется результат выполнения bind и listen. Могу предположить что во второй запуск bind возвращает что то типа address already in use.

Автор: VitaminCPP 17.1.2008, 00:20
Почему не сигнализируется? Есть ведь проверка на SOCKET_ERROR в самой функции и на INVALID_SOCKET после вызова.
Я файрволом проверил... Он показывает, что программа закрывает сокет на самом деле. Когда запускаю сервер показывает что порт открыт, когда вызываю функцию - что закрыт... Блин, где же проблема...

Автор: onsql 17.1.2008, 00:52
В упор не вижу где тут печать об ошибке если bind вернет SOCKET_ERROR
Код

...
    if (sock != INVALID_SOCKET)
    {
        sockaddr_in sinInterface;
        sinInterface.sin_family = AF_INET;
        sinInterface.sin_addr.s_addr = nInterfaceAddr;
        sinInterface.sin_port = htons(Port);
            
        if (bind(sock, (sockaddr*)&sinInterface, 
            sizeof(sockaddr_in)) !=    SOCKET_ERROR) 
            listen(sock, 1);
    }
...


Добавлено через 1 минуту и 8 секунд
Файрвол - это хорошо. А он показывает TCP соединения в состоянии FIN_WAIT, CLOSE_WAIT?

Автор: Олег2005 17.1.2008, 11:39
3 замечания:
 listen(sock, 1); - может вызывать проблемы
backlog не ставят 1 - может неверно формироваться очередь принятых соединений
 listen(sock, 5); - это минимально для всех видов стека
второе:
ShutDownConnection()
Зря там такой код - нужно просто делать closesocket()
Третье:
Для того чтобы выключать и быстро включать сервер без проблем надо на сервере для прослушивающего сокета ставить опцию SO_REUSEADDR

Автор: onsql 17.1.2008, 12:05
Если посмотреть на код в самом первом посте, то, думаю, тут происходит следующее:
1. Вызов DoServer. Из него первым делом вызывается SetUpListener()
2. В SetUpListener создается сокет, допустим успешно, переменная sock проинициализирована, не равна INVALID_SOCKET
3. В SetUpListener Вызывается bind, который, допустим, возвращает ошибку address already in use. В этом случае, listen не будет вызван. Никакой печати об этом нет, никаких действий не предпринимается.
4. Далее выход из SetUpListener, возврат в DoServer где проверяется sock на значение INVALID_SOCKET. Но оно в порядке, сокет создан, однако bind на требуемый порт не сделан, listen не вызван.
5. Далее по коду DoServer, поскольку дескриптор сокета валидный, на него делается accept который возвращает ошибку из за условий описанных в п.4.

Вот такой сценарий и приводит видимо к описанной автором темы ситуации.

Автор: VitaminCPP 17.1.2008, 20:34
onsql, ты прав. проблема в bind, ошибка Address already in use.
Попробовал поставить в опциях SO_REUSEADDR , сервер запускается сразу, клиент подключается, но данные не посылаются. 
Но я уже на верном пути, знаю точно что за ошибка. Тему пока не помечаю как решенную, может еще кто-то чего посоветует...

Автор: GrayCardinal 18.1.2008, 20:52
Цитата

Но я уже на верном пути, знаю точно что за ошибка. Тему пока не помечаю как решенную, может еще кто-то чего посоветует... 

 smile 
"Омская 1" рулит smile
 smile 

Автор: VitaminCPP 21.1.2008, 00:07
ладно, всё.

Автор: GremlinProg 23.1.2008, 17:08
Насоветовали много, в деталях, по ошибкам, а главное - упустили.

Вообще, странно было увидеть индивидуальное выделение сервера для каждого клиента, обычно это делается немного иначе. Сервер всегда один, клиенты, конечно можно обрабатывать в потоках, но по потоку на каждый клиент - слижком уж расточительно и небезопасно (обычный результат - перегрузка машины, на которой висит сервер).

Можно, конечно, использовать несколько серверов, но тогда уж и делить их нужно по портам (даже биндить можно на один адрес, но порты должны отличаться), в общем, это не так удобно и просто, как кажется. В развитии темы: даже в таком случае, при разделенных портами серверов, должен быть один - связующий, который раскидывает клиентов по свободным серверам, такая система даже не оправдывает свою себестоимость

По поводу портов: они будут свободны, пока не заняты )
Второй экземпляр сервера на том же адресе и том же порту в принципе не возможен до тех пор, пока первый экземпляр не закроется, отсюда, кстати, и происходят конфликты различных сетевых приложений, к примеру: проксирование на "общеизвестных" портах приводит к невозможности запуска различных "общеизвестных" служб, типа ftp, http, ssh, telnet и т.п.

Автор: VitaminCPP 25.1.2008, 00:30
GremlinProg, у моего приложения на один сервер может быть только один клиент. Игра 1 на 1. Всё продумано  smile 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)