отправить и recv на одном сокете из разных потоков не работает

Я читал, что это должно быть безопасно от разных потоков одновременно, но моя программа имеет странное поведение, и я не знаю, что не так.

У меня есть параллельные потоки, взаимодействующие с клиентским сокетом

  1. один делает отправить в сокет
  2. один делает select, а затем recv из того же сокета

Поскольку я все еще отправляю, клиент уже получил данные и закрыл сокет.
В то же время я делаю select и recv для этого сокета, который возвращает 0 (поскольку он закрыт), поэтому я закрываю этот сокет. Однако отправка еще не вернулась … и так как я вызываю close на этом сокете, отправка вызова завершается неудачно с EBADF.

Я знаю, что клиент получил данные правильно, так как я выводил их после закрытия сокета, и это правильно. Однако, с моей стороны, мой отправляющий вызов все еще возвращает ошибку (EBADF), поэтому я хочу исправить ее, чтобы она не потерпела неудачу.

Это не всегда происходит. Это случается, может быть, в 40% случаев. Я нигде не сплю. Я должен иметь паузы между посылками или recvs или что-нибудь?

Вот некоторый код:

Посылка:

while(true)
{
// keep sending until send returns 0
n = send(_sfd, bytesPtr, sentSize, 0);

if (n == 0)
{
break;
}
else if(n<0)
{
cerr << "ERROR: send returned an error "<<errno<< endl; // this case is triggered
return n;
}

sentSize -= n;
bytesPtr += n;
}

Прием:

 while(true)
{
memset(bufferPointer,0,sizeLeft);
n = recv(_sfd,bufferPointer,sizeLeft, 0);
if (debug) cerr << "Receiving..."<<sizeLeft<<endl;
if(n == 0)
{
cerr << "Connection closed"<<endl; // this case is triggered
return n;
}
else if (n < 0)
{
cerr << "ERROR reading from socket"<<endl;
return n;
}
bufferPointer += n;
sizeLeft -= n;
if(sizeLeft <= 0) break;

}

На клиенте я использую тот же код получения, затем вызываю close () для сокета.
Затем, со своей стороны, я получаю 0 при получении вызова, а также вызову close () на сокете
Тогда моя отправка не удалась. Это все еще не закончено ?! Но мой клиент уже получил данные!

2

Решение

Должен признаться, я удивлен, что вы видите эту проблему так же часто, как и вы, но это всегда возможно, когда вы имеете дело с потоками. Когда вы звоните send() в конечном итоге вы войдете в ядро, чтобы добавить данные в буфер сокетов, и поэтому весьма вероятно, что произойдет переключение контекста, возможно, к другому процессу в системе. Между тем ядро, вероятно, буферизовало и передало пакет довольно быстро. Я предполагаю, что вы тестируете в локальной сети, поэтому другой конец получает данные, закрывает соединение и очень быстро отправляет соответствующий FIN обратно. Все это может произойти, когда на отправляющей машине все еще выполняются другие потоки или процессы, потому что задержка в локальной сети Ethernet очень мала.

Теперь приходит FIN — ваш приемный поток в последнее время мало что сделал, так как он ждал ввода. Поэтому многие системы планирования немного повысят свой приоритет, и есть большая вероятность, что он будет запущен следующим (вы не указываете, какую ОС вы используете, но это может произойти, по крайней мере, в Linux, например). Этот поток закрывает сокет из-за его нулевого чтения. В какой-то момент вскоре после этого отправляющий поток будет повторно пробужден, но, по-видимому, ядро ​​замечает, что сокет закрыт, прежде чем он вернется из заблокированного send() и возвращается EBADF,

Теперь это всего лишь предположение относительно точной причины — помимо прочего, это сильно зависит от вашей платформы. Но вы можете видеть, как это могло произойти.

Самым простым решением, вероятно, является использование poll() в потоке отправки, но подождите, пока сокет не станет готовым к записи, а не готовым к чтению. Очевидно, что вам также нужно дождаться отправки буферизованных данных — то, как вы это сделаете, зависит от того, какой поток буферизует данные. poll() вызов позволит вам определить, когда соединение было закрыто, пометив его POLLHUP, который вы можете обнаружить, прежде чем пытаться send(),

Как правило, вы не должны закрывать сокет, пока не убедитесь, что буфер отправки полностью очищен — вы можете быть уверены в этом только после send() вызов вернулся и указывает, что все оставшиеся данные вышли. Я занимался этим в прошлом, проверяя буфер отправки, когда получаю нулевое чтение, и если он не пустой, я устанавливаю флаг «закрытия». В вашем случае отправляющий поток будет использовать это как подсказку для закрытия, когда все будет очищено. Это важно, потому что, если удаленный конец делает половину с shutdown() тогда вы получите нулевое чтение, даже если оно все еще читает. Однако вам может быть наплевать на половину закрытия, и тогда ваша стратегия в порядке.

Наконец, я бы лично избежал хлопот, связанных с отправкой и получением потоков, и имел бы только один поток, который выполняет и то, и другое — это более или менее важно select() а также poll(), чтобы позволить одному потоку выполнения иметь дело с одним или несколькими файловыми дескрипторами, не беспокоясь о выполнении операции, которая блокирует и блокирует другие соединения.

5

Другие решения

Нашел проблему. Это с моей петлей. Обратите внимание, что это бесконечный цикл. Когда у меня больше не осталось отправки, мой sentSize равен 0, но я все равно буду зацикливаться, чтобы попытаться отправить больше. В это время другой поток уже закрыл этот поток, и поэтому мой вызов отправки для 0 байтов возвращается с ошибкой.

Я исправил это, изменив цикл, чтобы остановить цикл, когда sentSize равен 0, и это исправило проблему!

4

По вопросам рекламы [email protected]