Подтвердить что ты не робот

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

Во-первых, немного фона для объяснения мотивации: я работаю над очень простым TCP-зеркальным зеркалом TCP на основе select(), который позволяет двум брандмауэрам-клиентам косвенно общаться друг с другом. Оба клиента подключаются к этому серверу, и как только оба клиента подключены, любые TCP-байты, отправленные на сервер клиентом A, перенаправляются клиенту B и наоборот.

Это более-менее работает с одним небольшим успехом: если клиент A подключается к серверу и начинает отправлять данные до того, как клиент B подключился, серверу негде разместить данные. Я не хочу его буферизировать в ОЗУ, так как это может привести к использованию большого количества ОЗУ; и я не хочу просто отбрасывать данные, так как это может понадобиться клиенту B. Поэтому я выбираю третий вариант, который не должен выбирать() - для чтения в клиентском сокете, пока клиент B также не подключится. Таким образом, клиент A блокируется, пока все не будет готово.

Это более или менее работает, но побочный эффект не для выбора-для-чтения-готовности на клиентском соке состоит в том, что если клиент A решает закрыть свое TCP-соединение с сервером, сервер не получает уведомления о этот факт - по крайней мере, только до тех пор, пока не появится клиент B, и сервер окончательно выберет для чтения в клиентском сокете, прочитает все ожидающие данные и затем получит уведомление с закрытым сокетом (то есть recv() возвращает 0).

Я бы предпочел, чтобы у сервера был некоторый способ узнать (своевременно), когда клиент А закрыл свое TCP-соединение. Есть ли способ узнать это? Опрос был бы приемлемым в этом случае (например, я мог бы выбрать() просыпаться один раз в минуту и ​​вызывать IsSocketStillConnected (носок) во всех сокетах, если такая функция существует).

4b9b3361

Ответ 1

Похоже, что ответ на мой вопрос "нет", если только вы не желаете и не можете изменить свой стек TCP, чтобы получить доступ к необходимой частной информации о состоянии сокета ".

Поскольку я не могу этого сделать, моим решением было перепроектировать прокси-сервер, чтобы всегда считывать данные со всех клиентов и удалять любые данные, поступающие с клиента, чей партнер еще не подключен. Это неоптимально, так как это означает, что потоки TCP, проходящие через прокси-сервер, больше не обладают потокоподобным свойством надежной доставки в порядке доставки, которое ожидают программы, использующие TCP-протокол, но этого будет достаточно для моей цели.

Ответ 2

Если вы хотите проверить, действительно ли сокет фактически закрыт вместо данных, вы можете добавить флаг MSG_PEEK на recv(), чтобы увидеть, были ли данные получены или вы получили 0 или ошибку.

/* handle readable on A */
if (B_is_not_connected) {
    char c;
    ssize_t x = recv(A_sock, &c, 1, MSG_PEEK);
    if (x > 0) {
        /* ...have data, leave it in socket buffer until B connects */
    } else if (x == 0) {
        /* ...handle FIN from A */
    } else {
        /* ...handle errors */
    }
}

Даже если A закрывается после отправки некоторых данных, ваш прокси-сервер, вероятно, хочет перенести эти данные в B сначала перед отправкой FIN на B, поэтому нет смысла знать, что A отправил FIN на соединение раньше, чем прочитав все отправленные им данные.

TCP-соединение не считается закрытым до тех пор, пока обе стороны не отправят FIN. Однако, если A принудительно выключит свою конечную точку, вы не узнаете об этом до тех пор, пока вы не попытаетесь отправить данные на него и не получите EPIPE (при условии, что вы подавили SIGPIPE).


После прочтения вашего зеркального прокси-приложения немного больше, поскольку это приложение обхода брандмауэра, кажется, что вам действительно нужен небольшой протокол управления, чтобы вы могли убедиться, что этим сверстникам действительно разрешено разговаривать друг с другом. Если у вас есть протокол управления, у вас есть много решений, доступных вам, но тот, который я бы защищал, состоял бы в том, чтобы одно из соединений описывало себя как сервер, а другое соединение описывало себя как клиент. Затем вы можете reset установить соединение с клиентом, если нет присутствия сервера для его соединения. Вы можете позволить серверам дождаться соединения с клиентом до некоторого таймаута. Сервер не должен запускать какие-либо данные, и если он работает без подключенного клиента, вы можете reset подключиться к серверу. Это устраняет проблему буферизации данных для мертвого соединения.

