Ошибка использования сокета при повторном использовании сокетов - PullRequest
3 голосов
/ 16 сентября 2009

Я пишу клиент XMLRPC на C ++, который предназначен для общения с сервером Python XMLRPC.

К сожалению, в настоящее время сервер Python XMLRPC может отправлять только один запрос на соединение, затем он закрывается, я обнаружил это благодаря ответу mhawke на мой предыдущий запрос о связанной теме

Из-за этого мне приходится создавать новое сокет-соединение с моим python-сервером каждый раз, когда я хочу сделать запрос XMLRPC. Это означает создание и удаление множества сокетов. Все отлично работает, пока не подойду ~ 4000 запросов. В этот момент я получаю ошибку сокета 10048, сокет используется .

Я пытался перевести поток в режим ожидания, чтобы позволить winsock исправить свои файловые дескрипторы, трюк, который работал, когда у моего клиента Python была идентичная проблема, но безрезультатно. Я пробовал следующее

int err = setsockopt(s_,SOL_SOCKET,SO_REUSEADDR,(char*)TRUE,sizeof(BOOL));

безуспешно.

Я использую winsock 2.0, поэтому WSADATA :: iMaxSockets не должны вступать в игру, и в любом случае я проверил и установил 0 (я предполагаю, что это означает бесконечность)

4000 запросов не похоже на странное количество запросов, которые нужно выполнить во время работы приложения. Есть ли способ использовать SO_KEEPALIVE на стороне клиента, когда сервер постоянно закрывается и снова открывается?

Я что-то упустил?

Ответы [ 3 ]

11 голосов
/ 17 сентября 2009

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

Windows динамически распределяет номера портов в диапазоне 1024-5000 (3977 портов), если вы явно не привязываете адрес сокета. Этот код Python демонстрирует проблему:

import socket
sockets = []
while True:
    s = socket.socket()
    s.connect(('some_host', 80))
    sockets.append(s.getsockname())
    s.close()

print len(sockets)    
sockets.sort()
print "Lowest port: ", sockets[0][1], " Highest port: ", sockets[-1][1]
# on Windows you should see something like this...
3960
Lowest port: 1025  Highest port: 5000

Если вы попытаетесь запустить это немедленно, он должен очень быстро выйти из строя, поскольку все динамические порты находятся в состоянии TIME_WAIT.

Есть несколько способов обойти это:

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

    port = 5000
    while True:
        s = socket.socket()
        s.bind(('your_host', port))
        s.connect(('some_host', 80))
        s.close()
        port += 1
    
  2. Скрипка с сокетом SO_LINGER вариант. Я обнаружил, что это иногда работает в Windows (хотя точно не знаю почему) s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, 1)

  3. Я не знаю, поможет ли это ваше конкретное приложение, Тем не менее, можно отправить несколько запросов XMLRPC над то же соединение, используя multicall метод. В принципе это позволяет вам накапливать несколько запросов, а затем отправить их все сразу. Вы не получите никаких ответы, пока вы не отправите накопленные запросы, так что вы можете по сути, думать об этом как о партии обработка - соответствует ли это дизайн вашего приложения?

1 голос
/ 17 сентября 2009

Обновление:

Я бросил это в код, и теперь, похоже, оно работает.

if(::connect(s_, (sockaddr *) &addr, sizeof(sockaddr))) 
  {
    int err = WSAGetLastError();
    if(err == 10048)   //if socket in user error,   force kill and reopen socket
    {
        closesocket(s_);
        WSACleanup();
        WSADATA info;
        WSAStartup(MAKEWORD(2,0), &info);
        s_ = socket(AF_INET,SOCK_STREAM,0);
        setsockopt(s_,SOL_SOCKET,SO_REUSEADDR,(char*)&x,sizeof(BOOL));
    }
  }

Обычно, если вы столкнулись с ошибкой 10048 (используется сокет), вы можете просто закрыть сокет, вызвать очистку и перезапустить WSA, сбросить сокет и его сокет

(последний сокет может не понадобиться)

Должно быть, раньше я пропускал вызовы WSACleanup / WSAStartup, потому что closesocket () и socket () определенно вызывались

эта ошибка возникает только один раз каждые 4000 звонков.

Мне любопытно, почему это может быть, хотя, кажется, это и исправляет. Если у кого-то есть какие-либо комментарии по этому вопросу, мне было бы очень интересно услышать это

0 голосов
/ 16 сентября 2009

Вы закрываете розетки после его использования?

...