Ответ 3

Я не вижу проблемы, как вы ее видите. Пусть говорят, что A подключается к серверу, отправляет некоторые данные и закрывается, он не нуждается в каком-либо сообщении. Сервер не будет считывать свои данные до тех пор, пока B не подключится, как только сервер прочитает сокет A и отправит данные на B. Первое чтение вернет отправленные данные A, а второе возвращает либо 0, либо -1 в том случае, когда сокет закрыто, закрыть сервер B. Предположим, что A отправляет большой фрагмент данных, метод A send() блокируется до тех пор, пока сервер не начнет считывать и не будет потреблять буфер.

Я бы использовал функцию с select, которая возвращает 0, 1, 2, 11, 22 или -1, где;

  • 0 = Нет данных в любом сокете (время ожидания)
  • 1 = A имеет данные для чтения
  • 2 = B имеет данные для чтения
  • 11 = Сокет имеет ошибку (отключен)
  • 22 = Разъем B имеет ошибку (отключен)
  • -1: Один/оба сокета/недействительны

    int WhichSocket(int sd1, int sd2, int seconds, int microsecs) { 
       fd_set sfds, efds;
       struct timeval timeout={0, 0};
       int bigger;
       int ret;
    
    
       FD_ZERO(&sfds);
       FD_SET(sd1, &sfds);
       FD_SET(sd2, &sfds);
       FD_SET(sd1, &efds);
       FD_SET(sd2, &efds);
       timeout.tv_sec=seconds;
       timeout.tv_usec=microsecs;
       if (sd1 > sd2) bigger=sd1;
       else bigger=sd2;
       // bigger is necessary to be Berkeley compatible, Microsoft ignore this param.
       ret = select(bigger+1, &sfds, NULL, &efds, &timeout);
       if (ret > 0) {
           if (FD_ISSET(sd1, &sfds)) return(1); // sd1 has data
           if (FD_ISSET(sd2, &sfds)) return(2); // sd2 has data
           if (FD_ISSET(sd1, &efds)) return(11);    // sd1 has an error
           if (FD_ISSET(sd2, &efds)) return(22);    // sd2 has an error
       }
       else if (ret < 0) return -1; // one of the socket is not valid
       return(0); // timeout
    }
    

Ответ 4

Для меня решение состояло в опросе статуса сокета.

В Windows 10 появился следующий код (но эквивалентные реализации, похоже, существуют для других систем):

WSAPOLLFD polledSocket;
polledSocket.fd = socketItf;
polledSocket.events = POLLRDNORM | POLLWRNORM;

if (WSAPoll(&polledSocket, 1, 0) > 0)
{
    if (polledSocket.revents &= (POLLERR | POLLHUP))
    {
        // socket closed
        return FALSE;
    }
}

Ответ 5

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

Итак, если клиент A отправляет данные и закрывает соединение до того, как соединение будет открыто на B, не беспокойтесь, просто переведите данные в B в обычном режиме (при открытии соединения с B).

Нет необходимости реализовывать специальную обработку для этого сценария.

Ваш прокси обнаруживает закрытое соединение, когда:

  • read возвращает ноль после открытия соединения с B и считываются все ожидающие данные из A. или
  • ваши программы пытаются отправить данные (от B) до A.

Ответ 6

Вы можете проверить, все еще подключен сокет, пытаясь записать в дескриптор файла для каждого сокета. Тогда, если возвращаемое значение записи равно -1 или если errno = EPIPE, вы знаете, что сокет был закрыт. Например,

int isSockStillConnected(int *fileDescriptors, int numFDs){
     int i,n;
     for (i=0;i<numFDs;i++){
          n = write(fileDescriptors+i,"heartbeat",9);
          if (n < 0) return -1;
          if (errno == EPIPE) return -1;
     }
     //made it here, must be okay
     return 0;
}